HashMap快速实现存取的原理(2)

HashMap的底层数组长度总是2的n次方,在构造函数中存在:capacity <<= 1;这样做总是能够保证HashMap的底层数组长度为2的n次方。当length为2的n次方时,h&(length - 1)就相当于对length取模,而且速度比直接取模快得多,这是HashMap在速度上的一个优化。至于为什么是2的n次方下面解释。
      我们回到indexFor方法,该方法仅有一条语句:h&(length - 1)  作用:均匀分布table数据和充分利用空间。
      这里我们假设length为16(2^n)和15,h为5、6、7。

length = 16  
h   length - 1   h & length -1      
5   15   0101 & 1111 = 00101   5  
6   15   0110 & 1111 = 00110   6  
7   15   0111 & 1111 = 00111   7  
length = 15  
5   14   0101 & 1110 = 00101   5  
6   14   0110 & 1110 = 00110   6  
7   14   0111 & 1110 = 00110   6  

当n=15时,6和7的结果一样,这样表示他们在table存储的位置是相同的,也就是产生了碰撞,6、7就会在一个位置形成链表,这样就会导致查询速度降低。诚然这里只分析三个数字不是很多,那么我们就看0-15。

h   length - 1   h & length - 1      
0   14   0000 & 1110 = 0000   0  
1   14   0001 & 1110 = 0000   0  
2   14   0010 & 1110 = 0010   2  
3   14   0011 & 1110 = 0010   2  
4   14   0100 & 1110 = 0100   4  
5   14   0101 & 1110 = 0100   4  
6   14   0110 & 1110 = 0110   6  
7   14   0111 & 1110 = 0110   6  
8   14   1000 & 1110 = 1000   8  
9   14   1001 & 1110 = 1000   8  
10   14   1010 & 1110 = 1010   10  
11   14   1011 & 1110 = 1010   10  
12   14   1100 & 1110 = 1100   12  
13   14   1101 & 1110 = 1100   12  
14   14   1110 & 1110 = 1110   14  
15   14   1111 & 1110 = 1110   14  

从上面的图表中我们看到总共发生了8此碰撞,同时发现浪费的空间非常大,有1、3、5、7、9、11、13、15处没有记录,也就是没有存放数据。这是因为他们在与14进行&运算时,得到的结果最后一位永远都是0,即0001、0011、0101、0111、1001、1011、1101、1111位置处是不可能存储数据的,空间减少,进一步增加碰撞几率,这样就会导致查询速度慢。而当length = 16时,length – 1 = 15 即1111,那么进行低位&运算时,值总是与原来hash值相同,而进行高位运算时,其值等于其低位值。所以说当length = 2^n时,不同的hash值发生碰撞的概率比较小,这样就会使得数据在table数组中分布较均匀,查询速度也较快。
      这里我们再来复习put的流程:当我们想一个HashMap中添加一对key-value时,系统首先会计算key的hash值,然后根据hash值确认在table中存储的位置。若该位置没有元素,则直接插入。否则迭代该处元素链表并依此比较其key的hash值。如果两个hash值相等且key值相等(e.hash == hash && ((k = e.key) == key || key.equals(k))),则用新的Entry的value覆盖原来节点的value。如果两个hash值相等但key值不等 ,则将该节点插入该链表的链头。具体的实现过程见addEntry方法,如下:

void addEntry(int hash, K key, V value, int bucketIndex) {  
    // 获取bucketIndex处的Entry
Entry<K,V> e = table[bucketIndex];  
// 将新创建的 Entry 放入 bucketIndex 索引处,并让新的 Entry 指向原来的 Entry
table[bucketIndex] = new Entry<K,V>(hash, key, value, e);  
    // 若HashMap中元素的个数超过极限了,则容量扩大两倍
if (size++ >= threshold)  
    resize(2 * table.length);  
}
 

内容版权声明:除非注明,否则皆为本站原创文章。

转载注明出处:https://www.heiqu.com/884bacaae670bded5a95a3efe6f5752e.html