结构
与 HashMap 类似,使用数组 + 链表 + 红黑树存储键值对
成员变量
transient volatile Node<K,V>[] table; // 存放 Node,第一次插入数据时候进行初始化,长度为 2 的倍数
private transient volatile Node<K,V>[] nextTable; // 仅扩容时用到,将节点迁移到新数组
private static final int MIN_TRANSFER_STRIDE = 16 // 扩容线程每次最少要迁移16 个 hash 桶,在扩容中,参与的单个线程允许处理的最少 table 桶首节点个数,虽然适当添加线程,会使得整个扩容过程变快,但需要考虑多线程内存同时分配的问题
private transient volatile int sizeCtl;
-
默认为 0
-
-1 表示正在初始化
-
-(1+number) 表示有 number 个线程同时在扩容,线程必须竞争到这个共享变量,才能进行初始化或者扩容。
-
正数, table 中元素数量阈值,超过这个阈值就会扩容
static final int MOVED = -1; // ForwardingNode 的 hash 值,为 -1- ForwardingNode,在扩容时使用,如果 index 处 Node 节点 hash 值为 -1,表示正在扩容。
static final int TREEBIN = -2; // 树节点的 Hash 值
static final int RESERVED = -3; // 临时保留的 Hash 值
static final int HASH_BITS = 0x7fffffff; // usable bits of normal
哈希桶 Table 初始化
-
根据共享变量 sizeCtl 的值来决定是否由当前线程执行初始化操作(单线程进行初始化),若 sizeCtl < 0 表示正在扩容,该线程需要等待。
-
sizeCtl 为正数时,表示 table 的阈值(= 0.75*n),元素个数超过这个值将会扩容。
private final Node<K,V>[] initTable() {
Node<K,V>[] tab; int sc;
while ((tab = table) == null || tab.length == 0) {
// 如果共享变量 sizeCtl < 0,说明有其它线程正在初始化或者扩容,让出 CPU,让其它线程先执行完
if ((sc = sizeCtl) < 0)
Thread.yield(); // lost initialization race; just spin
// CAS 方式获取到锁,那么由该线程进行初始化
else if (U.compareAndSwapInt(this, SIZECTL, sc, -1)) {
try {
// 将 table 赋值给 tab,判断 tab 是否已经初始化
if ((tab = table) == null || tab.length == 0) {
// sc = sizeCtl > 0 表示已经初始化,否则使用默认容量 16。
int n = (sc > 0) ? sc : DEFAULT_CAPACITY;
@SuppressWarnings("unchecked")
Node<K,V>[] nt = (Node<K,V>[])new Node<?,?>[n];
table = tab = nt;
// sc = 0.75*n,sc 表示阈值
sc = n - (n >>> 2);
}
} finally {
sizeCtl = sc;
}
break;
}
}
return tab;
}
key 对应到哈希桶的过程
-
hash 过程与 HashMap 类似,只是多了与 HASH_BITS 进行位运算
-
HASH_BITS 除了首位是 0,剩下的都是 1,按位与,得正数(首位为0);它的作用就是为了让上面的 hash 值为正数
// index 表示桶的位置
int index = spread(key.hashCode) & (length - 1)
static final int HASH_BITS = 0x7fffffff; // usable bits of normal node hash
static final int spread(int h) {
return (h ^ (h >>> 16)) & HASH_BITS;
}
get 方法
-
key,value 都不能为 null,key 为 null 时抛出 NullPointerException
-
将 key 进行 hash 然后找到数组位置处的索引 index
-
比较 Hash 值,并且若 index == key || equals 返回 true,直接返回 index 处元素
-
若 index 处的 hash 值小于 0,表示正在扩容,进一步调用 Node 子类的 find 方法
-
index 处的 hash 值不小于 0,通过链表 Next 指针遍历查找
static final int TREEBIN = -2; // hash for roots of trees
public V get(Object key) {
Node<K,V>[] tab; Node<K,V> e, p; int n, eh; K ek;
// 计算 hash 值
int h = spread(key.hashCode());
// 数组不为空 && 数组长度大于 0 && 该位置已经有元素
if ((tab = table) != null && (n = tab.length) > 0 && (e = tabAt(tab, (n - 1) & h)) != null) {
// 如果 key 的 hash 值一样,则进一步判断
if ((eh = e.hash) == h) {
// 如果是同一个 key 或者 equal 方法返回 true
if ((ek = e.key) == key || (ek != null && key.equals(ek)))
// 返回对应 value
return e.val;
}
// 如果 hash 值小于 0,表示数组正在扩容
// 此处 Node 节点为 ForwardingNode,调用 ForwardingNode 的 find 方法
else if (eh < 0)
return (p = e.find(h, key)) != null ? p.val : null;
// eh > 0 情况,链表遍历
while ((e = e.next) != null) {
if (e.hash == h &&
((ek = e.key) == key || (ek != null && key.equals(ek))))
return e.val;
}
}
// 没有元素
return null;
}
ForwardingNode 介绍
ForwardingNode 是 Node 子类之一,当节点的 Hash 值为 -1 时,该节点就为 ForwardingNode ,标记此处正在扩容
- 外层循环用于刷新 ForwardingNode 所在的 table 情况,因为扩容过程中 table 的节点情况在变化
- 内层循环则去以 next 指针的形式遍历 table,直到找到 hashCode 相同 && (== || equals )返回 true 的节点
static final class ForwardingNode<K,V> extends Node<