mirror of
https://github.com/Snailclimb/JavaGuide
synced 2025-06-16 18:10:13 +08:00
Merge pull request #2527 from HoeYeungHo/main
更新 `ConcurrentHashMap` 1.7 的扩容机制和集合判空中的例子
This commit is contained in:
commit
0980514464
@ -368,7 +368,19 @@ private void rehash(HashEntry<K,V> node) {
|
||||
}
|
||||
```
|
||||
|
||||
有些同学可能会对最后的两个 for 循环有疑惑,这里第一个 for 是为了寻找这样一个节点,这个节点后面的所有 next 节点的新位置都是相同的。然后把这个作为一个链表赋值到新位置。第二个 for 循环是为了把剩余的元素通过头插法插入到指定位置链表。这样实现的原因可能是基于概率统计,有深入研究的同学可以发表下意见。
|
||||
有些同学可能会对最后的两个 for 循环有疑惑,这里第一个 for 是为了寻找这样一个节点,这个节点后面的所有 next 节点的新位置都是相同的。然后把这个作为一个链表赋值到新位置。第二个 for 循环是为了把剩余的元素通过头插法插入到指定位置链表。~~这样实现的原因可能是基于概率统计,有深入研究的同学可以发表下意见。~~
|
||||
|
||||
内部第二个 `for` 循环中使用了 `new HashEntry<K,V>(h, p.key, v, n)` 创建了一个新的 `HashEntry`,而不是复用之前的,是因为如果复用之前的,那么会导致正在遍历(如正在执行 `get` 方法)的线程由于指针的修改无法遍历下去。正如注释中所说的:
|
||||
|
||||
> 当它们不再被可能正在并发遍历表的任何读取线程引用时,被替换的节点将被垃圾回收。
|
||||
>
|
||||
> The nodes they replace will be garbage collectable as soon as they are no longer referenced by any reader thread that may be in the midst of concurrently traversing table
|
||||
|
||||
为什么需要再使用一个 `for` 循环找到 `lastRun` ,其实是为了减少对象创建的次数,正如注解中所说的:
|
||||
|
||||
> 从统计上看,在默认的阈值下,当表容量加倍时,只有大约六分之一的节点需要被克隆。
|
||||
>
|
||||
> Statistically, at the default threshold, only about one-sixth of them need cloning when a table doubles.
|
||||
|
||||
### 5. get
|
||||
|
||||
|
@ -15,35 +15,58 @@ tag:
|
||||
|
||||
> **判断所有集合内部的元素是否为空,使用 `isEmpty()` 方法,而不是 `size()==0` 的方式。**
|
||||
|
||||
这是因为 `isEmpty()` 方法的可读性更好,并且时间复杂度为 O(1)。
|
||||
这是因为 `isEmpty()` 方法的可读性更好,并且时间复杂度为 `O(1)`。
|
||||
|
||||
绝大部分我们使用的集合的 `size()` 方法的时间复杂度也是 O(1),不过,也有很多复杂度不是 O(1) 的,比如 `java.util.concurrent` 包下的某些集合(`ConcurrentLinkedQueue`、`ConcurrentHashMap`...)。
|
||||
绝大部分我们使用的集合的 `size()` 方法的时间复杂度也是 `O(1)`,不过,也有很多复杂度不是 `O(1)` 的,比如 `java.util.concurrent` 包下的 `ConcurrentLinkedQueue`。`ConcurrentLinkedQueue` 的 `isEmpty()` 方法通过 `first()` 方法进行判断,其中 `first()` 方法返回的是队列中第一个值不为 `null` 的节点(节点值为`null`的原因是在迭代器中使用的逻辑删除)
|
||||
|
||||
下面是 `ConcurrentHashMap` 的 `size()` 方法和 `isEmpty()` 方法的源码。
|
||||
```java
|
||||
public boolean isEmpty() { return first() == null; }
|
||||
|
||||
Node<E> first() {
|
||||
restartFromHead:
|
||||
for (;;) {
|
||||
for (Node<E> h = head, p = h, q;;) {
|
||||
boolean hasItem = (p.item != null);
|
||||
if (hasItem || (q = p.next) == null) { // 当前节点值不为空 或 到达队尾
|
||||
updateHead(h, p); // 将head设置为p
|
||||
return hasItem ? p : null;
|
||||
}
|
||||
else if (p == q) continue restartFromHead;
|
||||
else p = q; // p = p.next
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
由于在插入与删除元素时,都会执行`updateHead(h, p)`方法,所以该方法的执行的时间复杂度可以近似为`O(1)`。而 `size()` 方法需要遍历整个链表,时间复杂度为`O(n)`
|
||||
|
||||
```java
|
||||
public int size() {
|
||||
long n = sumCount();
|
||||
return ((n < 0L) ? 0 :
|
||||
(n > (long)Integer.MAX_VALUE) ? Integer.MAX_VALUE :
|
||||
(int)n);
|
||||
int count = 0;
|
||||
for (Node<E> p = first(); p != null; p = succ(p))
|
||||
if (p.item != null)
|
||||
if (++count == Integer.MAX_VALUE)
|
||||
break;
|
||||
return count;
|
||||
}
|
||||
```
|
||||
|
||||
此外,在`ConcurrentHashMap` 1.7 中 `size()` 方法和 `isEmpty()` 方法的时间复杂度也不太一样。`ConcurrentHashMap` 1.7 将元素数量存储在每个`Segment` 中,`size()` 方法需要统计每个 `Segment` 的数量,而 `isEmpty()` 只需要找到第一个不为空的 `Segment` 即可。但是在`ConcurrentHashMap` 1.8 中的 `size()` 方法和 `isEmpty()` 都需要调用 `sumCount()` 方法,其时间复杂度与 `Node` 数组的大小有关。下面是 `sumCount()` 方法的源码:
|
||||
|
||||
```java
|
||||
final long sumCount() {
|
||||
CounterCell[] as = counterCells; CounterCell a;
|
||||
long sum = baseCount;
|
||||
if (as != null) {
|
||||
for (int i = 0; i < as.length; ++i) {
|
||||
if (as != null)
|
||||
for (int i = 0; i < as.length; ++i)
|
||||
if ((a = as[i]) != null)
|
||||
sum += a.value;
|
||||
}
|
||||
}
|
||||
return sum;
|
||||
}
|
||||
public boolean isEmpty() {
|
||||
return sumCount() <= 0L; // ignore transient negative values
|
||||
}
|
||||
```
|
||||
|
||||
这是因为在并发的环境下,`ConcurrentHashMap` 将每个 `Node` 中节点的数量存储在 `CounterCell[]` 数组中。在 `ConcurrentHashMap` 1.7 中,将元素数量存储在每个`Segment` 中,`size()` 方法需要统计每个 `Segment` 的数量,而 `isEmpty()` 只需要找到第一个不为空的 `Segment` 即可。
|
||||
|
||||
## 集合转 Map
|
||||
|
||||
《阿里巴巴 Java 开发手册》的描述如下:
|
||||
|
Loading…
x
Reference in New Issue
Block a user