我有一个列表(List<T> list
),我想使用地图(HashMap<Integer, T> map
)通过其ID对其对象编制索引。我总是在list.size()
构造函数中使用HashMap
作为初始容量,如下面的代码所示。这是在这种情况下使用的最佳初始容量吗?
注意:我永远不会在地图上添加更多项目。
List<T> list = myList;
Map<Integer, T> map = new HashMap<Integer, T>(list.size());
for(T item : list) {
map.put(item.getId(), item);
}
答案 0 :(得分:26)
如果您希望避免重复HashMap
,并且您知道其他元素不会放入HashMap
,那么您必须考虑到加载因子以及初始容量。载荷系数for a HashMap
defaults to 0.75。
每当添加新条目时,确定是否需要重新散列的计算,例如, put
放置一个新的键/值。因此,如果您指定的初始容量为list.size()
,且加载因子为1,那么它将在最后一个put
后重新散列。因此,为防止重新散列,请使用加载因子1和容量list.size() + 1
。
修改强>
查看HashMap
源代码,如果旧大小达到或超过阈值,它将重新进行,因此它不会在最后put
上重新进行。所以看起来list.size()
的容量应该没问题。
HashMap<Integer, T> map = new HashMap<Integer, T>(list.size(), 1.0);
以下是HashMap
源代码的相关部分:
void addEntry(int hash, K key, V value, int bucketIndex) {
Entry<K,V> e = table[bucketIndex];
table[bucketIndex] = new Entry<>(hash, key, value, e);
if (size++ >= threshold)
resize(2 * table.length);
}
答案 1 :(得分:13)
'capacity'关键字的定义不正确,并且没有按照通常预期的方式使用。
默认情况下,HashMap的“加载因子”为0.75,这意味着当HashMap中的条目数达到所提供容量的75%时,它将调整数组的大小并重新散列。
例如,如果我这样做:
Map<Integer, Integer> map = new HashMap<>(100);
当我添加第75个条目时,地图会将Entry表的大小调整为2 * map.size()(或2 * table.length)。所以我们可以做一些事情:
最好的选择是两者中的后者,让我解释一下这里发生了什么:
list.size() / 0.75
这将返回list.size()+ list.size()的25%,例如,如果我的列表的大小为100,它将返回133.然后,如果大小调整了地图的大小,我们会添加1它等于初始容量的75%,所以如果我们有一个大小为100的列表,我们将初始容量设置为134,这意味着从列表中添加所有100个条目不会产生任何大小调整地图。
最终结果:
Map<Integer, Integer> map = new HashMap<>(list.size() / 0.75 + 1);
答案 2 :(得分:12)
你做得很好。通过这种方式,您可以确保哈希映射的至少足够容纳初始值。如果您有关于哈希映射的使用模式的更多信息(例如:它是否经常更新?是否经常添加许多新元素?),您可能希望设置更大的初始容量(例如,list.size() * 2
),但永远不会降低使用分析器确定初始容量是否过早失效。
<强>更新强>
感谢@PaulBellora建议初始容量应设置为(int)Math.ceil(list.size() / loadFactor)
(通常,默认加载因子为0.75),以避免初始调整大小。
答案 3 :(得分:12)
Guava的Maps.newHashMapWithExpectedSize
使用此辅助方法根据预期的某些值计算默认加载因子0.75
的初始容量:
/**
* Returns a capacity that is sufficient to keep the map from being resized as
* long as it grows no larger than expectedSize and the load factor is >= its
* default (0.75).
*/
static int capacity(int expectedSize) {
if (expectedSize < 3) {
checkArgument(expectedSize >= 0);
return expectedSize + 1;
}
if (expectedSize < Ints.MAX_POWER_OF_TWO) {
return expectedSize + expectedSize / 3;
}
return Integer.MAX_VALUE; // any large value
}
参考:source
来自newHashMapWithExpectedSize
文档:
创建一个
HashMap
实例,具有足够高的“初始容量” 应该持有expectedSize
元素而不增长。这种行为 不能得到广泛保证,但它被认为是真实的 OpenJDK 1.6。也不能保证该方法不是 无意中超大返回的地图。
答案 4 :(得分:4)
根据reference documentation of java.util.HashMap:
在设置初始容量时,应考虑地图中预期的条目数及其加载因子,以便最大限度地减少重新扫描操作的次数。如果初始容量大于最大条目数除以负载因子,则不会发生任何重新连接操作。
这意味着,如果您事先知道HashMap应存储多少条目,则可以通过选择适当的初始容量和加载因子来阻止重新散列。但是:
作为一般规则,默认负载系数(.75)在时间和空间成本之间提供了良好的权衡。较高的值会减少空间开销,但会增加查找成本(反映在HashMap类的大多数操作中,包括get和put)。
答案 5 :(得分:0)
经验法则,如果您不知道负载系数/容量内部要素:
initialCapacityToUse = (Expected No. of elements in map / 0.75) + 1
使用此初始容量值,不会在给定预期编号的情况下进行存储的重新哈希处理。地图中的元素。