HashMap中有三个构造函数,我们从构造函数开始看HashMap的代码
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);
}
/**
* Constructs an empty {@code HashMap} with the specified initial
* capacity and the default load factor (0.75).
*
* @param initialCapacity the initial capacity.
* @throws IllegalArgumentException if the initial capacity is negative.
*/
public HashMap(int initialCapacity) {
this(initialCapacity, DEFAULT_LOAD_FACTOR);
}
/**
* Constructs an empty {@code HashMap} with the default initial capacity
* (16) and the default load factor (0.75).
*/
public HashMap() {
this.loadFactor = DEFAULT_LOAD_FACTOR; // all other fields defaulted
}
这里我们可以看到它有两个参数
- loadFactor 负载系数
- threshold 阈值 当我们调用无参构造函数时我们可以看到
/**
* Constructs an empty {@code HashMap} with the default initial capacity
* (16) and the default load factor (0.75).
*/
public HashMap() {
this.loadFactor = DEFAULT_LOAD_FACTOR; // all other fields defaulted
}
初始化的时候,修改了负载因子为loadFactor 0.75
当我们构建一个参数的构造函数时,我们需要传入一个initialCapacity(初始化容量)
/**
* Constructs an empty {@code HashMap} with the specified initial
* capacity and the default load factor (0.75).
*
* @param initialCapacity the initial capacity.
* @throws IllegalArgumentException if the initial capacity is negative.
*/
public HashMap(int initialCapacity) {
this(initialCapacity, DEFAULT_LOAD_FACTOR);
}
也是传入了DEFAULT_LOAD_FACTOR(0.75)做负载因子,然后调用两个入参的构造函数
public HashMap(int initialCapacity, float loadFactor) {
// 判断初始化容量是不是小于0
if (initialCapacity < 0)
throw new IllegalArgumentException("Illegal initial capacity: " +
initialCapacity);
//判断初始化容量是不是大于最大容量MAXIMUM_CAPACITY(1 << 30 即 2^30)
//是的话,给最大值给initialCapacity
if (initialCapacity > MAXIMUM_CAPACITY)
initialCapacity = MAXIMUM_CAPACITY;
//判断负载因子是否小于等于0
if (loadFactor <= 0 || Float.isNaN(loadFactor))
throw new IllegalArgumentException("Illegal load factor: " +
loadFactor);
//给负载因子赋值
this.loadFactor = loadFactor;
//计算阈值
this.threshold = tableSizeFor(initialCapacity);
}
可以看到,上面构造函数HashMap(int initialCapacity, float loadFactor)最后调用了tableSizeFor(initialCapacity)
方法,下面就解析一下这个方法是干嘛的
static final int tableSizeFor(int cap) {
int n = -1 >>> Integer.numberOfLeadingZeros(cap - 1);
return (n < 0) ? 1 : (n >= MAXIMUM_CAPACITY) ? MAXIMUM_CAPACITY : n + 1;
}
这里是一个静态方法,主要是把 -1 无符号右移指定位数得到 n,最后根据n,返回tableSize 在java中,负数的二进制表示与其原码不同,它是原码的反码再加 1 那么int -1 的转化为二进制为
int 在java中是32位的
所以int 1 就是:0000 0000 0000 0000 0000 0000 0000 0001
那么int 1 的反码就是 :1111 1111 1111 1111 11111 1111 1111 1110
那么int 1 的补码就是 :1111 1111 1111 1111 11111 1111 1111 1111
也就是32个1 2^32-1
解释完这个,我们来看Integer.numberOfLeadingZeros(cap - 1)方法
public static int numberOfLeadingZeros(int i) {
// HD, Count leading 0's
if (i <= 0)
return i == 0 ? 32 : 0;
int n = 31;
// 1向左移动16位就是2^16
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);
}
这个方法,主要是返回当前值1前面有几个0,由于int 是32位的,所以当int = 0 时,返回32,int<0时,由于 负数由于从-1开始 是32个1,所以没有0
这个方法主要是为了返回2^n的值出来,构造tableSize
上面说完了构造函数,下面开始看put方法
/**
* Associates the specified value with the specified key in this map.
* If the map previously contained a mapping for the key, the old
* value is replaced.
*
* @param key key with which the specified value is to be associated
* @param value value to be associated with the specified key
* @return the previous value associated with {@code key}, or
* {@code null} if there was no mapping for {@code key}.
* (A {@code null} return can also indicate that the map
* previously associated {@code null} with {@code key}.)
*/
public V put(K key, V value) {
return putVal(hash(key), key, value, false, true);
}
我们可以看到,其实put方法,里面调用了putVal方法
/**
* Implements Map.put and related methods.
*
* @param hash hash for key
* @param key the key
* @param value the value to put
* @param onlyIfAbsent if true, don't change existing value
* @param evict if false, the table is in creation mode.
* @return previous value, or null if none
*/
final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
boolean evict) {
Node<K,V>[] tab; Node<K,V> p; int n, i;
// 首先在判断中把table赋值给tab,tab为空 则 调用resize方法,构建table给tab
if ((tab = table) == null || (n = tab.length) == 0)
// 主要触发了resize()方法,肯定会生成新的table
n = (tab = resize()).length;
if ((p = tab[i = (n - 1) & hash]) == null)
// 如果节点为空,放入tab[i]中 (就是当前的捅,还没有存在链表)
tab[i] = newNode(hash, key, value, null);
else {
// 如果已经有了链表
Node<K,V> e; K k;
// 判断key和hash是否相等
if (p.hash == hash &&
((k = p.key) == key || (key != null && key.equals(k))))
// 相等的话,把p赋值给e
e = p;
// 如果p是一个treeNode,则走putTreeVal
else if (p instanceof TreeNode)
e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);
else {
// 当hash和key都不相同的时候,开始遍历p这个链表
// 定义了一个计数器,binCount
for (int binCount = 0; ; ++binCount) {
// 赋值p.next给到e,判断p是不是链表的尾
if ((e = p.next) == null) {
// 是的话 直接创建一个node,放入p的next中
p.next = newNode(hash, key, value, null);
// 如果链表的长度大于8了,就会触发treeifyBin方法,生成红黑树
// 在treeifyBin方法里,会先判断 table的长度,是否已经超过了64,如果没有,则会先触发扩容操作
if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st
treeifyBin(tab, hash);
break;
}
// 判断hash 和 key 是否相等,相等的话,直接跳出循环,不继续遍历了
if (e.hash == hash &&
((k = e.key) == key || (key != null && key.equals(k))))
break;
p = e;
}
}
// 判断e是否为空
if (e != null) { // existing mapping for key
V oldValue = e.value;
// 这里会根据onlyIfAbsent 判断是否要更新node的值
if (!onlyIfAbsent || oldValue == null)
e.value = value;
afterNodeAccess(e);
return oldValue;
}
}
// 记录这个hashMap修改了多少次
++modCount;
// 判断size是否大于阈值,是的话,进行扩容
if (++size > threshold)
resize();
afterNodeInsertion(evict);
return null;
}
这里有五个参数
- hash – hash for key
- key – the key
- value – the value to put
- onlyIfAbsent – if true, don't change existing value
- evict – if false, the table is in creation mode 首先hash这个值是通过hash(Object key) 这个方法生成的
static final int hash(Object key) {
int h;
return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
}
- h = key.hashCode(): 首先,获取 key 的哈希码并将其存储在变量 h 中。
- h >>> 16: 这一步是将 h 的二进制表示向右无符号移动了16位。这个操作是为了取得 h 的高位部分,以便与低位部分进行异或操作。
- h = key.hashCode()) ^ (h >>> 16): 最后,这段代码将 h 与 h 右移16位的结果进行异或运算。 异或运算的结果会对 key 的哈希码进行混合,以获得更好的分布性和减少哈希碰撞的可能性。
putVal方法中有一个很重要的方法,就是resize()方法,这个方法用来处理hashMap数组的初始化以及扩容
/**
* Initializes or doubles table size. If null, allocates in
* accord with initial capacity target held in field threshold.
* Otherwise, because we are using power-of-two expansion, the
* elements from each bin must either stay at same index, or move
* with a power of two offset in the new table.
*
* @return the table
*/
final Node<K,V>[] resize() {
Node<K,V>[] oldTab = table;
// 这里是取现有的table长度,作为旧的容器长度(也就是hashMap数组的长度)
int oldCap = (oldTab == null) ? 0 : oldTab.length;
// 把现有的阈值赋值给oldThr
int oldThr = threshold;
// 定义新的容器长度和阈值
int newCap, newThr = 0;
// 假如容器长度大于0(就是已经初始化过了)
if (oldCap > 0) {
// 旧容器大小已经超过MAXIMUM_CAPACITY(2^30)
if (oldCap >= MAXIMUM_CAPACITY) {
//取2^31 - 1 作为新的最大长度
threshold = Integer.MAX_VALUE;
return oldTab;
}
// 这里先把oldCap向左移动1位,也就是*2 赋值给newCap (这里其实已经把cap容器扩大两倍了其实就是在这里进行了扩容操作)
// newCap = oldCap * 2
// 如果newCap没有超过MAXIMUM_CAPACITY且oldCap>=DEFAULT_INITIAL_CAPACITY(16)
else if ((newCap = oldCap << 1) < MAXIMUM_CAPACITY &&
oldCap >= DEFAULT_INITIAL_CAPACITY)
// 则newThr = oldThr << 1 即 newThr = oldThr * 2 把阈值翻倍
newThr = oldThr << 1; // double threshold
}
// 假如旧容器oldCap<=0,且oldThr > 0
else if (oldThr > 0) // initial capacity was placed in threshold
// 新的cap容器大小为oldThr长度
// 这里分析一下为什么会出现这种情况,在使用new HashMap(a,b)的时候,我们的阈值,其实已经被初始化了
newCap = oldThr;
else { // zero initial threshold signifies using defaults
// 这里直接把DEFAULT_INITIAL_CAPACITY(16),赋值给了 newCap
newCap = DEFAULT_INITIAL_CAPACITY;
// 把计算新的阈值DEFAULT_LOAD_FACTOR * DEFAULT_INITIAL_CAPACITY 给newThr
newThr = (int)(DEFAULT_LOAD_FACTOR * DEFAULT_INITIAL_CAPACITY);
}
// 做完上面那些,我们容器newCap 和 newThr 基本就重新构建好了
// 判断newThr是否为0,为0的话,初始化一下阈值 newCap * loadFactor
// 这里直接调用hashMap()无参构造的时候,就会出现newThr == 0这种情况
if (newThr == 0) {
float ft = (float)newCap * loadFactor;
newThr = (newCap < MAXIMUM_CAPACITY && ft < (float)MAXIMUM_CAPACITY ?
(int)ft : Integer.MAX_VALUE);
}
// 把新的阈值赋值给threshold
threshold = newThr;
@SuppressWarnings({"rawtypes","unchecked"})
// 创建一个in新的长度为newCap 的Node<K,V> 数组为newTab
// 这里可以看到,扩容,是要创建一个新的数组,把旧数组的数据重新存过去,开销比较大
Node<K,V>[] newTab = (Node<K,V>[])new Node[newCap];
// 把新的数组 newTab 赋值给table
table = newTab;
// 如果旧的数组不为空
if (oldTab != null) {
// 遍历旧的数组,大小为oldCap旧的容器大小
for (int j = 0; j < oldCap; ++j) {
Node<K,V> e;
// 把oldTab[j]赋值给e,如果e不为空
// 其实这里的e就是取的数组中的链表的第一个值
if ((e = oldTab[j]) != null) {
// 把旧容器oldTab[j]的值清空
oldTab[j] = null;
// 判断node下面还有没有值(是否最后一个节点)
if (e.next == null)
// 没有的话,把e对象的hash和 (newCap -1) 做与预算,算出在newTab的下标
// 然后放入新数组
newTab[e.hash & (newCap - 1)] = e;
// 如果取出来的是树节点,那就执行split()方法
else if (e instanceof TreeNode)
((TreeNode<K,V>)e).split(this, newTab, j, oldCap);
else { // preserve order
// 如果取出来的值,不是最后一个节点,则遍历整个链表e
// hiTail,hiTail 其实就是链表的最后一个节点
Node<K,V> loHead = null, loTail = null;
Node<K,V> hiHead = null, hiTail = null;
Node<K,V> next;
do {
next = e.next;
// e.hash & oldCap 与操作,把所有0的值取出来
// 计算桶位置 i = (n - 1) & hash
// 这里其实是 看当前的数据的桶位置,是不是在oldCap之外
if ((e.hash & oldCap) == 0) {
// 如果loTail为空
if (loTail == null)
// 把e赋值给loHead
loHead = e;
else
// 如果loTail不为空
// 把e赋值给loTail.next(其实就是把e指给loTail的下一个节点)
loTail.next = e;
// 把e赋值给loTail
// 这里就相当于把链表的指针向后指了一位,
loTail = e;
}
else {
// 如果e.hash & oldCap 不等于0,则收集到hiTail和hiTail里
if (hiTail == null)
hiHead = e;
else
hiTail.next = e;
hiTail = e;
}
// 这里把e的下一个节点 赋值给e
} while ((e = next) != null);
// 执行完链表的遍历以后
if (loTail != null) {
// 把尾的下一个节点设为null
loTail.next = null;
// 把头节点放进oldCap下标范围内的数组中
newTab[j] = loHead;
}
if (hiTail != null) {
// 把尾的下一个节点设为null
hiTail.next = null;
// 把头节点放进j+oldCap下标范围内的数组中
newTab[j + oldCap] = hiHead;
}
}
}
}
}
return newTab;
}