1.成员变量
2.构造器
2.1 无参构造
2.2有参构造
2.2.1 传入一个参数容量
2.2.2 传入两个参数,容量和负载因子
对该运算进行分析,我们知道int的范围是2的-31次方 - 2的31次方-1。此处的cap一定是大于0的。
那随便假设一个二进制数00001010 01000100 01010000 01010010 来计算一下最后的值。
n = cap -1
00001010 01000100 01010000 01010001
n >>> 1
00000101 00100010 00101000 00101000
n | ( n >>> 1)
00001010 01000100 01010000 01010001
00000101 00100010 00101000 00101000
00001111 01100110 01111000 01111001
n >>> 2
00000011 11011001 10011110 00011110
n | ( n >>> 2)
00001111 01100110 01111000 01111001
00000011 11011001 10011110 00011110
00001111 11111111 11111110 01111111
n >>> 4
00000000 11111111 11111111 11100111
n | ( n >>> 4)
00001111 11111111 11111110 01111111
00000000 11111111 11111111 11100111
00001111 11111111 11111111 11111111
后面再执行答案不会变了
这一同无符号右移和位或运算的目的是把当前二进制数的所有位上都置为1,
最后+1得到比当前二进制数大的2的次方的数。这样做有什么用在后面说。
3.put方法
再说put方法之前先了解HashMap存储数据的数据结构:在jdk1.8之前使用的是数组加链表的方式,在1.8使用的是数组加链表加红黑树的方式。
上图片来自自学网站www.ydlclass.com
hashCode得到的是一个32位的二进制数,与自己右移16位进行异或运算,异或是相同为0,不同为1。那么前16位的结果不会变化,相当于把自己的后16位于前16位进行异或运算,目的可能是让hashCode的值更加散列。
put源代码(分析写在代码上方)
final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
boolean evict) {
Node<K,V>[] tab; Node<K,V> p; int n, i;
//判断数组是否初始化(有没有赋予length),没有初始化扩容(扩容后面说)
if ((tab = table) == null || (n = tab.length) == 0)
n = (tab = resize()).length;
//这里的n会是一个2的次方数字,为什么放在扩容里面
//比如n也就是table.length == 16
//16 - 1 = 15 15 & hash 的到的数字是一个0 ~ 15的数字 ,恰好就是数组的范围
//如果该位置上没有元素,就把这个元素放入这个位置上
if ((p = tab[i = (n - 1) & hash]) == null)
tab[i] = newNode(hash, key, value, null);
else {
Node<K,V> e; K k;
//如果位置上有数据,且位置上的hash和准备放入该位置的hash相等
//key相等和equals相等二选一满足一个,再加上上面的要求,就把原位置的给覆盖了
if (p.hash == hash &&
((k = p.key) == key || (key != null && key.equals(k))))
e = p;
else if (p instanceof TreeNode)
//如果上面的还没满足,就准备往下挂节点了
//这里是判断是否是红黑树
e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);
else {
//不是红黑树,就开始往链表中插入数据了
//对链表进行遍历
for (int binCount = 0; ; ++binCount) {
//如果还没有下一个节点,就把准备插入的数据挂在下面
if ((e = p.next) == null) {
p.next = newNode(hash, key, value, null);
//插入之后,进行判断,如果达到树化的阈值8,就要进行树化
if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st
treeifyBin(tab, hash);
break;
}
//这里的判断和最开始要放入数组的判断一样
if (e.hash == hash &&
((k = e.key) == key || (key != null && key.equals(k))))
break;
p = e;
}
}
if (e != null) { // existing mapping for key
V oldValue = e.value;
if (!onlyIfAbsent || oldValue == null)
e.value = value;
afterNodeAccess(e);
return oldValue;
}
}
//走到这里说明数据存放好了
++modCount;
//然后对size + 1
//判断是否达到阈值,达到了就扩容
if (++size > threshold)
resize();
afterNodeInsertion(evict);
return null;
}
resize() 扩容
final Node<K,V>[] resize() {
Node<K,V>[] oldTab = table;
int oldCap = (oldTab == null) ? 0 : oldTab.length;
int oldThr = threshold;
int newCap, newThr = 0;
//如果旧容量大于0
if (oldCap > 0) {
//大于最大值就把Integer的最大值给他
if (oldCap >= MAXIMUM_CAPACITY) {
threshold = Integer.MAX_VALUE;
return oldTab;
}
//进行扩容,左移一位,保持2的n次方
else if ((newCap = oldCap << 1) < MAXIMUM_CAPACITY &&
oldCap >= DEFAULT_INITIAL_CAPACITY)
newThr = oldThr << 1; // double threshold
}
//如果没有初始化,就把阈值赋值位容量
//阈值也是2的n次方
else if (oldThr > 0) // initial capacity was placed in threshold
newCap = oldThr;
//阈值也没有初始化
else { // zero initial threshold signifies using defaults
//就赋予初始容量16
newCap = DEFAULT_INITIAL_CAPACITY;
//阈值 = 容量 * 负载因子
newThr = (int)(DEFAULT_LOAD_FACTOR * DEFAULT_INITIAL_CAPACITY);
}
//如果阈值没有对其赋值
//阈值 = 容量 * 负载因子
if (newThr == 0) {
float ft = (float)newCap * loadFactor;
newThr = (newCap < MAXIMUM_CAPACITY && ft < (float)MAXIMUM_CAPACITY ?
(int)ft : Integer.MAX_VALUE);
}
threshold = newThr;
............
//这里是扩容之后,数组变大,需要对数组的元素进行重新放置位置
return newTab;
// 扩容之后,容量也保持2的n次方,对应put中的寻找数组位置。
}