类属性
/**
* The next size value at which to resize (capacity * load factor).
* 要调整大小的下一个大小值(容量*负载系数)既扩容之后集合的长度
*
* @serial
*/
// (The javadoc description is true upon serialization.
// Additionally, if the table array has not been allocated, this
// field holds the initial array capacity, or zero signifying
// DEFAULT_INITIAL_CAPACITY.)
int threshold;
/**
* The table, initialized on first use, and resized as
* necessary. When allocated, length is always a power of two.
* (We also tolerate length zero in some operations to allow
* bootstrapping mechanics that are currently not needed.)
*/
transient Node<K,V>[] table;
/** transient关键字代表这个字段不需要序列化, */
/**
* The maximum capacity, used if a higher value is implicitly specified
* by either of the constructors with arguments.
* MUST be a power of two <= 1<<30.
* 最大容量,如果某个具有参数的构造函数隐式指定了更高的值,则使用该值,必须为2的幂次方
*/
static final int MAXIMUM_CAPACITY = 1 << 30;
/**
* The default initial capacity - MUST be a power of two.
* 默认数组长度16,必须为2的幂次方
*/
static final int DEFAULT_INITIAL_CAPACITY = 1 << 4; // aka 16
存储方法
/**
* 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(如果该字段为true,不要去改变key的值)
* @param evict if false, the table is in creation mode.(参数如果为false,则处于创建模式)
* @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,如果table为空或者长度为0,重新扩容table
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;
}
扩容方法
/**
* 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;
int oldCap = (oldTab == null) ? 0 : oldTab.length;
int oldThr = threshold;
int newCap, newThr = 0;
if (oldCap > 0) {
//如果集合长度已到达最大值,则不扩容,直接返回原数组
if (oldCap >= MAXIMUM_CAPACITY) {
threshold = Integer.MAX_VALUE;
return oldTab;
}
//如果旧数组长度带符号左移之后不大于最大的长度并且旧集合长度大于等于默认数组长度,旧数组长度带符号左移1位赋值给新的
else if ((newCap = oldCap << 1) < MAXIMUM_CAPACITY &&
oldCap >= DEFAULT_INITIAL_CAPACITY)
newThr = oldThr << 1; // double threshold
}
//如果数组长度为0,但是阈值大于0,将阈值赋值给newCap
else if (oldThr > 0) // initial capacity was placed in threshold,这种情况针对的是手工指定数组长度的构造方法
newCap = oldThr;
else {
// zero initial threshold signifies using defaults 零初始阈值表示使用默认值(数组长度为16,扩容阈值为12)
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;
@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) {
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;
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;
}
if (hiTail != null) {
hiTail.next = null;
newTab[j + oldCap] = hiHead;
}
}
}
}
}
return newTab;
}
JDK1.7的扩容方法
void resize(int newCapacity) {
Entry[] oldTable = table;
int oldCapacity = oldTable.length;
if (oldCapacity == MAXIMUM_CAPACITY) { //当当前数据长度已经达到最大容量
threshold = Integer.MAX_VALUE;
return;
}
Entry[] newTable = new Entry[newCapacity]; // 创建新的数组
boolean oldAltHashing = useAltHashing;
useAltHashing |= sun.misc.VM.isBooted() &&
(newCapacity >= Holder.ALTERNATIVE_HASHING_THRESHOLD);
boolean rehash = oldAltHashing ^ useAltHashing; // 是否需要重新计算hash值
transfer(newTable, rehash); // 将table的数据转移到新的table中
table = newTable; // 数组重新赋值
threshold = (int)Math.min(newCapacity * loadFactor, MAXIMUM_CAPACITY + 1); //重新计算阈值
}
//放入新的集合方法
void transfer(Entry[] newTable, boolean rehash) {
int newCapacity = newTable.length;
for (Entry<K,V> e : table) {
while(null != e) {
Entry<K,V> next = e.next;
if (rehash) {
e.hash = null == e.key ? 0 : hash(e.key);
}
//重新计算哈希值
int i = indexFor(e.hash, newCapacity);
//将旧数组第一个元素的next指向新数组[i]的第一个元素
e.next = newTable[i];
//将旧数组第一个元素置于新数组[i]第一个
newTable[i] = e;
//将e的引用过改为next继续循环放入新数组的链表
e = next;
}
}
}
扩容方法中最重要的是链表替换和红黑树部分,链表替换JDK1.7和1.8有点不同,JDK1.7是直接用数组[I]来放入元素的,注意这里放入新的数组中的顺序与原有顺序是相反的,
这是transfer将当前数组中各节点e移动到新数组的 i 位置上的核心代码,为了避免调用put方法,它直接取 e.next = newTable[i];
例如:
oldtable[ i ]为:A->B->null
newtable[ j ]为:X->Y->null
移动oldtable[ i ]到newTable[ j ]中,步骤如下:
e指向A;
e.next指向newTable[ j ]也就是X,所以A->X;
newTable[ j ]指向A,所以此时newTable[ j ]为A->X->Y->null
e指向B;
类似循环操作1,2,3
最后newTable[ j ]结果为:B->A->X->Y->null,变成了逆序。
并且,并发的时候可能会发生死锁:
假如线程1在刚执行完 Entry<K,V> next = e.next; 后,此时e指向A,next指向B,
然后被B线程抢占,然后B完整执行完扩容后,此时newTable[ j ]为:B->A->X->Y->null,
然后A线程恢复执行,e.next = newTable[i];,此时newTable[ j ]为:B<->A X->Y->null,(已经形成环,并且后面链表丢失)
newTable[i] = e;,此时newTable[ j ]为:A<->B
继续循环,发现已经形成环,没有null了, while(null != e) 永远跳不出循环,所以会形成死锁。
引自于 https://www.cnblogs.com/Xieyang-blog/p/8886921.html
这里的newtable有数据的原因是已经为了解释形成闭环的原因,第一次放入新数组肯定是没有值的.
JDK1.8中因为旧数组的位置是由 hash & (数组容量-1)得来的,例如初始容量16-1为15,二进制为0000 1111,所以如果hash碰撞的时候后四位肯定是一样的,每次扩容都是数组容量带符号向左移1位也就是乘2.所以每次只需要判断hash & 数组容量是否为0就能得知是留在原下角标还是移像其他位置.
假如现在容量为初始容量16,再假如5,21,37,53的hash自己(二进制),
所以在oldTab中的存储位置就都是 hash & (16 - 1)【16-1就是二进制1111,就是取最后四位】,
5 :00000101
21:00010101
37:00100101
53:00110101
四个数与(16-1)相与后都是0101
即原始链为:5—>21—>37—>53---->null
此时进入代码中 do-while 循环,对链表节点进行遍历,判断是留下还是去新的链表:
lo就是扩容后仍然在原地的元素链表
hi就是扩容后下标为 原位置+原容量 的元素链表,从而不需要重新计算hash。
因为扩容后计算存储位置就是 hash & (32 - 1)【取后5位】,但是并不需要再计算一次位置,
此处只需要判断左边新增的那一位(右数第5位)是否为1即可判断此节点是留在原地lo还是移动去高位hi:(e.hash & oldCap) == 0 (oldCap是16也就是10000,相与即取新的那一位)
5 :00000101——————》0留在原地 lo链表
21:00010101——————》1移向高位 hi链表
37:00100101——————》0留在原地 lo链表
53:00110101——————》1移向高位 hi链表
退出循环后只需要判断lo,hi是否为空,然后把各自链表头结点直接放到对应位置上即可完成整个链表的移动。
原理是:利用了尾指针Tail,完成了尾部插入,不会造成逆序,所以也不会产生并发死锁的问题。
这种方法对比1.7中算法的优点是:
1、不管怎么样都不需要重新再计算hash;
2、放过去的链表内元素的相对顺序不会改变;
3、不会在并发扩容中发生死锁。
注意,时间复杂度并没有减少
接下来putVal方法就比较简单了如果计算出添加的key在数组的下角标,判断是否有node,没有则创建一个node,并添加到此位置,如果有,循环到链表末端位置并添加,这里忽略了红黑树的添加.还有如果key相同则替换原有的值,结束添加方法.这也就是Map不允许存在多个相同key的原因.
执行
++modCount;
if (++size > threshold)
resize();
如果数量超过阈值则扩容.