HashMap源码分析
Posted 技术无产者
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了HashMap源码分析相关的知识,希望对你有一定的参考价值。
JDK1.7和jdk1.8对于HashMap实现的异同总结:
具体可以看这篇文章,但这篇文章几个地方存在歧义,下面做以补充:
1.扰动函数hash:
jdk1.7和jdk1.8 中的hash函数不同,相⽐于 JDK1.8 的 hash ⽅法 ,JDK 1.7 的 hash ⽅法的性能会稍差⼀点点,因为毕竟扰动了 4 次,而jdk1.8中的hash扰动了4次。
2.JDK1.7的无效扩容问题:
来自评论区大佬:
void addEntry(int hash, K key, V value, int bucketIndex)
if ((size >= threshold) && (null != table[bucketIndex]))
resize(2 * table.length);
hash = (null != key) ? hash(key) : 0;
bucketIndex = indexFor(hash, table.length);
createEntry(hash, key, value, bucketIndex);
这是1.7中的hashmap添加元素的源代码,可以看到他是先判断size 》= threshold值 并且 当前数组索引的桶不为空就扩容。这样存在当插入的key值与桶中某一key一致时,是替换value而不是插入新的节点,这样的size值是保持不变,没有大于threshold,本来不需要进行扩容但1.7源代码进行了扩容。而在1.8源代码中,扩容的前提是一定添加了新的node节点(++size > threshold)所以1.8中不会出现无效扩容的问题(1.8中若有value替换的情况是直接返回了,不会到扩容这一步)
简单来说: 当table的size到达阈值,这时候将插入链表中已经存在的Key时,由于 if ((size >= threshold) && (null != table[bucketIndex])) 为true,会直接触发扩容,从而导致无效扩容。
3.为什么 HashMap 中 String、Integer 这样的包装类适合作为 key 键
来自评论区:
1.hashcode不可变;
2.String内部会维护一个hash缓存,在第一次使用时才会计算,从而避免每次计算;
3.对于Integer的hashcode是返回他自身的int值,重复概率高,并且低位特性不随机,所以不推荐使用Integer类型.
结论: 不是所有的包装类都适合作为key;String更适合. 其中包装类以自身为值: byte、integer、short、boolean(固定值) 不以自身为值:long、double、char
4.HashMap 中的 key若 Object类型, 则需实现哪些方法?
Object类型若不重写hashcode方法,则默认调用的是Object的hashcode,很可能会出现严重的hash碰撞问题
5.Jdk1.7和1.8 hashmap的异同:
来自评论区:
-
jdk7 数组+单链表 jdk8 数组+(单链表+红黑树)
-
jdk7 链表头插 jdk8 链表尾插
-
头插: resize后transfer数据时不需要遍历链表到尾部再插入
-
头插: 最近put的可能等下就被get,头插遍历到链表头就匹配到了
-
头插: resize后链表可能倒序; 并发resize可能产生循环链
-
jdk7 先扩容再put jdk8 先put再扩容 (why?有什么区别吗?)
-
jdk7 计算hash运算多 jdk8 计算hash运算少(http://www.jasongj.com/java/concurrenthashmap/#寻址方式-1)
-
jdk7 受rehash影响 jdk8 调整后是(原位置)or(原位置+旧容量)
6.fail-fast 并不是在多线程下才会被触发,具体可以看我的另一篇博客:
以上是关于HashMap源码分析的主要内容,如果未能解决你的问题,请参考以下文章