参考文章
继承关系
数据结构
一些重要属性的值都是多少
1. 常量
- 默认初始容量:DEFAULT_INITIAL_CAPACITY = 1 << 4
- 最大容量:MAXIMUM_CAPACITY = 1 << 30
- 默认负载因子:DEFAULT_LOAD_FACTOR = 0.75f
- 最小树化容量:MIN_TREEIFY_CAPACITY = 64
- 树化阈值:TREEIFY_THRESHOLD = 8
-
2. 可调整参数
初始容量:initialCapacity 只作为参数并通过
tableSizeFor(int cap)赋值给 threshold- 负载因子:loadFactor
- 阈值:threshold 当前所能容纳键值对数量的最大值,超过这个值,则需扩容。调用有参构造函数时为大于 initialCapacity 的最小2的幂,真正初始化底层数据结构时会通过 capacity * load factor 计算
什么时候创建的底层数据结构
以下除4之外的构造方法在调用时,只赋值了一些基本参数,并未实际创建底层数据结构,是在第一次插入元素时才实际创建底层数据结构
public HashMap(int initialCapacity, float loadFactor)阈值最开始为大于 initialCapacity 的最小2的幂,等真正初始化底层数据结构时会通过 capacity * load factor 计算;public HashMap(int initialCapacity):使用默认负载因子 DEFAULT_LOAD_FACTOR = 0.75f,阈值同上;public HashMap():使用默认容量 DEFAULT_INITIAL_CAPACITY = 1 << 4public HashMap(Map<? extends K, ? extends V> m)什么情况下转成红黑树
插入元素时当链表长度>=TREEIFY_THRESHOLD(8) 时,且桶数组大小 >= MIN_TREEIFY_CAPACITY(64) 时,才转换成红黑树,否则只扩容桶数组。原因是当桶数组容量比较小时,键值对节点 hash 的碰撞率可能会比较高,进而导致链表长度较长,此时应优先选择扩容。如果优先树化,由于此时容量较小,可能出现频繁扩容进而造成红黑树频繁拆分和反树化,比较耗时;扩容机制
- 元素数量 > 阈值时会扩容;
- 当某链表长度>=TREEIFY_THRESHOLD(8) ,且桶数组大小 < MIN_TREEIFY_CAPACITY(64) 会扩容;
扩容时,如果容量>= MAXIMUM_CAPACITY(230) 则不进行扩容,当 DEFAULT_INITIAL_CAPACITY=< 容量2 <=MAXIMUM_CAPACITY 时,桶数组长度和阈值都扩大为原来的2倍,注意此时阈值可能会溢出,若溢出会使用 capacity load factor 重新计算;扩容后会出现链表或红黑树的拆分
链表和红黑树如何拆分
根据 hash & oldCap 拆分链表和红黑树,拆分红黑树时由于树节点通过 next 和 prev 保留了原有的链表结构,因此拆分和链表过程类似,只不过拆分成了两段,会再判此时拆分出的链表长度 < UNTREEIFY_THRESHOLD,是则将链表转换成普通链表, 否则根据条件重新将 TreeNode 链表树化
如何计算 hash 值
通过(h = key.hashCode()) ^ (h >>> 16)计算,为了减少碰撞原因如下:
- 让 key 的 hash 值高位也可以参与桶数组下标的计算;
- 对于分布性不佳的 hashCode 方法,可以增加 hash 复杂度;
hashCode 方法产生的 hash 是 int 类型,32 位宽。前16位为高位,后16位为低位,所以要右移16位
如何计算 hash 值在桶数组的位置
HashMap 中桶数组的大小 length 总是2的幂,此时,(n - 1) & hash 等价于对 length 取余。但取余的计算效率没有位运算高,所以 (n - 1) & hash 也是一个小的优化
