Bug:参数' initialCapacity' ConcurrentHashMap的构造方法?

时间:2018-04-29 06:26:59

标签: java java-8 hashmap concurrenthashmap

java.util.concurrent.ConcurrentHashMap的构造方法之一:

public ConcurrentHashMap(int initialCapacity) {
        if (initialCapacity < 0)
            throw new IllegalArgumentException();
        int cap = ((initialCapacity >= (MAXIMUM_CAPACITY >>> 1)) ?
                   MAXIMUM_CAPACITY :
                   tableSizeFor(initialCapacity + (initialCapacity >>> 1) + 1));
        this.sizeCtl = cap;
    }

方法的参数是什么&#39; tableSizeFor(...)&#39;意思?

initialCapacity + (initialCapacity >>> 1) + 1

我认为参数应该是:

(int)(1.0 + (long)initialCapacity / LOAD_FACTOR)

或只是:

initialCapacity

我认为参数表达式是错误的,至少是一个bug.Did我误解了什么?

我向OpenJDK发送了一个错误报告,似乎他们正式确认它很可能是一个错误:https://bugs.openjdk.java.net/browse/JDK-8202422

更新:Doug Lea评论了这个bug,似乎他同意这是一个bug。

2 个答案:

答案 0 :(得分:5)

我强烈认为这是一个优化技巧。

你正确的想法。您引用的构造函数使用默认加载因子0.75,因此为了容纳initialCapacity元素,哈希表大小至少需要

initialCapacity / 0.75

(与乘以1.3333333333大致相同)。然而,浮点除法很昂贵(稍微有点,不错)。而且我们还需要舍入到整数。我想整数除法已经有用了

(initialCapacity * 4 + 2) / 3

+ 2用于确保结果向上舍入; * 4应该很便宜,因为它可以实现为左移)。实施者做得更好:轮班比分部便宜很多。

initialCapacity + (initialCapacity >>> 1) + 1

这实际上乘以1.5,所以给我们的结果通常会超过需要的结果,但速度很快。 + 1是为了弥补“乘法”向下舍入的事实。

详细信息:>>>是无符号右移,将零填充到最左边的位置。已经知道initialCapacity是非负的,这会得到与除以2相同的结果,忽略余数。

编辑:我可以将tableSizeFor向上加上2的幂,所以即使第一次计算得到的结果略大于所需的结果,大多数情况下2的相同幂也是最终结果。例如,如果你要求10个元素的容量(为了保持计算简单),表格大小14就足够了,公式产生16个。但是14会被四舍五入到2的幂,所以我们得到16个,所以最后没有区别。如果你要求留出12个元素的空间,那么16号就足够了,但是公式得到19,然后四舍五入到32.这是更不寻常的情况。

进一步编辑:感谢您作为JDK错误提交的评论中的信息以及提供链接:https://bugs.openjdk.java.net/browse/JDK-8202422。 Marin Buchholz的第一条评论同意你的意见:

  

是的,这里有一个错误。 one-arg构造函数有效地使用了   负载系数为2/3,而非记录的默认值3/4 ......

我自己不会认为这是一个错误,除非你认为它是一个你偶尔会获得比你要求的更大容量的错误。另一方面,当然(在你的示例性简洁错误报告中)你是对的,存在不一致性:你会期望new ConcurrentHashMap(22)new ConcurrentHashMap(22, 0.75f, 1)给出相同的结果,因为后者只是给出了记录默认负载系数/表密度;但你得到的桌子尺寸是前者的64个,后者的是32个。

答案 1 :(得分:0)

当你说(int)(1.0 + (long)initialCapacity / LOAD_FACTOR)时,HashMap而不是ConcurrentHashMap有意义(与HashMap的意义不同)。

对于HashMap,容量是调整大小之前buckets的数量,ConcurrentHashMap是执行调整大小之前条目的数量。

测试这一点非常简单:

private static <K, V> void debugResize(Map<K, V> map, K key, V value) throws Throwable {

    Field table = map.getClass().getDeclaredField("table");
    AccessibleObject.setAccessible(new Field[] { table }, true);
    Object[] nodes = ((Object[]) table.get(map));

    // first put
    if (nodes == null) {
        map.put(key, value);
        return;
    }

    map.put(key, value);

    Field field = map.getClass().getDeclaredField("table");
    AccessibleObject.setAccessible(new Field[] { field }, true);
    int x = ((Object[]) field.get(map)).length;
    if (nodes.length != x) {
        ++currentResizeCalls;
    }

}


public static void main(String[] args) throws Throwable {

    // replace with new ConcurrentHashMap<>(1024) to see a different result
    Map<Integer, Integer> map = new HashMap<>(1024);

    for (int i = 0; i < 1024; ++i) {
        debugResize(map, i, i);
    }

    System.out.println(currentResizeCalls);

}

对于HashMap,调整大小发生一次,因为ConcurrentHashMap它没有。

1.5增长并不是新事物,ArrayList具有相同的策略。

转变,嗯,它们比平时的数学便宜(呃);但也因为>>>未签名。