Snailclimb / JavaGuide

「Java学习+面试指南」一份涵盖大部分 Java 程序员所需要掌握的核心知识。准备 Java 面试,首选 JavaGuide!
https://javaguide.cn
Apache License 2.0
147.1k stars 45.63k forks source link

集合中使用`isEmpty()`判空利用`ConcurrentHashMap`的例子不合理 #2525

Closed HoeYeungHo closed 1 week ago

HoeYeungHo commented 3 weeks ago

ConcurrentHashMap 1.8中,size()方法和isEmpty()方法都会调用sumCount()方法,他们的时间复杂度均与Node数组的大小有关。更合适的例子应用使用ConcurrentLinkedQueue,其isEmpty()方法的时间复杂度近似为O(1),而size()方法的时间复杂度为o(n)

ConcurrentLinkedQueue中,isEmpty()方法通过first()方法进行判断,其中first()方法返回的是队列中第一个值不为null的节点

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)。而出现节点值为null的原因是在迭代器中使用的逻辑删除

private class Itr implements Iterator<E> {

    public void remove() {
        Node<E> l = lastRet;
        if (l == null) throw new IllegalStateException();
        l.item = null;
        lastRet = null;
    }
}

size()方法需要遍历整个链表,时间复杂度为$O(n)$

public int size() {
    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是JDK 1.8中的,因为对于JDK 1.7,在ConcurrentHashMap 1.7中,将元素数量存储在每个Segment中,size() 方法需要统计每个Segment的数量,而isEmpty()只需要找到第一个不为空的Segment即可。

Snailclimb commented 2 weeks ago

ConcurrentHashMap 1.8中,size()方法和isEmpty()方法都会调用sumCount()方法,他们的时间复杂度均与Node数组的大小有关。更合适的例子应用使用ConcurrentLinkedQueue,其isEmpty()方法的时间复杂度近似为O(1),而size()方法的时间复杂度为o(n)

ConcurrentLinkedQueue中,isEmpty()方法通过first()方法进行判断,其中first()方法返回的是队列中第一个值不为null的节点

感谢指出,欢迎提交PR完善一下哈~