1.概述
HashMap在多线程编程中是线程不安全的,而Hashtable由于使用了synchronized修饰方法而导致执行效率不高;因此,在concurrent包中,实现了ConcurrentHashMap以使在多线程编程中可以使用一个高性能的线程安全HashMap方案,将锁的粒度缩小,不像HashTable一样锁整个表。1.7和1.8采用不同的方式实现。下面先来说一下两者的不同。然后主要介绍jdk8中的实现
2.concurrentHashMap1.7与1.8的区别
2.1锁的机制不同
1.7中维护了一个Segment数组,及将整个Hash表划分成多个分段。而每个Segment元素对应一个散列表。这样在put操作的时候会先根据hash算法定位到Segment元素。然后锁住对应的散列表。比起HashTable的锁整个表的粒度上变小了。这样就提高了并发度。
1.8中并没有维护一个Segment数组 而是采用和1.8中HashMap类似的数据结构 数组+链表+红黑树。利用CAS和synchronized来锁一链表或一棵树。即锁住散列表的一个格子。这样在锁的粒度上比1.7又更小了一点。提高了并发度。
2.2 数据结构不同
1.7采用Segment数组 + HashEntry数组的方式实现。Segment元素对应一个HashEntry数组,HashEntry是一个链表。Segment数组默认大小是16,HashEntry数组最小值为2,两种数组的长度都要满足2的n次方。且Segment数组的大小看如下的代码
int size =1; while(size < concurrencyLevel) { ++a; size <<=1; } 根据concurrencyLevel大小。取第一个比concurrencyLevel大的2的n次方的数字。
当然concurrencyLevel最大只能用16位的二进制来表示,即65536,换句话说,Segment的大小最多65536个。
1.8采用数组+链表+红黑树的结构。默认数组大小为16
3.类图
继承了AbstractMap 实现了Map的一些基本操作
实现了ConcurrentMap接口、序列化接口
4.属性
静态常量
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;//默认支持16个并发 1.8中并没有使用到这个常量。1.7这个代表着默认的Segement数组的大小默认16.
private static final float LOAD_FACTOR = 0.75f;//装载因子
static final int TREEIFY_THRESHOLD = 8;//树化的阈值
static final int UNTREEIFY_THRESHOLD = 6;//链化的阈值
static final int MIN_TREEIFY_CAPACITY = 64;//最小树化的散列表大小 达到64并且一个结点链表长度到8才会树化
private static final int MIN_TRANSFER_STRIDE = 16;// 最少迁移的Hash槽个数
private static int RESIZE_STAMP_BITS = 16;// todo
private static final int MAX_RESIZERS = (1 << (32 - RESIZE_STAMP_BITS)) - 1;// 可执行扩容的线程数
private static final int RESIZE_STAMP_SHIFT = 32 - RESIZE_STAMP_BITS;// todo
static final int MOVED = -1; // 表示正在执行扩容
static final int TREEBIN = -2; // 表示这是一个红黑树结点
static final int RESERVED = -3; // hash for transient reservations
static final int HASH_BITS = 0x7fffffff; // 用于得到32位的Hash值
static final int NCPU = Runtime.getRuntime().availableProcessors();//处理器数量
属性字段
transient volatile Node<K, V>[] table;//散列表
private transient volatile Node<K, V>[] nextTable;// 过渡数组 在扩容的时候用到
private transient volatile long baseCount;// 保存键值对的个数
// 默认为0
// hash表初始化或扩容时的一个控制位标识量。
// 负数代表正在进行初始化或扩容操作
// -1代表正在初始化
// -N 表示有N-1个线程正在进行扩容操作
// 当初始化或扩容完成后 这个数值表示下一次进行扩容的阈值
private transient volatile int sizeCtl;
private transient volatile int transferIndex;//扩容时候 要分割的nextTable的索引
private transient volatile int cellsBusy;// 自旋锁 扩容或者创建CounterCells 时使用
private transient volatile CounterCell[] counterCells;// 计数器表 记录了散列表中对应的每一个槽包含的结点的数量
private transient KeySetView<K, V> keySet;//键的集合
private transient ValuesView<K, V> values;//值的集合
private transient EntrySetView<K, V> entrySet;//键值结点对的集合
5.node
实现了Map的Entry结点 与HashMap不同的是 node的value加上了 volatile修饰
下一个结点也加上了volatile修饰 保证了可见性
6.静态工具方法
这些方法都是在ConcurrentHashMap中使用的,没有提供给外部调用
6.1 spread
再hash 将计算好的hash值留下低32位。然后高16位与低16位进行异或。
static final int spread(int h) {
// HASH_BITS 是32个1 也就是留下了低32位的hash值。
// 再hash hash值高16与它的低16位进行hash
return (h ^ (h >>> 16)) & HASH_BITS;
}
6.2 tableSizeFor
找到比给定容量c大的最小的2的n次方的值。
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;
}
6.3 comparableClassFor
这个方法就是判断传入的Object对象x是否实现了****Comparable*接口.如果传入的对象时String类型,它自然而然实现了Comparable接口,所以直接跳过判断.但是对于其他的类,比方说我们自己写了一个类对象,然后存在HashMap中,但是就HashMap来说它并不知道我们有没有实现Comparable*接口,甚至都不知道我们Comparable接口中有没有用泛型,泛型具体用的是哪个类.
static Class<?> comparableClassFor(Object x) {
//返回x的类
if (x instanceof Comparable) {
Class<?> c;
Type[] ts, as;
Type t;
ParameterizedType p;
if ((c = x.getClass()) == String.class) // bypass checks
return c;
if ((ts = c.getGenericInterfaces()) != null) {
// 判断是否有直接实现的接口
for (int i = 0; i < ts.length; ++i) {
// 遍历直接实现的接口
if (((t = ts[i]) instanceof ParameterizedType) && // 该接口实现了泛型
((p = (ParameterizedType) t).getRawType() == // 获取接口不带参数部分的类型对象
Comparable.class) &&// 该类型是Comparable
(as = p.getActualTypeArguments()) != null && // 获取泛型参数数组
as.length == 1 && as[0] == c) // 只有一个泛型参数,且该实现类型是该类型本身
return c; // 返回该类型
}
}
}
return null;
}
6.4 compareComparables
两个对象的比较
static int compareComparables(Class<?> kc, Object k, Object x) {
return (x == null || x.getClass() != kc ? 0 ://若x为空 或者x的类不是kc 返回0 否者k和x进行比较
((Comparable) k).compareTo(x));
}
6.5 tabAt
寻找数组tab 在内存中i位置的数据
static final <K, V> Node<K, V> tabAt(Node<K, V>[] tab, int i) {
//寻找数组tab 在内存中i位置的数据
return (Node<K, V>) U.getObjectVolatile(tab, ((long) i << ASHIFT) + ABASE);
}
6.6 casTabAt
cas修改tab数组 内存中i位置的数据 期望从c修改为v
static final <K, V> boolean casTabAt(Node<K, V>[] tab, int i,
Node<K, V> c, Node<K, V> v) {
// cas修改tab数组 内存中i位置的数据 期望从c修改为v
return U.compareAndSwapObject(tab, ((long) i << ASHIFT) + ABASE, c, v);
}
6.7 setTabAt
cas设置tab数组 内存中i位置的数据为v
static final <K, V> void setTabAt(Node<K, V>[] tab, int i, Node<K, V> v) {
//cas设置tab数组 内存中i位置的数据为v
U.putObjectVolatile(tab, ((long) i << ASHIFT) + ABASE, v);
}
7.构造函数
7.1 无参构造函数
public ConcurrentHashMap() {
}
7.2 ConcurrentHashMap(int initialCapacity)
jdk8中 散列表是延迟初始化的
public ConcurrentHashMap(int initialCapacity) {
if (initialCapacity < 0)
throw new IllegalArgumentException();
int cap = ((initialCapacity >= (MAXIMUM_CAPACITY >>> 1)) ?//设置为大于参数initialCapacity的最小的2的n次方的数
MAXIMUM_CAPACITY :
tableSizeFor(initialCapacity + (initialCapacity >>> 1) + 1));
this.sizeCtl = cap;//和HashMap一样 初始化的时候没有对散列表进行初始化 sizeCtl初始化为下一次要扩容的大小
}
7.3 ConcurrentHashMap(Map<? extends K, ? extends V> m)
public ConcurrentHashMap(Map<? extends K, ? extends V> m) {
//这里下次要扩容的大小 sizeCtl 将集合m中的元素添加进map中
this.sizeCtl = DEFAULT_CAPACITY;//默认容量16
putAll(m);
}
7.4 ConcurrentHashMap(int initialCapacity, float loadFactor)
指定初始容量和装载因子
public ConcurrentHashMap(int initialCapacity, float loadFactor) {
this(initialCapacity, loadFactor, 1);
}
7.5 ConcurrentHashMap(int initialCapacity,
float loadFactor, int concurrencyLevel)
指定初始容量和装载因子以及并发度,并发等级只是用来干扰初始化容量的
public ConcurrentHashMap(int initialCapacity,
float loadFactor, int concurrencyLevel) {
if (!(loadFactor > 0.0f) || initialCapacity < 0 || concurrencyLevel <= 0)
throw new IllegalArgumentException();
//若参数初始容量 小于 并发等级 则将初始容量设置为并发等级的大小。
if (initialCapacity < concurrencyLevel) // Use at least as many bins
initialCapacity = concurrencyLevel; // as estimated threads
long size = (long) (1.0 + (long) initialCapacity / loadFactor);//根据初始容量和装载因子 计算出需要的散列表大小。
// 实际的散列表大小 要满足2的n次方 所以需要调用tableSizeFor方法 找到比size大的 数中最小的2的n次方
int cap = (size >= (long) MAXIMUM_CAPACITY) ?
MAXIMUM_CAPACITY : tableSizeFor((int) size);
this.sizeCtl = cap;//设置下次要扩容的大小为cap
}
8.主要方法(1.8)
8.1 put
public V put(K key, V value) {
return putVal(key, value, false);
}
8.1.1 putVal
- 首先判断是否是第一次put,在第一次put之前散列表还没有进行初始化。所以需要对散列表进行初始化 执行initTable()方法
- 根据key计算hash值 然后对其进行再散列后。找到应该存储在散列表中的位置。若这个位置是空的,则执行cas将新的节点添加到散列表中的这个槽位上。
- 如果这个位置的hash是MOVED 即 -1 说明这是一个forwarding node表示正在扩容。那么这个正在执行put操作的线程会去帮助扩容。执行helpTransfer方法
- 当前散列表不在扩容,那么就synchronized锁住当前这个槽位。然后判断这个节点是链表节点 还是红黑树节点。根据不同的节点类型。执行不同的put逻辑。
final V putVal(K key, V value, boolean onlyIfAbsent) {
if (key == null || value == null) throw new NullPointerException();
int hash = spread(key.hashCode());//对key进行再散列
int binCount = 0;
for (Node<K, V>[] tab = table; ; ) {
Node<K, V> f;
int n, i, fh;
if (tab == null || (n = tab.length) == 0)//第一次put 散列表还没初始化
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)))//则执行cas将新的结点添加到对应的槽的位置中
break; // no lock when adding to empty bin
} else if ((fh = f.hash) == MOVED)//正在扩容
tab = helpTransfer(tab, f);//这个线程帮助扩容 todo 了解细节
else {
V oldVal = null;
synchronized (f) {
//锁一个结点
if (tabAt(tab, i) == f) {
if (fh >= 0) {
//这个结点是链表结点
binCount = 1;
for (Node<K, V> e = f; ; ++binCount) {
K ek;
if (e.hash == hash &&
((ek = e.key) == key ||
(ek != null && key.equals(ek)))) {
//已存在key
oldVal = e.val;
if (!onlyIfAbsent)//onlyIfAbsent 为false则直接修改对应的value
e.val = value;
break;
}
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;
}
}
}
}
if (binCount != 0) {
if (binCount >= TREEIFY_THRESHOLD)
treeifyBin(tab, i);//树化
if (oldVal != null)
return oldVal;
break;
}
}
}
addCount(1L, binCount);//结点数量加1 若超过阈值 则扩容
return null;
}
8.1.1.1 initTable
初始化散列表。
private final Node<K, V>[] initTable() {
Node<K, V>[] tab;
int sc;
while ((tab = table) == null || tab.length == 0) {
if ((sc = sizeCtl) < 0)// 其他线程正在创建
Thread.yield(); // lost initialization race; just spin
else if (U.compareAndSwapInt(this, SIZECTL, sc, -1)) {
// -1表示正在初始化 将sizeCtl 设置为-1
try {
if ((tab = table) == null || tab.length == 0) {
//散列表为空
int n = (sc > 0) ? sc : DEFAULT_CAPACITY;//散列表的大小
@SuppressWarnings("unchecked")
Node<K, V>[] nt = (Node<K, V>[]) new Node<?, ?>[n];//新建散列表
table = tab = nt;
sc = n - (n >>> 2);//扩容阈值为容量*0.75 也就是n - n/4
}
} finally {
sizeCtl = sc;
}
break;
}
}
return tab;
}
8.1.1.2addCount
还没有累加单元数组,那么就直接在baseCount上进行cas累加
若累加失败,并且还没有累加单元数组则创建累加单元数组 再次累加
累加完之后判断是否需要扩容
private final void addCount(long x, int check) {
CounterCell[] as;
long b, s;
// counterCells 不为空
if ((as = counterCells) != null ||
// 还没有 向baseCount累加
!U.compareAndSwapLong(this, BASECOUNT, b = baseCount, s = b + x)) {
CounterCell a;
long v;
int m;
boolean uncontended = true;
// 还没有累加单元数组
if (as == null || (m = as.length - 1) < 0 ||
// 还没有累加单元
(a = as[ThreadLocalRandom.getProbe() & m]) == null ||
// cell cas 增加计数失败
!(uncontended =
U.compareAndSwapLong(a, CELLVALUE, v = a.value, v + x))) {
// 创建累加单元数组和cell 累加重试
fullAddCount(x, uncontended);
return;
}
if (check <= 1)
return;
// 获取元素个数
s = sumCount();
}
if (check >= 0) {
Node<K, V>[] tab, nt;
int n, sc;
// 看看是否需要扩容
while (s >= (long) (sc = sizeCtl) && (tab = table) != null &&
(n = tab.length) < MAXIMUM_CAPACITY) {
int rs = resizeStamp(n);
if (sc < 0) {
if ((sc >>> RESIZE_STAMP_SHIFT) != rs || sc == rs + 1 ||
sc == rs + MAX_RESIZERS || (nt = nextTable) == null ||
transferIndex <= 0)
break;
// newTable 已经创建了 帮助扩容
if (U.compareAndSwapInt(this, SIZECTL, sc, sc + 1))
transfer(tab, nt);
}
// 需要扩容 这是 newTable 还未创建
else if (U.compareAndSwapInt(this, SIZECTL, sc,
(rs << RESIZE_STAMP_SHIFT) + 2))
transfer(tab, null);
s = sumCount();
}
}
}
8.2 helpTransfer
final Node<K, V>[] helpTransfer(Node<K, V>[] tab, Node<K, V> f) {
Node<K, V>[] nextTab;
int sc;
//如果table不是空的 并且f结点是一个转移类型的结点 数据校验
if (tab != null && (f instanceof ForwardingNode) &&
//并且f结点的nextTable不为空 同样也是在数据校验。
(nextTab = ((ForwardingNode<K, V>) f).nextTable) != null) {
// 尝试帮助扩容
int rs = resizeStamp(tab.length); //根据tab的大小 得到一个标识符号
while (nextTab == nextTable && table == tab &&
(sc = sizeCtl) < 0) {
//如果 nextTab 没有被并发修改 且 tab 也没有被并发修改 且 sizeCtl < 0 (说明还在扩容)
// 如果 sizeCtl 无符号右移 16 不等于 rs ( sc前 16 位如果不等于标识符,则标识符变化了)
// 或者 sizeCtl == rs + 1 (扩容结束了,不再有线程进行扩容)(默认第一个线程设置 sc ==rs 左移 16 位 + 2,当第一个线程结束扩容了,就会将 sc 减一。这个时候,sc 就等于 rs + 1)
// 或者 sizeCtl == rs + 65535 (如果达到最大帮助线程的数量,即 65535)
// 或者转移下标正在调整 (扩容结束)
// 结束循环,返回 table
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;
}
8.2.1 transfer
private final void transfer(Node<K, V>[] tab, Node<K, V>[] nextTab) {
int n = tab.length, stride;
// 需要迁移多少个hash槽 最少MIN_TRANSFER_STRIDE 16个
// 计算table的长度/8再处于cpu的核心数 如果小于16的化 则用16。
if ((stride = (NCPU > 1) ? (n >>> 3) / NCPU : n) < MIN_TRANSFER_STRIDE)
stride = MIN_TRANSFER_STRIDE; // subdivide range
if (nextTab == null) {
// nextTab为空
try {
@SuppressWarnings("unchecked")
Node<K, V>[] nt = (Node<K, V>[]) new Node<?, ?>[n << 1];//两倍扩容
nextTab = nt;
} catch (Throwable ex) {
// try to cope with OOME
sizeCtl = Integer.MAX_VALUE;
return;
}
nextTable = nextTab;
transferIndex = n;
}
int nextn = nextTab.length;
// 创建一个 fwd 节点,用于占位。当别的线程发现这个槽位中是 fwd 类型的节点,则跳过这个节点。
ForwardingNode<K, V> fwd = new ForwardingNode<K, V>(nextTab);
boolean advance = true; //推进状态
boolean finishing = false; // to ensure sweep before committing nextTab
for (int i = 0, bound = 0; ; ) {
Node<K, V> f;
int fh;
while (advance) {
int nextIndex, nextBound;
if (--i >= bound || finishing)
advance = false;
else if ((nextIndex = transferIndex) <= 0) {
i = -1;
advance = false;
}
// CAS 修改 transferIndex,即 length - 区间值,留下剩余的区间值供后面的线程使用
else if (U.compareAndSwapInt
(this, TRANSFERINDEX, nextIndex,
nextBound = (nextIndex > stride ?
nextIndex - stride : 0))) {
bound = nextBound;// 这个值就是当前线程可以处理的最小当前区间最小下标
i = nextIndex - 1;// 初次对i 赋值,这个就是当前线程可以处理的当前区间的最大下标
advance = false;
}
}
if (i < 0 || i >= n || i + n >= nextn) {
int sc;
if (finishing) {
// 如果完成了扩容
nextTable = null; // 删除成员变量
table = nextTab;// 更新table
sizeCtl = (n << 1) - (n >>> 1); // 更新阈值
return;
}
if (U.compareAndSwapInt(this, SIZECTL, sc = sizeCtl, sc - 1)) {
// 尝试将 sc -1. 表示这个线程结束帮助扩容了,将 sc 的低 16 位减一。
if ((sc - 2) != resizeStamp(n) << RESIZE_STAMP_SHIFT)// 如果 sc - 2 不等于标识符左移 16 位。如果他们相等了,说明没有线程在帮助他们扩容了。也就是说,扩容结束了。
return;
finishing = advance = true;
i = n; // recheck before commit
}
} else if ((f = tabAt(tab, i)) == null)// 获取老 tab i 下标位置的变量,如果是 null,就使用 fwd 占位。
advance = casTabAt(tab, i, null, fwd);// 如果成功写入 fwd 占位,再次推进一个下标
else if ((fh = f.hash) == MOVED)// 如果不是 null 且 hash 值是 MOVED。
advance = true; // 说明别的线程已经处理过了,再次推进一个下标
else {
// 到这里,说明这个位置有实际值了,且不是占位符。对这个节点上锁。为什么上锁,防止 putVal 的时候向链表插入数据
synchronized (f) {
if (tabAt(tab, i) == f) {
Node<K, V> ln, hn;// ln转移之后原位置的头结点 hn原位置+16 的头结点
if (fh >= 0) {
// 链表结点
int runBit = fh & n;
Node<K, V> lastRun = f;
for (Node<K, V> p = f.next; p != null; p = p.next) {
int b = p.hash & n;
if (b != runBit) {
runBit = b;
lastRun = p;
}
}
if (runBit == 0) {
ln = lastRun;
hn = null;
} else {
hn = lastRun;
ln = null;
}
// 生成两个链表 根据hash & n最高位是1和0来判断
for (Node<K, V> p = f; p != lastRun; p = p.next) {
int ph = p.hash;
K pk = p.key;
V pv = p.val;
if ((ph & n) == 0)
ln = new Node<K, V>(ph, pk, pv, ln);
else
hn = new Node<K, V>(ph, pk, pv, hn);
}
//赋值
setTabAt(nextTab, i, ln);
setTabAt(nextTab, i + n, hn);
setTabAt(tab, i, fwd);
advance = true; // 扩容完成
} else if (f instanceof TreeBin) {
//树结点
TreeBin<K, V> t = (TreeBin<K, V>) f;
TreeNode<K, V> lo = null, loTail = null;
TreeNode<K, V> hi = null, hiTail = null;
int lc = 0, hc = 0;
for (Node<K, V> e = t.first; e != null; e = e.next) {
int h = e.hash;
TreeNode<K, V> p = new TreeNode<K, V>
(h, e.key, e.val, null, null);
if ((h & n) == 0) {
if ((p.prev = loTail) == null)
lo = p;
else
loTail.next = p;
loTail = p;
++lc;
} else {
if ((p.prev = hiTail) == null)
hi = p;
else
hiTail.next = p;
hiTail = p;
++hc;
}
}
ln = (lc <= UNTREEIFY_THRESHOLD) ? untreeify(lo) :
(hc != 0) ? new TreeBin<K, V>(lo) : t;
hn = (hc <= UNTREEIFY_THRESHOLD) ? untreeify(hi) :
(lc != 0) ? new TreeBin<K, V>(hi) : t;
setTabAt(nextTab, i, ln);
setTabAt(nextTab, i + n, hn);
setTabAt(tab, i, fwd);
advance = true;
}
}
}
}
}
}
8.3 get
-
首先根据key计算hash值然后再散列。
-
根据再散列后的值定位到散列表中的位置。
-
如果这个位置的节点的hash值等于再散列后的hash值并且这个位置的key等于参数key 则直接返回这个节点的value
-
如果这个节点的hash值小于0 则执行find方法 根据不同的节点类型执行不同的逻辑。
-
否则这里是个链表,则遍历这个链表,返回找到的节点的value值
public V get(Object key) {
Node<K, V>[] tab;
Node<K, V> e, p;
int n, eh;
K ek;
int h = spread(key.hashCode());//再散列
if ((tab = table) != null && (n = tab.length) > 0 &&
(e = tabAt(tab, (n - 1) & h)) != null) {
//根据再散列后的值取后tab.length位 得到存储再散列表中这个位置的结点
if ((eh = e.hash) == h) {
//如果这个结点的hash值等于 key再散列后的hash值
if ((ek = e.key) == key || (ek != null && key.equals(ek)))//并且这个结点的key与参数key相同
return e.val;//返回key对应的value
} else if (eh < 0)// eh小于0 说明 正在扩容或者 是treebin 下面的find根据结点的不同 有不同的实现。
return (p = e.find(h, key)) != null ? p.val : null;
while ((e = e.next) != null) {
//头结点不是要找到的key 遍历链表
if (e.hash == h &&
((ek = e.key) == key || (ek != null && key.equals(ek))))
return e.val;
}
}
return null;
}
8.4 size
public int size() {
long n = sumCount();
return ((n < 0L) ? 0 :
(n > (long) Integer.MAX_VALUE) ? Integer.MAX_VALUE :
(int) n);
}
若累加单元数组不为空,则将其全部累加起来。
final long sumCount() {
CounterCell[] as = counterCells;
CounterCell a;
long sum = baseCount;
if (as != null) {
for (int i = 0; i < as.length; ++i) {
if ((a = as[i]) != null)
sum += a.value;
}
}
return sum;
}