学习目标:
掌握HashMap底层原理
学习内容:
1.hashmap的底层数据结构 1.8之前和1.8的区别?
2.hashmap存储元素的过程
3.hashMap 加载因子为什么0.75
4.hashmap 为什么长度是2的n次方
5.为什么转换成红黑树的节点是8
6.hashmap的构造函数哪些?
1.hashmap的底层数据结构 1.8之前和1.8的区别?
JDK1.8之前:
HashMap底层由数组+链表组成,数组不做解释(>@_@<),链表由两部分组成,一部分存储的自身值,另一部分存储指向下一个元素的内存地址值。
HashMap集合对象创建时,会在构造方法种创建一个长度为16的Entry[ ] table 数组,用来存储键值对数据(1.8之前)
JDK1.8之后,HashMap底层引入了红黑树,当数组长度超过64,链表长度超过8后(同时满足),会将链表转换为红黑树结构
在第一次调用put方法时,创建长度为16的Node[ ] table数组,用来存储键值对(1.8之后)
2.hashmap存储元素的过程
HashMap在存储元素时,首先会获取key的hashCode值结合移位运算得到最终的hash值,算出该元素在数组中的位置,如果当前位置没有元素,就直接添加,如果该位置已有其他元素存在,那么会调用该元素的hashCode方法,比较元素的hashCode值是否相等:
(1)hashCode值不相等 : 就在当前索引位置划分出一个节点来存储当前元素(拉链法)
(2)hashCode值相等(哈希碰撞,哈希冲突):
进一步调用元素的equals方法,比较Key的内容是否一致,如果一致,就会将原来的value值覆盖。如果Key的值不一致,就会在当前索引位置上划分一个节点来存储当前的元素.
附图:
size 表示 HashMap 中键值对的实时数量(即,所存储元素的数量),注意这个不等于数组的长度。
threshold(临界值)= capacity(容量)* loadFactor(负载因子)。这个值是当前已占用数组长度的最大值。size 超过这个值就重新 resize(扩容),扩容后的 HashMap 容量是之前容量的2倍
--------------------------总结一下:--------------------------
hashMap存储元素的步骤:
1)先通过hash值计算出key映射到哪个桶;
2)如果桶上没有发生碰撞冲突,则直接插入;
3)如果出现碰撞冲突了,则需要处理冲突:
a:如果该桶使用红黑树处理冲突,则调用红黑树的方法插入数据;
b:否则采用传统的链式方法插入。如果链的长度达到临界值,数组长度超过64,则把链表转变为红黑树;
4)如果桶中存在重复的键,则为该键替换新值value;
5)如果size大于阈值threshold,则进行扩容;
附加: 面试题:哈希表底层采用何种算法计算hash值?还有哪些算法可以计算出哈希值?
底层采用的是根据key的hashCode方法结合数组长度进行移位运算得到最终的hash值计算出索引的策略,还可以采用平方取中法,取余数,伪随机数算法。因为采用移位运算(无符号右移>>>,按位异或 ^ ,按位与 &)的效率要高于其他运算,所以底层采用这种运算
3.hashMap 加载因子为什么是0.75?
加载因子:hash表中元素填满程度
加载因子过小,元素在数组中过于分散,有些空间一直没有元素,会造成空间的浪费
加载因子过大,元素在数组中会特别的拥挤,查找元素的效率降低,造成链表过长,增加hash碰撞几率,根据泊松分布规律,和官方给出的测试数据,加载因子为0.75时最合适,既不会因为扩容带来的rehash,复制数据等操作而影响性能,也不会因为数组长度太小导致利用率降低。
例如:加载因子是0.4 。那么16* 0.4----->6 如果数组中满6个空间就进行扩容会造成数组利用率太低了
加载因子是0.9。那么16* 0.9------->14那么这样就会导致链表有点多了。导致查找元素效率低。
所以既兼顾数组利用率又考虑链表不要太多,经过大量测试0.75是最佳方案。
4.hashmap 为什么长度是2的n次方?
2的n次方实际就是1后面n个0,2的n次方-1实际就是n个1;
如果数组的长度不是2的n次方,计算出的索引特别容易相同 ,哈希碰撞的几率增大,导致数组空间很大程度上并没有存储数据,链表或红黑树过长,效率降低。当数组的长度为2的n次幂时,可以保证数据的均匀插入,减少ha