HashMap的扩容机制以及默认大小为何是2次幂
2018-07-05 11:17
627 查看
HashMap的Put方法
HashMap的数据结构设计可以参考链接。接下来回顾HashMap的put(Key k, Value v)过程:(1)对 Key求Hash值,计算出Hash表下标,对应hashCode()方法,所以使用class对象作为Key时需要重写该对象的hashCode()方法与equals()方法。
(2)如果没有碰撞,直接放入桶中,即Hash表数组对应位置的链表表头。
(3)如果碰撞了,若节点已经存在就替换旧值,否则以链表的方式将该元素链接到后面。
(4)如果链表长度超过阀值(TREEIFY_THRESHOLD == 8),就把链表转成红黑树。红黑树我不熟悉,这里不展开讲。
(5)如果桶满了(容量 * 加载因子),就需要resize。
HashMap的扩容机制
假设length为Hash表数组的大小,方法indexFor(int hash, int length)为indexFor(int hash, int length) { return hash % length; }
在旧数组中同一条Entry链上的元素,在resize过程中,通过重新计算索引位置后,有可能被放到了新数组的不同位置上。JDK8做了一些优化,resize过程中对Hash表数组大小的修改使用的是2次幂的扩展(指长度扩为原来2倍),这样有2个好处。
好处1
在hashmap的源码中。put方法会调用indexFor(int h, int length)方法,这个方法主要是根据key的hash值找到这个entry在Hash表数组中的位置,源码如下:/** * Returns index for hash code h. */ static int indexFor(int h, int length) { // assert Integer.bitCount(length) == 1 : "length must be a non-zero power of 2"; return h & (length-1); }
上述代码也相当于对length求模。 注意最后return的是h&(length-1)。如果length不为2的幂,比如15。那么length-1的2进制就会变成1110。在h为随机数的情况下,和1110做&操作。尾数永远为0。那么0001、1001、1101等尾数为1的位置就永远不可能被entry占用。这样会造成浪费,不随机等问题。 length-1 二进制中为1的位数越多,那么分布就平均。
好处2
以下图为例,其中图(a)表示扩容前的key1和key2两种key确定索引位置的示例,图(b)表示扩容后key1和key2两种key确定索引位置的示例,n代表length。元素在重新计算hash之后,因为n变为2倍,那么n-1的mask范围在高位多1bit(红色),因此新的index就会发生这样的变化:
resize过程中不需要像JDK1.7的实现那样重新计算hash,只需要看看原来的hash值新增的那个bit是1还是0就好了,是0的话索引没变,是1的话索引变成“原索引+oldCap”,可以看看下图为16扩充为32的resize示意图(一方面位运算更快,另一方面抗碰撞的Hash函数其实挺耗时的):
源码如下
1 final Node<K,V>[] resize() { 2 Node<K,V>[] oldTab = table; 3 int oldCap = (oldTab == null) ? 0 : oldTab.length; 4 int oldThr = threshold; 5 int newCap, newThr = 0; 6 if (oldCap > 0) { 7 // 超过最大值就不再扩充了,就只好随你碰撞去吧 8 if (oldCap >= MAXIMUM_CAPACITY) { 9 threshold = Integer.MAX_VALUE; 10 return oldTab; 11 } 12 // 没超过最大值,就扩充为原来的2倍 13 else if ((newCap = oldCap << 1) < MAXIMUM_CAPACITY && 14 oldCap >= DEFAULT_INITIAL_CAPACITY) 15 newThr = oldThr << 1; // double threshold 16 } 17 else if (oldThr > 0) // initial capacity was placed in threshold 18 newCap = oldThr; 19 else { // zero initial threshold signifies using defaults 20 newCap = DEFAULT_INITIAL_CAPACITY; 21 newThr = (int)(DEFAULT_LOAD_FACTOR * DEFAULT_INITIAL_CAPACITY); 22 } 23 // 计算新的resize上限 24 if (newThr == 0) { 25 26 float ft = (float)newCap * loadFactor; 27 newThr = (newCap < MAXIMUM_CAPACITY && ft < (float)MAXIMUM_CAPACITY ? 28 (int)ft : Integer.MAX_VALUE); 29 } 30 threshold = newThr; 31 @SuppressWarnings({"rawtypes","unchecked"}) 32 Node<K,V>[] newTab = (Node<K,V>[])new Node[newCap]; 33 table = newTab; 34 if (oldTab != null) { 35 // 把每个bucket都移动到新的buckets中 36 for (int j = 0; j < oldCap; ++j) { 37 Node<K,V> e; 38 if ((e = oldTab[j]) != null) { 39 oldTab[j] = null; 40 if (e.next == null) 41 newTab[e.hash & (newCap - 1)] = e; 42 else if (e instanceof TreeNode) 43 ((TreeNode<K,V>)e).split(this, newTab, j, oldCap); 44 else { // 链表优化重hash的代码块 45 Node<K,V> loHead = null, loTail = null; 46 Node<K,V> hiHead = null, hiTail = null; 47 Node<K,V> next; 48 do { 49 next = e.next; 50 // 原索引 51 if ((e.hash & oldCap) == 0) { 52 if (loTail == null) 53 loHead = e; 54 else 55 loTail.next = e; 56 loTail = e; 57 } 58 // 原索引+oldCap 59 else { 60 if (hiTail == null) 61 hiHead = e; 62 else 63 hiTail.next = e; 64 hiTail = e; 65 } 66 } while ((e = next) != null); 67 // 原索引放到bucket里 68 if (loTail != null) { 69 loTail.next = null; 70 newTab[j] = loHead; 71 } 72 // 原索引+oldCap放到bucket里 73 if (hiTail != null) { 74 hiTail.next = null; 75 newTab[j + oldCap] = hiHead; 76 } 77 } 78 } 79 } 80 } 81 return newTab; 82 }
Reference
https://zhidao.baidu.com/question/1738414783693877787.htmlhttps://blog.csdn.net/aichuanwendang/article/details/53317351
相关文章推荐
- 分析可变形字符串序列StringBuilder 以及 StringBuffer之默认大小与扩容
- Java细节与规范:ArrayList为何建议赋予默认值及其扩容机制
- 分析可变形字符串序列StringBuilder 以及 StringBuffer之默认大小与扩容
- ArrayList,HashMap,LinkedList 初始化大小和 扩容机制
- StringBuilder 以及 StringBuffer默认大小与扩容
- StringBuilder 以及 StringBuffer默认大小与扩容
- HashMap扩容机制
- ArrayList、Vector、HashMap、HashTable、HashSet的默认初始容量、加载因子、扩容增量
- SDWebImage的运行机制以及缓存大小、有效时间设置
- ArrayList集合底层源代码展示以及结构解析,扩容机制
- ArrayList、Vector、HashMap、HashSet的默认初始容量、加载因子、扩容增量
- 浅谈JAVA中HashMap、ArrayList、StringBuilder等的扩容机制
- hashMap扩容机制
- 深入理解HashMap的扩容机制
- ArrayList、Vector、HashMap、HashSet的默认初始容量、加载因子、扩容增量
- ArrayList、Vector、HashMap、HashSet的默认初始容量、加载因子、扩容增量
- HashMap的扩容机制
- ArrayList和Vector区别以及其扩容机制
- 改变百度ueditor默认的字体以及字体大小
- HashMap的扩容机制---resize()