了解过HashMap的小伙伴都知道,JDK1.8时,HashMap采用数组+链表+红黑树存储
当单条链表长度超过8时,会将这条链表转换为红黑树,我们来分析一下源码看看
public class HashMap<K,V> ...{
transient Node<K,V>[] table; //HashMap存储数据的容器,也称为Hash桶
//The bin count threshold for using a tree rather than list for a bin.
//使用树而不是列表作为存储箱的存储箱计数阈值。即链表转为红黑树
static final int TREEIFY_THRESHOLD = 8;
//The default initial capacity - MUST be a power of two.
//默认的初始容量-必须是二的幂。
static final int DEFAULT_INITIAL_CAPACITY = 1 << 4; // aka 16 位移运算 = 10000 = 16
//The number of key-value mappings contained in this map.
//此映射中包含的键值映射的数量。
transient int size;
//The load factor used when none specified in constructor.
//在构造函数中未指定时使用的负载系数。
static final float DEFAULT_LOAD_FACTOR = 0.75f;
//The next size value at which to resize (capacity * load factor).
//要调整大小的下一个大小值(容量*负载系数)。即扩容阈值
int threshold;
public V put(K key, V value) {
return putVal(hash(key), key, value,...); //hash值8位16进制
}
final V putVal(int hash, K key, V value,...) {
Node<K,V>[] tab; //hashMap存储数的容器
Node<K,V> p; //数据以Node格式存储
int n, i;
//tab hash表 n=hash表长度(第一次添加元素为16)
if ((tab = table) == null || (n = tab.length) == 0){
n = (tab = resize()).length; //如果第一次添加元素,扩容hash表
}
//p=当前元素的存储下标里的第一个元素 i=当前元素存储下标
if ((p = tab[i = (n - 1) & hash]) == null){
tab[i] = newNode(hash, key, value, null); //计算出当前元素存储位置,判断位置是否为空,空则直接存入
}else{
//执行到这里代表hash表不为空,且当前元素存储的hash表下标也不为空
//判断当前元素存储的下标的第一个元素hash值 是否与当前元素的hash值相等 && 值是否相等
if (p.hash == hash &&
((k = p.key) == key || (key != null && key.equals(k)))){
e = p; //相等则直接覆盖值,添加操作完成
}
...
else {
//执行到这里代表当前元素存储的hash表下标不为空,且第一个元素不等于当前存入元素
//+1
for (int binCount = 0; ; ++binCount) {
//e= p的下一个指向的元素
if ((e = p.next) == null) {
//如果p的下一个元素为空则直接把元素存入到p的下一个元素
p.next = newNode(hash, key, value, null); //+1
if (binCount >= TREEIFY_THRESHOLD - 1){
//binCount+1+1 => 链表长度>TREEIFY_THRESHOLD(8)
//当前下标存储元素超过8个之后,将当前下标的元素存储结构由单向链表转为红黑树
treeifyBin(tab, hash);
}
break;
}
if (e.hash == hash &&
((k = e.key) == key || (key != null && key.equals(k)))){
//p的下一个元素不为空,并且hash值一致,值也一致则跳出循环
break;
}
p = e;
}
}
if (++size > threshold){
//当前存储元素的总数量>负载阈值,则触发Hash表扩容
resize();
}
}
//The smallest table capacity for which bins may be treeified
//可以将存储箱树化的最小表容量。
static final int MIN_TREEIFY_CAPACITY = 64;
/*Replaces all linked nodes in bin at index for given hash unless table is too small, in which case resizes instead.*/
//替换给定哈希的索引处bin中的所有链接节点,除非表太小,在这种情况下会调整大小。
final void treeifyBin(Node<K,V>[] tab, int hash) {
int n, index; Node<K,V> e;
if (tab == null || (n = tab.length) < MIN_TREEIFY_CAPACITY)
//如果当前数组长度小于64则不会转化树,而是扩容数组
resize();
else if ((e = tab[index = (n - 1) & hash]) != null) {
... //链表转为红黑树操作略
}
}
}
截取部分源码:
//使用树而不是列表作为存储箱的存储箱计数阈值。即链表转为红黑树
static final int TREEIFY_THRESHOLD = 8;
看到这里有人可能会奇怪TREEIFY_THRESHOLD不是=8吗,条件既然是binCount>=8-1,那么binCount应该>6时就会转换了呀,为什么都说链表大于8时才会转换红黑树呢?
注意:binCount初始为0,所以当binCount=7时,实际已经存储了8个元素,此时条件binCount>=8-1代表binCount>7时会转换,接着继续分析:
注意看:
在判断之前还进行了元素添加操作,所以此时的数组存储实际元素数量应为:binCount+2
也就是binCount>=8-1代表binCount>8时会转换
接下来继续看treeifyBin(tab,hash)方法
只有当整个HashMap存储的元素总数!<64时才会转换红黑树,否则会改成数组扩容
总结:当链表长度>8且总元素数量大于64时会将链表转化为红黑树