HashMap1.7与1.8的区别
1.JDK1.8主要解决或优化的问题
resize 扩容优化
引入了红黑树,目的是避免单条链表过长而影响查询效率
解决了多线程死循环问题,但仍是非线程安全的,多线程时可能会造成数据丢失问题。
不同 | JDK1.7 | JDK1.8 |
---|---|---|
存储结构 | 数组+链表 | 数组+链表+红黑树 |
初始化方式 | 单独函数:inflateTable() | 直接集成到了扩容函数 resize() 中 |
hash值计算方式 | 扰动处理 = 9次扰动 = 4次位运 算 + 5次异或运算 | 扰动处理 = 2次扰动 = 1次位运算 + 1次异 或运算 |
存放数据的规则 | 无冲突时,存放数组;冲突 时,存放链表 | 无冲突时,存放数组;冲突 & 链表长度 < 8:存放单链表;冲突 & 链表长度 > 8: 树化并存放红黑树 |
插入数据方式 | 头插法(先讲原位置的数据移 到后1位,再插入数据到该位 置) | 尾插法(直接插入到链表尾部/红黑树) |
扩容后存储位置的计算方式 | 全部按照原来方法进行计算 (即hashCode ->> 扰动函数 - >> (h&length-1)) | 按照扩容后的规律计算(即扩容后的位置 =原位置 or 原位置 + 旧容量) |
2.红黑树的特点
结点是红色或黑色。
根结点是黑色。
所有叶子都是黑色。(叶子是NIL结点)
每个红色结点的两个子结点都是黑色。(从每个叶子到根的所有路径上不能有两个连续的红色结点)
从任一节结点其每个叶子的所有路径都包含相同数目的黑色结点。
3.扩容流程图对比
1.8put操作
4.为什么在JDK1.7的时候是先进行扩容后进行插入,而在JDK1.8的时候则是先插入后进行扩容的呢?
1.7 先扩容后插入可以减少一次数据迁移
1.8 高低位的操作让迁移的成本大大降低,所以直接可以先插入在扩容(个人理解)
5.为什么在JDK1.8中进行对HashMap优化的时候,把链表转化为红黑树的阈值是8,而不是7或者不是20呢
如果选择6和8(如果链表小于等于6树还原转为链表,大于等于8转为树),中间有个差值7可以有效防止链表和树频繁转换。假设一下,如果设计成链表个数超过8则链表转换成树结构,链表个数小于8则树结构转换成链表,如果一个HashMap不停的插入、删除元素,链表个数在8左右徘徊,就会频繁的发生树转链表、链表转树,效率会很低。
还有一点重要的就是由于treenodes的大小大约是常规节点的两倍,因此我们仅在容器包含足够的节点以保证使用时才使用它们,当它们变得太小(由于移除或调整大小)时,它们会被转换回普通的node节点,容器中节点分布在hash桶中的频率遵循泊松分布,桶的长度超过8的概率非常非常小。所以作者应该是根据概率统计而选择了8作为阀值
6.哈希表如何解决Hash冲突?
7.HashMap的特点
8.为什么 HashMap 中 String、Integer 这样的包装类为 key 键?
9.HashMap 中的 key若 Object类型, 则需实现哪些方法?