java数据结构-HashMap
来源:互联网 发布:moka软件下载 编辑:程序博客网 时间:2024/06/06 00:25
下面以HashMap为例子,深入对Map的实现机制进行了解,在这个过程中,请打开jdk源码。
Hash算法
HashMap使用Hash算法,所以在解剖HashMap之间,需要先简单的了解Hash算法,Hash算法一般也成为散列算法,通过散列算法将任意的值转化成固定的长度输出,该输出就是散列值,这是一种压缩映射,也就是,散列值的空间远远小于输入的值空间。
简单的说,hash算法的意义在于提供了一种快速存取数据的方法,它用一种算法建立键值与真实值之间的对应关系,(每一个真实值只能有一个键值,但是一个键值可以对应多个真实值),这样可以快速在数组等里面存取数据。
下面我们建立一个HashMap,然后往里面放入12对key-value,这个HashMap的默认数组长度为16,我们的key分别存放在该数组的格子中,每个格子下面存放的元素又是以链表的方式存放元素。
当我们新添加一个元素时,首先我们通过Hash算法计算出这个元素的Hash值的hashcode,通过这个hashcode的值,我们就可以计算出这个新元素应该存放在这个hash表的哪个格子里面,如果这个格子中已经存在元素,那么就把新的元素加入到已经存在格子元素的链表中。
运行上面的程序,我们对HashMap源码进行一点修改,打印出每个key对象的hash值
What-->hash值:8
You-->hash值:3
Don't-->hash值:7
Know-->hash值:13
About-->hash值:11
Geo-->hash值:12
APIs-->hash值:1
Can't-->hash值:7
Hurt-->hash值:1
you-->hash值:10
google-->hash值:3
map-->hash值:8
hello-->hash值:0
计算出来的Hash值分别代表该key应该存放在Hash表中对应数字的格子中,如果该格子已经有元素存在,那么该key就以链表的方式依次放入格子中
从上表可以看出,Hash表是线性表和链表的综合所得,根据数据结构的定义,可以得出粗劣的结论,Hash算法的存取速度要比数组差一些,但是比起单纯的链表,在查找和存取方面却要好多。
如果要查找一个元素时,同样的方式,通过Hash函数计算出这个元素的Hash值hashcode,然后通过这个hashcode值,直接找到跟这个hash值相对应的线性格子,进如该格子后,对这个格子存放的链表元素逐个进行比较,直到找到对应的hash值。
在简单了解完Hash算法后,我们打开HashMap源码
初始化HashMap
下面我们看看Map map = new HashMap();这段代码究竟做了什么,发生了什么数据结构的变化。
HashMap中几个重要的属性
transient Entry[] table;
用来保存key-value的对象Entry数组,也就是Hash表
transient int size;
返回HashMap的键值对个数
final float loadFactor;
负载因子,用来决定Entry数组是否扩容的因子,HashMap默认是0.75f
int threshold;
重构因子,(capacity * load factor)负载因子与Entry[]数组容积的乘值
首先是要确定hashMap的初始化的长度,这里使用的策略是循环查出一个大于initialCapacity的2的次方的数,例如initialCapacity的值是10,那么大于10的数是2的4次方,也就是16,capacity的值被赋予了16,那么实际上table数组的长度是16,之所以采用这样的策略来构建Hash表的长度,是因为2的次方运算对于计算机来说是有相当的效率。
loadFactor,被称为负载因子,HashMap的默认负载因子是0.75f
threshold,接下来是重构因子,由负载因子和容量的乘机组成,它表示当HashMap元素被存放了多少个之后,需要对HashMap进行重构。
通过这一系列的计算和定义后,初始化Entry[] table;
put(key,value)
接下来看一对key-value是如何被存放到HashMap中:put(key,value)
这里是整个hash的关键,请打开源码查看一步一步查看。
hash(key.hashCode()) 计算出key的hash码 //对于hash()的算法,这里有一篇分析很透彻的文章<HashMap hash方法分析>
indexFor(hash, table.length) 通过一个与算法计算出来,该key应在存放在Hash表的哪个格子中。
for (Entry<K,V> e = table[i]; e != null; e = e.next) 然后再遍历table[i]格中的链表,判断是否已经存在一样的key,如果存在一样的key值,那么就用新的value覆盖旧的value,并把旧的value值返回。
addEntry(hash, key, value, i) 如果经过遍历链表没有发现同样的key,那么进行addEntry函数的操作,增加当前key到hash表中的第i个格子中的链表中
Entry<K,V> e = table[bucketIndex]; 创建一个Entry对象来存放键值(ps:Entry对象是一个链表对象)
table[bucketIndex] = new Entry<K,V>(hash, key, value, e); 将Entry对象添加到链表中
if (size++ >= threshold) resize(2 * table.length); 最后将size进行自增,判断size值是否大于重构因子,如果大于那么就是用resize进行扩容重构。
这里为什么是否需要扩容重构,其实是涉及到负载因子的性能问题
loadFactor负载因子
上面说过loadFactor是一个hashMap的决定性属性,HashSet和HashMap的默认负载因子都是0.75,它表示,如果哈希表的容量超过3/4时,将自动成倍的增加哈希表的容量,这个值是权衡了时间和空间的成本,如果负载因子较高,虽然会减少对内存空间的需求,但也会增加查找数据的时间开销,无论是put()和get()都涉及到对数据进行查找的动作,所以负载因子是不适宜设置过高
get(key)
接下来看看get(key)做了什么
这些动作似乎是跟put(key,value)相识,通过hash算法获取key的hash码,再通过indexFor定位出该key存在于table的哪一个下表,获取该下标然后对下标中的链表进行遍历比对,如果有符合就直接返回该key的value值。
keySet()
这里还涉及另一个问题,上面说了HashMap是跟set没有任何亲属关系,但map也一样实现了keySet接口,下面谱析一下keySet在hashMap中是如何实现的,这里给出部分代码,请结合源码查看
代码很简单,就是对每个格子里面的链表进行遍历,也正是这个原因,当我们依次将key值put进hashMap中,但在使用map.entrySet().iterator()进行遍历时候却不是put时候的顺序。
扩容
在前面说到put函数的时候,已经提过了扩容的问题
这里一个是否扩容的判断,当数据达到了threshold所谓的重构因子,而不是HashMap的最大容量,就进行扩容。
void resize(int newCapacity) {
Entry[] oldTable = table;
int oldCapacity = oldTable.length;
if (oldCapacity == MAXIMUM_CAPACITY) {
threshold = Integer.MAX_VALUE;
return;
}
Entry[] newTable = new Entry[newCapacity];
transfer(newTable);
table = newTable;
threshold = (int)(newCapacity * loadFactor);
}
void transfer(Entry[] newTable) {
Entry[] src = table;
int newCapacity = newTable.length;
for (int j = 0; j < src.length; j++) {
Entry<K,V> e = src[j];
if (e != null) {
src[j] = null;
do {
Entry<K,V> next = e.next;
int i = indexFor(e.hash, newCapacity);
e.next = newTable[i];
newTable[i] = e;
e = next;
} while (e != null);
}
}
}
transfer方法实际上是将所有的元素重新进行一些hash,这是因为容量变化了,每个元素相对应的hash值也会不一样。
使用HashMap
1.不要再高并发中使用HashMap,HashMap是线程不安全,如果被多个线程共享之后,将可能发生不可预知的问题。
2.如果数据大小事固定的,最好在初始化的时候就给HashMap一个合理的容量值,如果使用new HashMap()默认构造函数,重构因子的值是16*0.75=12,当HashMap的容量超过了12后,就会进行一系列的扩容运算,重建一个原来成倍的数组,并且对原来存在的元素进行重新的hash运算,如果你的数据是有成千上万的,那么你的成千上万的数据也要跟这你的扩容不断的hash,这将产生高额的内存和cpu的大量开销。
其中indexFor和hash源码如下:
indexFor这个方法论坛中已有人分析过,这里就不再分析。
现在分析一下hash算法:
假设key.hashCode()的值为:0x7FFFFFFF,table.length为默认值16。
上面算法执行如下:
得到i=15
其中h^(h>>>7)^(h>>>4) 结果中的位运行标识是把h>>>7 换成 h>>>8来看。
即最后h^(h>>>8)^(h>>>4) 运算后hashCode值每位数值如下:
8=8
7=7^8
6=6^7^8
5=5^8^7^6
4=4^7^6^5^8
3=3^8^6^5^8^4^7
2=2^7^5^4^7^3^8^6
1=1^6^4^3^8^6^2^7^5
结果中的1、2、3三位出现重复位^运算
3=3^8^6^5^8^4^7 -> 3^6^5^4^7
2=2^7^5^4^7^3^8^6 -> 2^5^4^3^8^6
1=1^6^4^3^8^6^2^7^5 -> 1^4^3^8^2^7^5
算法中是采用(h>>>7)而不是(h>>>8)的算法,应该是考虑1、2、3三位出现重复位^运算的情况。使得最低位上原hashCode的8位都参与了^运算,所以在table.length为默认值16的情况下面,hashCode任意位的变化基本都能反应到最终hash table 定位算法中,这种情况下只有原hashCode第3位高1位变化不会反应到结果中,即:0x7FFFF7FF的i=15。
Hash算法
HashMap使用Hash算法,所以在解剖HashMap之间,需要先简单的了解Hash算法,Hash算法一般也成为散列算法,通过散列算法将任意的值转化成固定的长度输出,该输出就是散列值,这是一种压缩映射,也就是,散列值的空间远远小于输入的值空间。
简单的说,hash算法的意义在于提供了一种快速存取数据的方法,它用一种算法建立键值与真实值之间的对应关系,(每一个真实值只能有一个键值,但是一个键值可以对应多个真实值),这样可以快速在数组等里面存取数据。
下面我们建立一个HashMap,然后往里面放入12对key-value,这个HashMap的默认数组长度为16,我们的key分别存放在该数组的格子中,每个格子下面存放的元素又是以链表的方式存放元素。
public static void main(String[] args) {
Map map = new HashMap();
map.put("What", "chenyz");
map.put("You", "chenyz");
map.put("Don't", "chenyz");
map.put("Know", "chenyz");
map.put("About", "chenyz");
map.put("Geo", "chenyz");
map.put("APIs", "chenyz");
map.put("Can't", "chenyz");
map.put("Hurt", "chenyz");
map.put("you", "chenyz");
map.put("google", "chenyz");
map.put("map", "chenyz");
map.put("hello", "chenyz");
}
Map map = new HashMap();
map.put("What", "chenyz");
map.put("You", "chenyz");
map.put("Don't", "chenyz");
map.put("Know", "chenyz");
map.put("About", "chenyz");
map.put("Geo", "chenyz");
map.put("APIs", "chenyz");
map.put("Can't", "chenyz");
map.put("Hurt", "chenyz");
map.put("you", "chenyz");
map.put("google", "chenyz");
map.put("map", "chenyz");
map.put("hello", "chenyz");
}
当我们新添加一个元素时,首先我们通过Hash算法计算出这个元素的Hash值的hashcode,通过这个hashcode的值,我们就可以计算出这个新元素应该存放在这个hash表的哪个格子里面,如果这个格子中已经存在元素,那么就把新的元素加入到已经存在格子元素的链表中。
运行上面的程序,我们对HashMap源码进行一点修改,打印出每个key对象的hash值
What-->hash值:8
You-->hash值:3
Don't-->hash值:7
Know-->hash值:13
About-->hash值:11
Geo-->hash值:12
APIs-->hash值:1
Can't-->hash值:7
Hurt-->hash值:1
you-->hash值:10
google-->hash值:3
map-->hash值:8
hello-->hash值:0
计算出来的Hash值分别代表该key应该存放在Hash表中对应数字的格子中,如果该格子已经有元素存在,那么该key就以链表的方式依次放入格子中
从上表可以看出,Hash表是线性表和链表的综合所得,根据数据结构的定义,可以得出粗劣的结论,Hash算法的存取速度要比数组差一些,但是比起单纯的链表,在查找和存取方面却要好多。
如果要查找一个元素时,同样的方式,通过Hash函数计算出这个元素的Hash值hashcode,然后通过这个hashcode值,直接找到跟这个hash值相对应的线性格子,进如该格子后,对这个格子存放的链表元素逐个进行比较,直到找到对应的hash值。
在简单了解完Hash算法后,我们打开HashMap源码
初始化HashMap
下面我们看看Map map = new HashMap();这段代码究竟做了什么,发生了什么数据结构的变化。
HashMap中几个重要的属性
transient Entry[] table;
用来保存key-value的对象Entry数组,也就是Hash表
transient int size;
返回HashMap的键值对个数
final float loadFactor;
负载因子,用来决定Entry数组是否扩容的因子,HashMap默认是0.75f
int threshold;
重构因子,(capacity * load factor)负载因子与Entry[]数组容积的乘值
public class HashMap<K,V>
extends AbstractMap<K,V>
implements Map<K,V>, Cloneable, Serializable
{
int threshold;
final float loadFactor;
transient Entry[] table;
static final float DEFAULT_LOAD_FACTOR = 0.75f;
static final int DEFAULT_INITIAL_CAPACITY = 16;
public HashMap(int initialCapacity, float loadFactor) {
if (initialCapacity < 0)
throw new IllegalArgumentException("Illegal initial capacity: " +
initialCapacity);
if (initialCapacity > MAXIMUM_CAPACITY)
initialCapacity = MAXIMUM_CAPACITY;
if (loadFactor <= 0 || Float.isNaN(loadFactor))
throw new IllegalArgumentException("Illegal load factor: " +
loadFactor);
// Find a power of 2 >= initialCapacity
int capacity = 1;
while (capacity < initialCapacity)
capacity <<= 1;
this.loadFactor = loadFactor;
threshold = (int)(capacity * loadFactor);
table = new Entry[capacity];
init();
}
以public HashMap(int initialCapacity, float loadFactor)构造函数为例,另外两个构造函数实际上也是以同种方式来构建HashMap.extends AbstractMap<K,V>
implements Map<K,V>, Cloneable, Serializable
{
int threshold;
final float loadFactor;
transient Entry[] table;
static final float DEFAULT_LOAD_FACTOR = 0.75f;
static final int DEFAULT_INITIAL_CAPACITY = 16;
public HashMap(int initialCapacity, float loadFactor) {
if (initialCapacity < 0)
throw new IllegalArgumentException("Illegal initial capacity: " +
initialCapacity);
if (initialCapacity > MAXIMUM_CAPACITY)
initialCapacity = MAXIMUM_CAPACITY;
if (loadFactor <= 0 || Float.isNaN(loadFactor))
throw new IllegalArgumentException("Illegal load factor: " +
loadFactor);
// Find a power of 2 >= initialCapacity
int capacity = 1;
while (capacity < initialCapacity)
capacity <<= 1;
this.loadFactor = loadFactor;
threshold = (int)(capacity * loadFactor);
table = new Entry[capacity];
init();
}
首先是要确定hashMap的初始化的长度,这里使用的策略是循环查出一个大于initialCapacity的2的次方的数,例如initialCapacity的值是10,那么大于10的数是2的4次方,也就是16,capacity的值被赋予了16,那么实际上table数组的长度是16,之所以采用这样的策略来构建Hash表的长度,是因为2的次方运算对于计算机来说是有相当的效率。
loadFactor,被称为负载因子,HashMap的默认负载因子是0.75f
threshold,接下来是重构因子,由负载因子和容量的乘机组成,它表示当HashMap元素被存放了多少个之后,需要对HashMap进行重构。
通过这一系列的计算和定义后,初始化Entry[] table;
put(key,value)
接下来看一对key-value是如何被存放到HashMap中:put(key,value)
public V put(K key, V value) {
if (key == null)
return putForNullKey(value);
int hash = hash(key.hashCode());
int i = indexFor(hash, table.length);
System.out.println(key+"-->hash值:"+i);//这就是刚才程序打印出来的key对应hash值
for (Entry<K,V> e = table[i]; e != null; e = e.next) {
Object k;
if (e.hash == hash && ((k = e.key) == key || key.equals(k))) {
V oldValue = e.value;
e.value = value;
e.recordAccess(this);
return oldValue;
}
}
modCount++;
addEntry(hash, key, value, i);
return null;
}
static int hash(int h) {
h ^= (h >>> 20) ^ (h >>> 12);
return h ^ (h >>> 7) ^ (h >>> 4);
}
static int indexFor(int h, int length) {
return h & (length-1);
}
if (key == null)
return putForNullKey(value);
int hash = hash(key.hashCode());
int i = indexFor(hash, table.length);
System.out.println(key+"-->hash值:"+i);//这就是刚才程序打印出来的key对应hash值
for (Entry<K,V> e = table[i]; e != null; e = e.next) {
Object k;
if (e.hash == hash && ((k = e.key) == key || key.equals(k))) {
V oldValue = e.value;
e.value = value;
e.recordAccess(this);
return oldValue;
}
}
modCount++;
addEntry(hash, key, value, i);
return null;
}
static int hash(int h) {
h ^= (h >>> 20) ^ (h >>> 12);
return h ^ (h >>> 7) ^ (h >>> 4);
}
static int indexFor(int h, int length) {
return h & (length-1);
}
这里是整个hash的关键,请打开源码查看一步一步查看。
hash(key.hashCode()) 计算出key的hash码 //对于hash()的算法,这里有一篇分析很透彻的文章<HashMap hash方法分析>
indexFor(hash, table.length) 通过一个与算法计算出来,该key应在存放在Hash表的哪个格子中。
for (Entry<K,V> e = table[i]; e != null; e = e.next) 然后再遍历table[i]格中的链表,判断是否已经存在一样的key,如果存在一样的key值,那么就用新的value覆盖旧的value,并把旧的value值返回。
addEntry(hash, key, value, i) 如果经过遍历链表没有发现同样的key,那么进行addEntry函数的操作,增加当前key到hash表中的第i个格子中的链表中
void addEntry(int hash, K key, V value, int bucketIndex) {
Entry<K,V> e = table[bucketIndex];
table[bucketIndex] = new Entry<K,V>(hash, key, value, e);
if (size++ >= threshold)
resize(2 * table.length);
}
Entry<K,V> e = table[bucketIndex];
table[bucketIndex] = new Entry<K,V>(hash, key, value, e);
if (size++ >= threshold)
resize(2 * table.length);
}
Entry<K,V> e = table[bucketIndex]; 创建一个Entry对象来存放键值(ps:Entry对象是一个链表对象)
table[bucketIndex] = new Entry<K,V>(hash, key, value, e); 将Entry对象添加到链表中
if (size++ >= threshold) resize(2 * table.length); 最后将size进行自增,判断size值是否大于重构因子,如果大于那么就是用resize进行扩容重构。
void resize(int newCapacity) {
Entry[] oldTable = table;
int oldCapacity = oldTable.length;
if (oldCapacity == MAXIMUM_CAPACITY) {
threshold = Integer.MAX_VALUE;
return;
}
Entry[] newTable = new Entry[newCapacity];
transfer(newTable);
table = newTable;
threshold = (int)(newCapacity * loadFactor);
}
Entry[] oldTable = table;
int oldCapacity = oldTable.length;
if (oldCapacity == MAXIMUM_CAPACITY) {
threshold = Integer.MAX_VALUE;
return;
}
Entry[] newTable = new Entry[newCapacity];
transfer(newTable);
table = newTable;
threshold = (int)(newCapacity * loadFactor);
}
这里为什么是否需要扩容重构,其实是涉及到负载因子的性能问题
loadFactor负载因子
上面说过loadFactor是一个hashMap的决定性属性,HashSet和HashMap的默认负载因子都是0.75,它表示,如果哈希表的容量超过3/4时,将自动成倍的增加哈希表的容量,这个值是权衡了时间和空间的成本,如果负载因子较高,虽然会减少对内存空间的需求,但也会增加查找数据的时间开销,无论是put()和get()都涉及到对数据进行查找的动作,所以负载因子是不适宜设置过高
get(key)
接下来看看get(key)做了什么
public V get(Object key) {
if (key == null)
return getForNullKey();
int hash = hash(key.hashCode());
for (Entry<K,V> e = table[indexFor(hash, table.length)];
e != null;
e = e.next) {
Object k;
if (e.hash == hash && ((k = e.key) == key || key.equals(k)))
return e.value;
}
return null;
}
if (key == null)
return getForNullKey();
int hash = hash(key.hashCode());
for (Entry<K,V> e = table[indexFor(hash, table.length)];
e != null;
e = e.next) {
Object k;
if (e.hash == hash && ((k = e.key) == key || key.equals(k)))
return e.value;
}
return null;
}
这些动作似乎是跟put(key,value)相识,通过hash算法获取key的hash码,再通过indexFor定位出该key存在于table的哪一个下表,获取该下标然后对下标中的链表进行遍历比对,如果有符合就直接返回该key的value值。
keySet()
这里还涉及另一个问题,上面说了HashMap是跟set没有任何亲属关系,但map也一样实现了keySet接口,下面谱析一下keySet在hashMap中是如何实现的,这里给出部分代码,请结合源码查看
public K next() {
return nextEntry().getKey();
}
final Entry<K,V> nextEntry() {
if (modCount != expectedModCount)
throw new ConcurrentModificationException();
Entry<K,V> e = next;
if (e == null)
throw new NoSuchElementException();
if ((next = e.next) == null) {
Entry[] t = table;
while (index < t.length && (next = t[index++]) == null)
;
}
current = e;
return e;
}
return nextEntry().getKey();
}
final Entry<K,V> nextEntry() {
if (modCount != expectedModCount)
throw new ConcurrentModificationException();
Entry<K,V> e = next;
if (e == null)
throw new NoSuchElementException();
if ((next = e.next) == null) {
Entry[] t = table;
while (index < t.length && (next = t[index++]) == null)
;
}
current = e;
return e;
}
代码很简单,就是对每个格子里面的链表进行遍历,也正是这个原因,当我们依次将key值put进hashMap中,但在使用map.entrySet().iterator()进行遍历时候却不是put时候的顺序。
扩容
在前面说到put函数的时候,已经提过了扩容的问题
if (size++ >= threshold)
resize(2 * table.length);
resize(2 * table.length);
这里一个是否扩容的判断,当数据达到了threshold所谓的重构因子,而不是HashMap的最大容量,就进行扩容。
void resize(int newCapacity) {
Entry[] oldTable = table;
int oldCapacity = oldTable.length;
if (oldCapacity == MAXIMUM_CAPACITY) {
threshold = Integer.MAX_VALUE;
return;
}
Entry[] newTable = new Entry[newCapacity];
transfer(newTable);
table = newTable;
threshold = (int)(newCapacity * loadFactor);
}
void transfer(Entry[] newTable) {
Entry[] src = table;
int newCapacity = newTable.length;
for (int j = 0; j < src.length; j++) {
Entry<K,V> e = src[j];
if (e != null) {
src[j] = null;
do {
Entry<K,V> next = e.next;
int i = indexFor(e.hash, newCapacity);
e.next = newTable[i];
newTable[i] = e;
e = next;
} while (e != null);
}
}
}
transfer方法实际上是将所有的元素重新进行一些hash,这是因为容量变化了,每个元素相对应的hash值也会不一样。
使用HashMap
1.不要再高并发中使用HashMap,HashMap是线程不安全,如果被多个线程共享之后,将可能发生不可预知的问题。
2.如果数据大小事固定的,最好在初始化的时候就给HashMap一个合理的容量值,如果使用new HashMap()默认构造函数,重构因子的值是16*0.75=12,当HashMap的容量超过了12后,就会进行一系列的扩容运算,重建一个原来成倍的数组,并且对原来存在的元素进行重新的hash运算,如果你的数据是有成千上万的,那么你的成千上万的数据也要跟这你的扩容不断的hash,这将产生高额的内存和cpu的大量开销。
当然啦,HashMap的函数还有很多,不过都是基于table的链表进行操作,当然也就是hash算法,Map & hashMap在平时我们的应用非常多,最重要的是我们要对每句代码中每块数据结构变化心中有数。
HashMap 中hash table 定位算法:
- int hash = hash(key.hashCode());
- int i = indexFor(hash, table.length);
其中indexFor和hash源码如下:
- /**
- * Applies a supplemental hash function to a given hashCode, which
- * defends against poor quality hash functions. This is critical
- * because HashMap uses power-of-two length hash tables, that
- * otherwise encounter collisions for hashCodes that do not differ
- * in lower bits. Note: Null keys always map to hash 0, thus index 0.
- */
- static int hash(int h) {
- // This function ensures that hashCodes that differ only by
- // constant multiples at each bit position have a bounded
- // number of collisions (approximately 8 at default load factor).
- h ^= (h >>> 20) ^ (h >>> 12);
- return h ^ (h >>> 7) ^ (h >>> 4);
- }
- /**
- * Returns index for hash code h.
- */
- static int indexFor(int h, int length) {
- return h & (length-1);
- }
indexFor这个方法论坛中已有人分析过,这里就不再分析。
现在分析一下hash算法:
- h ^= (h >>> 20) ^ (h >>> 12);
- return h ^ (h >>> 7) ^ (h >>> 4);
假设key.hashCode()的值为:0x7FFFFFFF,table.length为默认值16。
上面算法执行如下:
得到i=15
其中h^(h>>>7)^(h>>>4) 结果中的位运行标识是把h>>>7 换成 h>>>8来看。
即最后h^(h>>>8)^(h>>>4) 运算后hashCode值每位数值如下:
8=8
7=7^8
6=6^7^8
5=5^8^7^6
4=4^7^6^5^8
3=3^8^6^5^8^4^7
2=2^7^5^4^7^3^8^6
1=1^6^4^3^8^6^2^7^5
结果中的1、2、3三位出现重复位^运算
3=3^8^6^5^8^4^7 -> 3^6^5^4^7
2=2^7^5^4^7^3^8^6 -> 2^5^4^3^8^6
1=1^6^4^3^8^6^2^7^5 -> 1^4^3^8^2^7^5
算法中是采用(h>>>7)而不是(h>>>8)的算法,应该是考虑1、2、3三位出现重复位^运算的情况。使得最低位上原hashCode的8位都参与了^运算,所以在table.length为默认值16的情况下面,hashCode任意位的变化基本都能反应到最终hash table 定位算法中,这种情况下只有原hashCode第3位高1位变化不会反应到结果中,即:0x7FFFF7FF的i=15。
- java数据结构-HashMap
- java数据结构-HashMap
- java数据结构-HashMap分析
- java 数据结构--HashMap
- java数据结构-HashMap(转)
- java数据结构-HashMap
- java数据结构-HashMap
- java数据结构-HashMap
- java数据结构-HashMap
- java数据结构-HashMap
- Java 数据结构 HashMap
- java数据结构-HashMap
- java数据结构-HashMap
- java HashMap 原理 数据结构
- Java数据结构-Hashmap
- java HashMap底层数据结构
- java数据结构之HashMap
- Java数据结构源码分析-HashMap
- android adk
- 实时监控方案
- Java知识拾遗:三大框架的技术起源
- DB2错误信息(按sqlcode排序)
- NTDDI_VERSION,_WIN32_WINNT,WINVER,_WIN32_IE
- java数据结构-HashMap
- ABS 增加分享
- C#设置IE代理
- IOS开发-UIPageControl 分页控件
- Client does not support authentication protocol requested by server; consider upgrading MySQL client
- 对DataSet的数据 用 PagedDataSource 进行分页
- asp.net关于时间方法,当前月有多少天、求某年有多少周、求当前日期是一年的中第几周
- Oracle中EXECUTE IMMEDIATE的使用技巧
- MySQL和PostgreSQL的比较