HashMap过程
结构:
Node数组+链表
1.当执行put方法时,首先判断table是否为空,如果为空就执行resize方法进行扩容
2.更具hashcode&(n-1),定位到table的位置。
3.先判断table这个位置为不为空
4.如果为空,就把这个数据变成一个node插入在这个位置
5.如果不为空判断这个节点的类型
6.如果为红黑树类型就以红黑树类型的插入
7.如果不是红黑树类型的就以node节点的形式插入到链表的最后面,然后判断数量是否大于8。如果大于8,则调用链表变红黑树的方法。treeifyBin这个方法会判断容量是否小于64,如果小于64就扩容,不是则转换红黑树
8.插入完成之后,判断是否大于容量乘以阈值
9.如果超过容量乘以阈值,就调用resize进行阔容
resize扩容:
前面的一大堆代码,是扩容的容量的值得判断。扩容容量为原来得2倍
然后更具新的容量,new出新的table
然后遍历旧的table
如果旧的table的某一项只有一个节点,就更具hashcode计算新的位置插入到新的table中
如果不止一个节点,就判断首个节点的类型,如果是红黑树类型的。就执行红黑树split方法。
因为红黑树中的节点,只有地位和高位两种,所以分别用TreeNode记录下这些节点
如果这些节点的数量小于6,就变成链表类型的插入到对应的位置
否则从新构建红黑树。地位的是在原位置不动,高位移动到(低位+n)的位置
如果是链表类型的,会用4个指针记录下,高位和地位的位置整体移动。低位在原来的位置,高位移动到(低位+n)的位置
几个重要的参数解释
//默认容量是16
static final int DEFAULT_INITIAL_CAPACITY = 1 << 4; // aka 16
//最大容量是2^30
static final int MAXIMUM_CAPACITY = 1 << 30;
//加载因子是0.75
static final float DEFAULT_LOAD_FACTOR = 0.75f;
//链表达到8,变成红黑树
static final int TREEIFY_THRESHOLD = 8
//红黑树减少到6,变成链表
static final int UNTREEIFY_THRESHOLD = 6;
//总容量达到64时,才会出现链表变成红黑树的情况,否则就算链表长度为8也不变
static final int MIN_TREEIFY_CAPACITY = 64;
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);
else {
Node<K,V> e; K k;
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);
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;
if (++size > threshold)
resize();
afterNodeInsertion(evict);
return null;
}
resize扩容
final Node<K,V>[] resize() {
Node<K,V>[] oldTab = table;
//获取原来table中的元素个数
int oldCap = (oldTab == null) ? 0 : oldTab.length;
//获取扩容的阈值,在构造方法中传了容量,则为大于等于指定容量的 最小2的指数
int oldThr = threshold;
//新的容量和阈值
int newCap, newThr = 0;
//如果原table不为空
if (oldCap > 0) {
//如果原容量已经达到最大容量了,无法进行扩容,直接返回
if (oldCap >= MAXIMUM_CAPACITY) {
//阈值变成最大
threshold = Integer.MAX_VALUE;
return oldTab;
}
//设置新容量为旧容量的两倍
else if ((newCap = oldCap << 1) < MAXIMUM_CAPACITY &&
oldCap >= DEFAULT_INITIAL_CAPACITY)
//阈值也变为原来的两倍
newThr = oldThr << 1; // double threshold
}
/**
* 从构造方法我们可以知道
* 如果没有指定initialCapacity, 则不会给threshold赋值, 该值被初始化为0
* 如果指定了initialCapacity, 该值被初始化成大于initialCapacity的最小的2的次幂
* 这里这种情况指的是原table为空,并且在初始化的时候指定了容量,
* 则用threshold作为table的实际大小
*/
//构造方法中设置容量的值了
else if (oldThr > 0)
newCap = oldThr;
//构造方法中没有指定容量,则使用默认值
else { // zero initial threshold signifies using defaults
//默认是16
newCap = DEFAULT_INITIAL_CAPACITY;
newThr = (int)(DEFAULT_LOAD_FACTOR * DEFAULT_INITIAL_CAPACITY);
}
// 计算指定了initialCapacity情况下的新的 threshold
//newThr没有前面没有设置
if (newThr == 0) {
float ft = (float)newCap * loadFactor;
//阈值是加载因子*newCap大小
newThr = (newCap < MAXIMUM_CAPACITY && ft < (float)MAXIMUM_CAPACITY ?
(int)ft : Integer.MAX_VALUE);
}
threshold = newThr;
/**从以上操作我们知道, 初始化HashMap时,
* 如果构造函数没有指定initialCapacity, 则table大小为16
* 如果构造函数指定了initialCapacity, 则table大小为threshold,
* 即大于指定initialCapacity的最小的2的整数次幂
* 从下面开始, 初始化table或者扩容, 实际上都是通过新建一个table来完成
*/
@SuppressWarnings({"rawtypes","unchecked"})
Node<K,V>[] newTab = (Node<K,V>[])new Node[newCap];
table = newTab;
if (oldTab != null) {
for (int j = 0; j < oldCap; ++j) {
Node<K,V> e;
if ((e = oldTab[j]) != null) {
/** 这里注意, table中存放的只是Node的引用,这里将oldTab[j]=null只是清除旧表的引用,
* 但是真正的node节点还在, 只是现在由e指向它
*/
oldTab[j] = null;
//桶中只有一个节点,直接放入新桶中
if (e.next == null)
newTab[e.hash & (newCap - 1)] = e;
//桶中为红黑树,则对树进行拆分,对树的操作有机会再讲
else if (e instanceof TreeNode)
((TreeNode<K,V>)e).split(this, newTab, j, oldCap);
//桶中为链表,对链表进行拆分
else { // preserve order
//下面为对链表的拆分,我们单独来讲一下。
Node<K,V> loHead = null, loTail = null;
Node<K,V> hiHead = null, hiTail = null;
Node<K,V> next;
//遍历该桶
do {
next = e.next;
//1.8的优化,链表的低位还是在原来位置,高位会移动到 n+oldCap的位置
//e.hash & oldCap== 0表示位置没有移动
//记录着低位的头节点和尾节点位置
if ((e.hash & oldCap) == 0) {
if (loTail == null)
loHead = e;
else
loTail.next = e;
loTail = e;
}
//记录高位的头节点和尾节点的位置
else {
if (hiTail == null)
hiHead = e;
else
hiTail.next = e;
hiTail = e;
}
} while ((e = next) != null);
//低位还是在原来的数组的位置
if (loTail != null) {
loTail.next = null;
newTab[j] = loHead;
}
//高位移动到j+oldCap的位置
if (hiTail != null) {
hiTail.next = null;
newTab[j + oldCap] = hiHead;
}
}
}
}
}
return newTab;
}
tableSizeFor()这个方法的作用是找到大于等于给定容量的最小2的次幂值
static final int tableSizeFor(int cap) {
int n = cap - 1;
//向右移动1位,并且和当前的n取或操作
//这样高位的右边也是1
n |= n >>> 1;
//经过上面的操作,高位和次高位变成1了,已经有两个1了
//所有接下移动两位,高位第3位和第4也会变成1。
n |= n >>> 2;
//经过>>>1和>>>2操作,一共有4个1了
n |= n >>> 4;
n |= n >>> 8;
//最多移动16位,因为int使用32位来存储的
n |= n >>> 16;
//返回n+1
return (n < 0) ? 1 : (n >= MAXIMUM_CAPACITY) ? MAXIMUM_CAPACITY : n + 1;
}
/*
我们这里假设n的初始值为9,那么9的二进制表示是
00000000 00000000 00000000 00001001
那么经历一次右移之后,二进制表示是
00000000 00000000 00000000 00000100
这两个值进行或运算之后,就是
00000000 00000000 00000000 00001101
这里我们如果看最高的1的话,那么可以发现右移1位并且或运算之后,使得最高位的右边一位也是1.
同理,我们可以下一句右移2位并且进行或运算之后,使得最高两位1的后两位也是1.
我们之后运行到最后一个右移,可以发现n的值变为了
00000000 00000000 00000000 00001111
当然该值大于1并且小于最大值那么+1之后,该值就变成了
00000000 00000000 00000000 00010000
惊讶的发现这个值不就是2的次幂嘛!!!
此时我们回到第一句-1,如果给定的n已经是2的次幂,但是不进行-1操作的话,那么得到的值就是大于给定值的最小2的次幂值。
至于为什么右移到16位,可以得到的最大值是32个1
11111111 11111111 11111111 11111111
这个是因为java的int类型用4个字节32位来进行存储的。最往后没有意义。
*/