基础概念
//最大容量
private static final int MAXIMUM_CAPACITY = 1 << 30;
//默认容量
private static final int DEFAULT_CAPACITY = 16;
//最大数组大小
static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;
//默认并发量
private static final int DEFAULT_CONCURRENCY_LEVEL = 16;
//负载因子
private static final float LOAD_FACTOR = 0.75f;
//转变树的阙值
static final int TREEIFY_THRESHOLD = 8;
//转变链表的阙值
static final int UNTREEIFY_THRESHOLD = 6;
//当容量小于64的时候,即便某个链表长度大雨了treeify_threshold,也不会转成树,而是进行扩容,如果当容量大于了64,那么当某个链表长度大于了这个treeify_threshold就会转换成树结构
static final int MIN_TREEIFY_CAPACITY = 64;
//和Hashmap一样,作为一个Node的数组
transient volatile Node<K,V>[] table;
/**
* 下一个新的table,即扩容的时候不能为空
* 简单来说,就是扩容的新数组。为原来数组两倍大小
*/
private transient volatile Node<K,V>[] nextTable;
/**
* 用来控制初始化和扩容操作
* 有几种值:
* 0表示默认值
* -1:表示正在初始化
* -N(N>1):表示有N个线程正在扩容
* >1:
* 如果table没有初始化,那么就需要进行初始化的大小
* 如果已经初始化了,就是table容量
*/
private transient volatile int sizeCtl;
Node数据结构数据结构
ForwardNode数据结构:在扩容的时候使用,它的node的hash值都为-1,nextTable存储着上面nextTable的引用
几个下面要经常用的方法
/*扰动函数:
*将h的低16位和高16位做^操作,从而增加了随机性,碰撞旅能够减少10%
*/
static final int spread(int h) {
return (h ^ (h >>> 16)) & HASH_BITS;
}
//找到容量c的最小2次幂
private static final int tableSizeFor(int c) {
int n = c - 1;
n |= n >>> 1;
n |= n >>> 2;
n |= n >>> 4;
n |= n >>> 8;
n |= n >>> 16;
return (n < 0) ? 1 : (n >= MAXIMUM_CAPACITY) ? MAXIMUM_CAPACITY : n + 1;
}
重要的三个原子操作
//Unsafe的getObjectVolatile方法用来获取指定位置的变量引用
//这里是获取table数组的第i个元素,其计算需要用i*单位元素大小+基础偏移量,从而获得了指定位置元素的其实内存地址
static final <K,V> Node<K,V> tabAt(Node<K,V>[] tab, int i) {
return (Node<K,V>)U.getObjectVolatile(tab, ((long)i << ASHIFT) + ABASE);
}
//这里是利用比较和交换的方法,获取指定位置的元素,和自己的预期值c进行比较,如果相同则赋值v,如果不同那么就CAS失败,返回false。其底层还是Unsafe的compareAndSwapObject方法在起作用
static final <K,V> boolean casTabAt(Node<K,V>[] tab, int i,
Node<K,V> c, Node<K,V> v) {
return U.compareAndSwapObject(tab, ((long)i << ASHIFT) + ABASE, c, v);
}
//将tab的第i个元素设置为v,其底层也是调用了Unsafe的putObjectVolatile方法,其中i*单位元素大小+基础偏移量来获取指定位置的内存
static final <K,V> void setTabAt(Node<K,V>[] tab, int i, Node<K,V> v) {
U.putObjectVolatile(tab, ((long)i << ASHIFT) + ABASE, v);
}
扩容相关:helpTransfer方法
final Node<K,V>[] helpTransfer(Node<K,V>[] tab, Node<K,V> f) {
Node<K,V>[] nextTab; int sc;
if (tab != null && (f instanceof ForwardingNode) &&
(nextTab = ((ForwardingNode<K,V>)f).nextTable) != null) {
int rs = resizeStamp(tab.length);
while (nextTab == nextTable && table == tab &&
(sc = sizeCtl) < 0) {
if ((sc >>> RESIZE_STAMP_SHIFT) != rs || sc == rs + 1 ||
sc == rs + MAX_RESIZERS || transferIndex <= 0)
break;
if (U.compareAndSwapInt(this, SIZECTL, sc, sc + 1)) {
transfer(tab, nextTab);
break;
}
}
return nextTab;
}
return table;
}
扩容相关:addCount方法
添加元素putVal方法
不管CurentHashMap怎么添加元素,都是最后调用的putVal方法的
final V putVal(K key, V value, boolean onlyIfAbsent) {
//key和value不能为空
if (key == null || value == null) throw new NullPointerException();
//调用扰动函数先找到这个key的hash值
int hash = spread(key.hashCode());
int binCount = 0;//用来计数当前这个位置有多少个元素,一边后续进行扩容或者链表转红黑树或者红黑树转链表
//接下来就是死循环自旋了,处理并发操作,一直到操作成功的
for (Node<K,V>[] tab = table;;) {
Node<K,V> f;//用来遍历某个hash位置下的所有元素
int n, i, fh;//fh保存f对应的hash值
if (tab == null || (n = tab.length) == 0)
//这里说明将CurrentHashMap的Node数组的创建延迟到了put方法中,调用上面的initTable进行初始化咯
tab = initTable();
else
//否则这时候就是已经初始化过咯
if ((f = tabAt(tab, i = (n - 1) & hash)) == null) {
//如过在hash位置为空,说明这里的元素应该为空的,
//也就是数组那个位置还没有赋值,这时候赋值就行了
if (casTabAt(tab, i, null, new Node<K,V>(hash, key, value, null))) break;
// 这里利用CAS,如果和预期值相同说明可以进行交换设置预期值,并且可以跳出循环了
//否则就只能继续死循环,继续进行上面的过程,一直到放入元素成功的
}
else
if ((fh = f.hash) == MOVED)
//如果hash值为-1,说明当前这个节点正在参与扩容,应该调用helpTransfer去帮助他
tab = helpTransfer(tab, f);
else {
//没有扩容也没有初始化,那么进入正常的模式
V oldVal = null;
//利用synchronized锁住f对象,即这个正在加入的数组位置的第一个元素
synchronized (f) {
if (tabAt(tab, i) == f) {
//如果tab的第i偏移量元素
if (fh >= 0) {
//如果fh也就是f的hash值>0,说明是链表结构
binCount = 1;
for (Node<K,V> e = f;; ++binCount) {
K ek;
//如果是同一key那么就替代掉,然后退出循环
if (e.hash == hash &&
((ek = e.key) == key ||
(ek != null && key.equals(ek)))) {
oldVal = e.val;
if (!onlyIfAbsent)
e.val = value;
break;
}
//不是同一key就继续往后遍历,直到尾节点为止,可见是尾插法
Node<K,V> pred = e;
if ((e = e.next) == null) {
pred.next = new Node<K,V>(hash, key,
value, null);
break;
}
}
}
//如果是红黑树结构,那么就采用红黑树的替入方式
else if (f instanceof TreeBin) {
Node<K,V> p;
binCount = 2;
if ((p = ((TreeBin<K,V>)f).putTreeVal(hash, key,
value)) != null) {
oldVal = p.val;
if (!onlyIfAbsent)
p.val = value;
}
}
}
}
//看binCount即这个位置下元素的个数
if (binCount != 0) {
//大于treeify_threshold就需要扩容或者转红黑树
//这个treeifyBin方法要看,当当下位置的元素达到了treeify_threshold的值了,还要判断是否总容量达到了MIN_TREEIFY_CAPACITY=64(默认64),如果达到了就转红黑树,没达到就扩容即可。
if (binCount >= TREEIFY_THRESHOLD)
treeifyBin(tab, i);
if (oldVal != null)
return oldVal;
break;
}
}
}
//添加binCount,里面还可以进行扩容的。
addCount(1L, binCount);
return null;
}
Get操作😊
public V get(Object key) {
Node<K,V>[] tab;
Node<K,V> e, p;
int n, eh;
K ek;
//先取key的hash值
int h = spread(key.hashCode());
//
if ((tab = table) != null && (n = tab.length) > 0 &&(e = tabAt(tab, (n - 1) & h)) != null) {
//如果table不空,而且table的长度大于0,而且h经过hash之后的数组位置不为空
if ((eh = e.hash) == h) {
//如果当前位置的hash值和这个key的值确实相等
//说明没有扩容操作,那么就可以直接读取
if ((ek = e.key) == key || (ek != null && key.equals(ek)))
//如果数组的当前位置的第一个元素是的,那么就直接返回吧。
return e.val;
}
else if (eh < 0)
//这时候如果hash值小于0了,说明这个点是ForwardNode,正在处于扩容的阶段,它的hash值是-1,通过nextTable指向了新扩容的数组
//这时候就需要通过ForwardNode节点的find方法,取新扩容的数组去找
return (p = e.find(h, key)) != null ? p.val : null;
//这时候经过上面的过程了,说明这个元素不在第一个节点,那么就需要进行遍历
while ((e = e.next) != null) {
if (e.hash == h &&
((ek = e.key) == key || (ek != null && key.equals(ek))))
return e.val;
}
}
return null;
}
去看看find方法
Node<K,V> find(int h, Object k) {
// loop to avoid arbitrarily deep recursion on forwarding nodes
outer: for (Node<K,V>[] tab = nextTable;;) {
Node<K,V> e; int n;
//如果table为空或者k为空或者在k的位置为空直接返回null
if (k == null || tab == null || (n = tab.length) == 0 || (e = tabAt(tab, (n - 1) & h)) == null)
return null;
for (;;) {
int eh; K ek;
//和之前的调用一样,如果确实第一个元素的hash值是这个,且对应其他条件相同的化,就直接返回这个元素
if ((eh = e.hash) == h && ((ek = e.key) == k || (ek != null && k.equals(ek))))
return e;
//如果hash还是小于0,说明这个位置处于扩容状态
if (eh < 0) {
if (e instanceof ForwardingNode) {
//如果这个点确实是ForwardingNode实例,那么就需要指向nextTable节点去找
tab = ((ForwardingNode<K,V>)e).nextTable;
continue outer;
}
//否则的化就是Node实例咯,这样说明这个节点是已经扩容之后的了。
else
return e.find(h, k);
}
if ((e = e.next) == null)
return null;
}
}
}
扩容机制
前面到处都有一些莫名其妙的方法,比如get中的find方法,以及扩容怎么高的,都怪怪的,现在就来了解一些。