目录
一、HashMap的数据结构
HashMap未树化前的数据结构:
放入的元素经过两次hash后,取模放入hashmap对应位置。
java1.7:数组+链表
java1.8:数组+ (链表 | 红黑树)
解析1.8版本:
1、红黑树介绍:
(1)hashmap中的树化主要是为了预防Dos攻击,防止超长的链表影响查询性能,树化也是偶然的结果,因为一般情况下,某个数组元素的链表是不会超过8个元素。
(2)hash表的查询时间复杂度为O(1),而红黑树查询的时间复杂度为O(log2 N),TreeNode占用的空间也比普通Node大,如非必要,尽量使用链表。
(3)hash值是随机的,在hash表内部服从泊松分布,负载因子0.75的情况下,长度超过8的链表出现的几率是0.00000006,选择8就是让树化几率足够小。
2、树化的两个条件
树化介绍:
hashmap中每个数组元素对应一个链表,当链表长度在非正常情况下超过8(树化阀值)时,链表树化为红黑树以便查询效率提升。
(1)链表长度超过树化阀值:8。
(2)数组容量大于等于64。
两者缺一不可。
Hashmap的数组扩容机制:
链表长度超过8时,若数组长度小于等于64会先进行扩容,最终再看要不要树化。
Hashmap默认的数组容量是16,当出现链表长度超过8时,容量变为原来的2倍,(取2倍是因为在2倍的基础上做了些优化),扩容后重新计算hash值分布会更发散,当容量扩容到64以上时,对应的链表长超过8的也会发生树化。
如果数组扩容后大于等于64个元素,某个元素结点的链表长度仍大于8则会发生树化。
3、链表化的条件
链表化介绍:
该过程刚好与树化相反。
(1)退化情况1:在扩容如果拆分树时,树元素个数<=6,则会退化为链表。
(2)退化情况2:remove树节点时,若root、root.left、root.right、root.left.left有一个为NULL也会退化为链表。
二、HashMap的索引计算
1、计算对象的hashCode(),再调用Hashmap的hash()方法进行二次哈希,最后 & (Capacity - 1)得到索引。
这里提一下: 取2的模运算为:x%2 = x & (数组元素长度 - 1)
由于&运算效率高于取模运算,所以采用后者。注意: 只要2的mode才满足该等式。
2、二次hash()是为了综合高位数据,让hash后的分布更均匀。
3、计算索引时,如果数组的容量是2的n次幂时,可以使用位与运算代替取模,效率更高;扩容时hash & oldCap = 0的元素留在原来的位置,否则新位置=旧位置+oldCap
4、1~3都是在数组容量为2的n次幂时的有化,例如HashTable的容量就不是2的n次幂,并不是说哪种设计更优,设计者通过综合各方面因素后,最终选取2的n次幂作为容量。
三、HashMap的Put方法实现流程
(1)HashMap是懒惰创建数组的,首次使用才创建数组。
(2)计算索引(桶下标)
(3)如果桶下标还没被人暂用,创建Node占位返回。
(4)如果桶下标已被人占
情况1:已经是TreeNode走红黑树的添加或更新逻辑;
情况2:是普通Node, 走链表的添加或更新逻辑,如果链表长度超过树化阀值,走树化逻辑
(5)返回前检测容量是否超过阀值,一旦超过进行扩容。
四、HashMap的加载因子
加载因子默认为0.75f。
1、在空间占用与时间查询之间取得一个较好的平衡。
2、大于这个值空间节省了,但链表就会比较影响性能。
3、小于这个值,冲突减小了,但扩容就会更频繁,空间占用多。
五、HashMap的并发数据
多线程下:
1.7版本会出先扩容死链
1.7版本和1.8版本都会出现数据错乱
六、HashMap的Key
1、HashMap的key可以为null,但Map的其他实现却不能。
2、作为Key的对象,必须实现hashCode和equals,并且key内容不能修改(不可变)。
(equals的两个元素hashCode一定相等,hashCode相等的元素不一定是同一个元素)
补充:
字符串的hashCode()设计