- 最大优点
- 可以实现快速查找
- 底层数据结构
- 1.7用的是数组加链表
- 1.8用的是数组+链表|红黑树
- 扩容时机
- 大于 容量X0.75时比如16X0.75=12,也就是大于12时
- 为什么要用红黑树
- HashMap使用红黑树的原因是为了提高检索速度和插入速度。当链表长度超过8时,将链表转换为红黑树,可以减少查找的时间复杂度。
- 为什么一上来不树化
- 首先,hash表的查找,更新的时间复杂度是0(1),而红黑树的查找,更新的时间复杂度是O(log2n),时间更慢
- 其次TreeNode占用的空间也比普通Node大
- 所以非必要情况下,尽量使用链表
- 树化的阈值为什么是8
- 长度超过8的链表 出现的概率十分小,选8是为了让树化几率足够小
- 何时会树化
- 链表长度超过树化阈值,数组容量>=64(同时满足)
- 何时会退化成链表
- 扩容时,如果拆分树,数元素个数<=6,则会退化成链表
- remove树节点时,若root、root.left、root.right、root.left.left有一个为null,也会退化成链表
- 索引如何计算
- 计算对象的hashCode(),再进行调用HashMap的hash()方法进行二次哈希,最后&(capacity(容量位2的n次幂)-1)得到索引
- 为什么进行二次哈希
- 可以使hash分布均匀,防止链表过长的情况
- 容量为什么是2的n次幂
- 好处
- 可以用&运算代替%运算,提高运行效率
- 扩容时 hash&oldCap==0的元素保留在原来位置,否则新位置=旧位置+oldCap
- 缺点
- 哈希的分布性没有质数好,而二次哈希就是为了解决这个问题
- 好处
- 为什么进行二次哈希
- 计算对象的hashCode(),再进行调用HashMap的hash()方法进行二次哈希,最后&(capacity(容量位2的n次幂)-1)得到索引
- put流程
- HashMap是懒惰创建数组的,首次使用才创建数组
- 计算索引(桶下表)
- 如果桶下标还没人占用,创建Node占位返回
- 如果桶下标已经被占用
- 已经是TreeNode走红黑树的添加或更新逻辑
- 是普通Node,走链表的添加和更新逻辑,如果链表的长度超过树化阈值,则走树化流程
- 返回前检查容量是否超过阈值,一旦超过 进行扩容
- 1.7与1.8的不同
- 链表插入节点时,1.7头插法,1.8尾插法
- 1.7是大于等于阈值时没有空位才会扩容,1.8是大于阈值就扩容
- 1.8中在扩容计算node索引时,有优化
- 加载因子为何默认时0.75f
- 在空间占用和查询时间之间获得了较好的权衡
- 大于这个值,空间节省了,但链表就会比较长,影响性能
- 小于这个值,冲突减少,但是扩容变得更加频繁,空间占用会更多
- 多线程下会有的问题
- 扩容死链(1.7)
- 数据错乱(1.7,1.8)
- key的问题
- HashMap的key可以为null,但Map的其他实现则不然
- 作为key的对象,必须实现hashCode和equals,并且key的内容不能修改
- hashCode相同,不一定equals
- 但equals相同,hashCode一定相同
- String对象的hashCode
-
字符串的每个字符都可以表现为一个数字,每个字符串的hashCode足够独特
-
散列公式:
-
为什么乘以31
- 31代入公式有较好的散列特性
- 31*h可以被优化
- 32*h-h
- 2^(5)*h-h
- h<<5-h
- 这样就可以将乘法变成减法了,效率高
-
HashMap的一些性质,基础入门
于 2023-03-12 19:25:35 首次发布