1. 构造器分析
使用HashMap时,经常用这样的语句(没有加入泛型):Map map = new HashMap();
static final float DEFAULT_LOAD_FACTOR = 0.75f;
final float loadFactor;
public HashMap() {
this.loadFactor = DEFAULT_LOAD_FACTOR; // all other fields defaulted
}
使用空参构造器时,底层只初始化了了loadFactor,初始化的值为 0.75,经过空参构造之后,无法存储key-value数据,接下来我们看下put方法。
2. put方法分析
transient Node<K,V>[] table;
public V put(K key, V value) {
return putVal(hash(key), key, value, false, true);
}
final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
boolean evict) {
Node<K,V>[] tab; Node<K,V> p; int n, i;
if ((tab = table) == null || (n = tab.length) == 0)
n = (tab = resize()).length;
if ((p = tab[i = (n - 1) & hash]) == null)
tab[i] = newNode(hash, key, value, null);
// ...
}
2.1 首次put时,内部的 HashMap 成员变量 table为null,底层创建了一个长度为16的Node类型数组
2.2 put数据时,发生hash冲突(先不考虑红黑树的情况)
2.3 如果hash值相同,但是跟链表第一个节点不同(也就是key不同,equals判断也不相同)
那么遍历链表上的每一个节点,如果没有找到key相同的节点,那么将数据插入到链表的末尾。
2.4 链表转换为红黑树
在putVal方法中,以上逻辑是将链表转换为红黑树,TREEIFY_THRESHOLD,也就是链表的长度即将达到阈值8的时候(实际上是7),会触发转换逻辑。我们继续看下面的逻辑:
hash表中数据元素的个数小于64的时候,也不会将链表转换为红黑树。
所以,某个bucket上的链表转换为红黑树要同时满足两个条件:
1. 该hash bucket的链表长度>=8 (TREEIFY_THRESHOLD)
2. hash表的总长度>=64 (MIN_TREEIFY_CAPACITY)
对以上过程总结:
HashMap map = new HashMap():
在实例化以后,底层只是初始化了一个加载因子 this.loadFactor = DEFAULT_LOAD_FACTOR; ,该值为0.75,
首次map.put(key1,value1):底层初始化一个长度为 16 的 Node数组
多次put之后...
首先,调用key1所在类的hashCode()计算key1哈希值,此哈希值(比较大)经过某种算法(哈希函数、散列函数)计算以后,得到在Node数组中的存放位置(也就是常说的hash bucket)。
※如果此位置上的数据为空,此时的key1-value1(Node)添加成功(需要判断是否满足转换为红黑树的条件)
※如果此位置上的数据不为空,(意味着此位置上存在一个或多个数据(以链表或树形式存在)),比较key1和已经存在的一个或多个数据
的哈希值:
√如果key1的哈希值与已经存在的数据的哈希值都不相同,此时key1-value1(Entry)添加成功。(需要判断是否满足转换为红黑树的条件)
√如果key1的哈希值和已经存在的某一个数据(key2-value2,Entry)的哈希值相同,继续比较:调用key1所在类的equals(key2)方法,比较:(不同的对象可能存在hash值相同的情况)
✨如果equals()返回false:此时key1-value1添加成功
✨如果equals()返回true:使用value1替换value2
在不断的添加过程中,会涉及到扩容问题,当超出临界值(且要存放的位置非空)时,扩容。默认的扩容方式:扩容为原来容量的2倍,并将原有的数据复制过来。
DEFAULT_INITIAL_CAPACITY : HashMap 的默认容量,16
DEFAULT_LOAD_FACTOR:HashMap 的默认加载因子:0.75,兼顾了数组的利用率,也兼顾了链表不能太长
threshold:扩容的临界值,= 容量 * 填充因子:16 * 0.75 => 12
TREEIFY_THRESHOLD: Bucket 中链表长度大于该默认值,转化为红黑树: 8
MIN_TREEIFY_CAPACITY:桶中的 Node 被树化时最小的 hash 表容量 : 64