考点:
1. HashMap的底层数据结构?
2. HashMap的存取原理?
3. Java7和Java8的区别?
4. 为啥会线程不安全?
5. 有什么线程安全的类代替么?
6. 默认初始化大小是多少?为啥是这么多?为啥大小都是2的幂?
7. HashMap的扩容方式?负载因子是多少?为什是这么多?
8. HashMap的主要参数都有哪些?
9. HashMap是怎么处理hash碰撞的?
10. hash的计算规则?
-
#负载因子
当 负载因子 < 0.75 时,数组中元素较少时即触发扩容,时间效率提升(链表相对较少,取值直接hash),空间利用率降低
当 负载因子 > 0.75 时,数组中元素较多时才触发扩容,时间效率降低(链表相对较多,取值需进行查找),空间利用率提升
当 负载因子 = 0.75 时,空间利用率较高,可避免相当多的 hash 冲突(降低了链表/红黑树的高度),且取值基本可直接hash
-
#init
阿里巴巴插件规范:在初始化HashMap时尽量赋初值且值为2^n
HashMap默认初始长度为 1<< 4 (16)
索引的计算方式为 `index = (n - 1) & hash` _(其中 n = key.length())_
所以为了尽量使key的对应索引值分布均匀,可将值设为 2^n 使得 (n - 1) = 0b1111111...,index 的值等同于hashCode的后几位。即只要输入的 hashCode 本身分布均匀,hash算法结果就是均匀的(减少干扰)
这是为了实现均匀分布。
-
#put
HashMap使用链表+数组的方式储存节点 (1.7 Entry 1.8 Node)
在Java8以前,对于重复数据采用的是头插法(作者认为新插入的值被查找的可能性更大),从Java8开始改用尾插法
HashMap进行扩容时先将数组长度扩容,然后枚举数组所有位置对存在的列表的所有节点重新计算哈希值,当使用头插法时可能导致链表倒序,多线程下还有形成环形链表的风险
1. 原链表
2. 倒序
3. 闭环
因为扩容前后结点顺序不变,故1.8以后使用尾插法时不会出现上述两种情况。但仍无法保证线程安全(方法未加锁)