HashMap 源码解析:
扰动函数:
/**
* 1,h>>>16,右移16位,将原来高位移动到低位
* 2. h ^ (h>>>16) 异或原来的高位和低位,增加水“随机性”,减少hash碰撞,数据分配均匀
*/
static final int hash(Object key) {
int h;
return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
}
使用扰动函数就是为了增加随机性,让数据元素更加均衡的散列,减少碰撞。
初始化容量和负载因子
初始化容量
- 初始化容器会寻找2的倍数的最小值,最大容量MAXIMUM_CAPACITY为1<<30 (2^30)
- jdk8使用按位或(|)的方法,先-1,在|=向右移位1、2、4、8、16位,最后+1
/**
* 1. 初始化容器会寻找2的倍数的最小值,最大容量MAXIMUM_CAPACITY为1<<30 (2^30)
* @param initialCapacity 初始化容量
* @param loadFactor 负载因子
*/
public HashMap(int initialCapacity, float loadFactor) {
if (initialCapacity < 0)
throw new IllegalArgumentException("Illegal initial capacity: " +
initialCapacity);
if (initialCapacity > MAXIMUM_CAPACITY)
initialCapacity = MAXIMUM_CAPACITY;
if (loadFactor <= 0 || Float.isNaN(loadFactor))
throw new IllegalArgumentException("Illegal load factor: " +
loadFactor);
this.loadFactor = loadFactor;
this.threshold = tableSizeFor(initialCapacity);
}
/**
* jdk8和jdk11的实现不同:
* jdk11使用Integer中numberOfLeadingZeros方法
*/
static final int tableSizeFor(int cap) {
int n = -1 >>> Integer.numberOfLeadingZeros(cap - 1);
return (n < 0) ? 1 : (n >= MAXIMUM_CAPACITY) ? MAXIMUM_CAPACITY : n + 1;
}
/**
* jdk8使用按位或(|)
*/
static final int tableSizeFor(int cap) {
// 先减1
int n = cap - 1;
// 向右移位1、2、4、8、16位,把二进制的各个位置都填1
n |= n >>> 1;
n |= n >>> 2;
n |= n >>> 4;
n |= n >>> 8;
n |= n >>> 16;
// 当各个位置都是1以后,就是一个标准的2倍数减1,然后结果+1返回
return (n < 0) ? 1 : (n >= MAXIMUM_CAPACITY) ? MAXIMUM_CAPACITY : n + 1;
}
public final class Integer extends Number implements Comparable<Integer> {
public static int numberOfLeadingZeros(int i) {
// HD, Count leading 0's
if (i <= 0)
return i == 0 ? 32 : 0;
int n = 31;
if (i >= 1 << 16) { n -= 16; i >>>= 16; }
if (i >= 1 << 8) { n -= 8; i >>>= 8; }
if (i >= 1 << 4) { n -= 4; i >>>= 4; }
if (i >= 1 << 2) { n -= 2; i >>>= 2; }
return n - (i >>> 1);
}
}
负载因子
默认0.75f, 负载因子决定数据量多少以后进行扩容,如果希望用更多的空间换取时间,可以把负载因子调的更小一些,减少碰撞。
static final float DEFAULT_LOAD_FACTOR = 0.75f;
扩容元素拆分
-
原hash和扩容新增的长度,进行与(&)运算,如果值为0,则下标不变;如果不为0,那么在原来的下标的基础上加新增的长度;
-
这样的好处就是不用计算每一个数组中的元素的哈希值。
-
扩容时计算出新的 newCap、newThr,这是两个单词的缩写,一个是 Capacity ,另一个是阀 Threshold
-
newCap 用于创新的数组桶 new Node[newCap];
-
随着扩容后,原来那些因为哈希碰撞,存放成链表和红黑树的元素,都需要进行拆分存放到新的位置中。
链表树化
- 链表树化的条件有两点;链表长度大于等于 8、桶容量大于 64,否则只是扩容,不会树化。
- 链表树化的过程中是先由链表转换为树节点,此时的树可能不是一颗平衡树。同时在树转换过程中会记录链表的顺序,tl.next = p,这主要方便后续树转链表和拆分更方便。
- 链表转换成树完成后,在进行红黑树的转换。先简单介绍下,红黑树的转换需要染色和旋转,以及比对大小。在比较元素的大小中,有一个比较有意思的方法,tieBreakOrder 加时赛,这主要是因为 HashMap 没有像 TreeMap 那样本身就有 Comparator 的实现。