关于HashMap的相关问题
-
如果HashMap的大小超过了负载因子定义的容量,将会怎么进行扩容?
默认的负载因子大小为0.75,也就是说,当一个hashMap填充到75%的时候,将会创建一个是原来大小的点二倍的bucket数组,用来调整HashMap的大小,将原来的对象放入到新的bucket数组当中(在这个过程中,原来在bucket[i]中的元素,进过hash,可能会改变这个位置,变为bucket[原来数组长+i])。
-
为什么负载因子是0.75,而不是其他数据呢?
首先如果加载因子比较大,那么扩容发生的频率就比较低,但是他浪费的空间比较小,不过发生hash冲突的几率就比较大,比如加载因子是1的时候,如果hashmap长度为128,那么可能hashmap的实际存储元素数量在64至128之间的时间段比较多,而这个时间段发生hash冲突就比较大,造成数组中其中一条链表较长,就会影响性能。
当加载因子值比较小的时候,扩容的频率就会变高,因此会占用更多的空间,但是元素的存储就比较稀疏,发生哈希冲突的可能性就比较小,因此操作性能会比较高,比如设置成0.5,同样128长度的hashmap,当数量达到65的时候就会触发hashmap的扩容,扩容后长度为256,256里面只存储了65个似乎有点浪费了。
-
HashMap中关于链表转化为红黑树
树化源码如下:
final void treeifyBin(Node<K,V>[] tab, int hash) { int n, index; Node<K,V> e; if (tab == null || (n = tab.length) < MIN_TREEIFY_CAPACITY) resize(); else if ((e = tab[index = (n - 1) & hash]) != null) { TreeNode<K,V> hd = null, tl = null; do { TreeNode<K,V> p = replacementTreeNode(e, null); if (tl == null) hd = p; else { p.prev = tl; tl.next = p; } tl = p; } while ((e = e.next) != null); if ((tab[index] = hd) != null) hd.treeify(tab); } }
有if - else可知,当数组长度小于MIN_TREEIFY_CAPACITY(64)的时候,会进行扩容,但不会将链表转换为红黑树,即使链表中的元素超过了8个。
-
JDK1.8 HashMap 扩容是做了哪些优化?
在JDK 1.7 中HashMap是以数组加链表的形式组成的,而在JDK 1.8之后,新增了红黑树的组成接口。当链表长度大于8并且数组容量大于64的时候,链表将装换为红黑树。即使在hashcode完全相同的极端情况下,由于红黑树的性质,查找摸个特定元素也只需要O(log n)的时间复杂度,而链表则需要O(n),从而大大提高了检索的效率。
-
HashMap 是如何导致死循环的?
死循环都是说JDK1.7的HashMap 的死循环,主要是在多线程情况下,多个线程对map进行扩容的同时曹成的,jdk7扩容关键代码如图所示:
造成死循环的原因:
造成死循环的原因主要在于线程1执行单key=9,并取出他的next(key=25)作为下次遍历后,线程1交出了执行权休息去了,而接着线程2吭哧吭哧的执行完扩容,扩容由于采用头插法,此时key=25那个Entry对象的next是key=9。
所以当线程1继续执行,到遍历next(key=25)时他的next变成了key=9,最终造成了两个Entry互相引用,如果后面调用map.get(41)就会刚好查询table[9],然后遍历这个链表,造成死循环!