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。
答案 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
具有相同的策略。
转变,嗯,它们比平时的数学便宜(呃);但也因为>>>
未签名。