索引列表时的最佳 HashMap 初始容量
Posted
技术标签:
【中文标题】索引列表时的最佳 HashMap 初始容量【英文标题】:Best HashMap initial capacity while indexing a List 【发布时间】:2013-03-28 11:09:46 【问题描述】:我有一个列表 (List<T> list
),我想使用映射 (HashMap<Integer, T> map
) 通过对象的 ID 对其对象进行索引。我总是在HashMap
构造函数中使用list.size()
作为初始容量,如下面的代码所示。这是在这种情况下使用的最佳初始容量吗?
注意:我永远不会向地图添加更多项目。
List<T> list = myList;
Map<Integer, T> map = new HashMap<Integer, T>(list.size());
for(T item : list)
map.put(item.getId(), item);
【问题讨论】:
我建议:1) 将您的变量声明为Map
而不是 HashMap
,2) 如果您注意到 使用分析器,请让 JVM 出现此类问题> 这会给你的表现带来冲击,然后开始评估它。
@LuiggiMendoza 通常是的,同意,但这是一个常见的用例,我们也可以摆脱重新调整大小
【参考方案1】:
如果您希望避免重新散列HashMap
,并且您知道不会将其他元素放入HashMap
,那么您必须考虑负载因子以及初始容量。负载系数for a HashMap
defaults to 0.75。
每当添加新条目时,都会进行计算以确定是否需要重新散列,例如put
放置一个新的键/值。因此,如果您指定初始容量为list.size()
,负载因子为1,那么它将在最后一个put
之后重新散列。因此,为了防止重新散列,请使用 1 的负载因子和list.size() + 1
的容量。
编辑
查看HashMap
源代码,如果old大小达到或超过阈值,它将重新hash,因此它不会在最后一个put
上重新hash。所以看起来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.0 是一个非常糟糕的主意?! @rgettman:如果你知道哈希映射在内部是如何工作的,你会注意到你不仅搞砸了插入,而且还搞砸了读取。所有操作都将变为 O(N) 而不是 O(1),因为到处都有碰撞,你必须从一个桶跳到另一个桶 你知道你设置的正确答案是错误答案吗? 请注意,初始容量将在内部四舍五入为二的下一个幂。因此,200 的容量将四舍五入为 256。如果 HashMap 不四舍五入为capacity
的二次幂值,则将永远不会使用某些存储桶。放置地图数据的桶索引由bucketIndex = hashCode(key) & (capacity-1)
确定。这对于 Java 7 和 8 都是如此,afaik。
使用负载因子 1 是一个坏主意,考虑到如果空闲桶越少,哈希冲突就会增加。 Java 设计人员默认使用 0.75 的负载因子是空间和时间之间的权衡。如果您不确定负载因子内部,请不要触摸此默认值。现在,如果您使用 0.75 而不是 1 的负载因子,则可以使用 initialCapacity = (预期的元素数/0.75)+1 计算不应该导致它重新散列的地图容量。期间。【参考方案2】:
“容量”关键字的定义不正确,并且未按通常预期的方式使用。
默认情况下,HashMap 的“负载因子”为 0.75,这意味着当 HashMap 中的条目数达到所提供容量的 75% 时,它将调整数组大小并重新散列。
例如,如果我这样做:
Map<Integer, Integer> map = new HashMap<>(100);
当我添加第 75 个条目时,映射会将条目表的大小调整为 2 * map.size()(或 2 * table.length)。所以我们可以做一些事情:
-
更改负载系数 - 这可能会影响地图的性能
将初始容量设置为 list.size() / 0.75 + 1
最好的选择是两者中的后者,让我解释一下这里发生了什么:
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);
【讨论】:
查看JDK源代码,实际表大小四舍五入到最接近的2的幂。另外,re。您的声明“默认情况下,HashMap 的‘负载因子’为 0.75,这意味着当 HashMap 中的条目数达到所提供容量的 75% 时,它将调整数组大小并重新散列。” - 有点迂腐,仅当条目超过(未达到)容量的 75% 时才会发生调整大小。因此,例如,指定初始容量为 64,负载因子为 0.5,您可以在不调整大小的情况下放入 32 个条目。 也是 100 / 0.75 = 133,并不是说它改变了什么【参考方案3】: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
元素没有增长。这种行为 不能广泛保证,但观察到对于 开放JDK 1.6。也不能保证该方法不是 无意中过大返回的地图。
【讨论】:
+1。对于那些不想了解地图内部结构而只想按预期工作的人来说,这是最简单、最简单的解决方案。【参考方案4】:你正在做的很好。通过这种方式,您可以确定哈希映射 至少 有足够的容量用于初始值。如果您有更多关于哈希映射使用模式的信息(例如:它是否经常更新?是否经常添加许多新元素?),您可能希望设置更大的初始容量(例如,list.size() * 2
),但永远不要降低。使用分析器确定初始容量是否过快不足。
更新
感谢@PaulBellora 建议将初始容量设置为(int)Math.ceil(list.size() / loadFactor)
(通常,默认加载因子为 0.75)以避免初始调整大小。
【讨论】:
“哈希映射至少有足够的容量容纳初始值”——我认为默认加载因子为 0.75 时这是不正确的。 @PaulBellora 初始容量与initialCapacity
参数中指定的容量相同。负载因子是衡量哈希表在其容量(初始或非初始)自动增加之前允许达到的程度
对,所以在负载因子为0.75
和初始容量为n
的情况下,输入n
值会导致它调整大小。
@PaulBellora 所以你建议初始容量应该是 size()/.75 以避免初始调整大小?有道理,我会更新我的答案
@italo 在这种情况下,rgettman 的答案和我自己的答案都是等价的。另外,如果你想强制 hashmap 永远不会改变的不变量,也许你应该使用 Collections.unmodifiableMap()
使它不可变【参考方案5】:
根据reference documentation of java.util.HashMap:
在设置其初始容量时应考虑map中预期的条目数及其负载因子,以尽量减少rehash操作的次数。如果初始容量大于最大条目数除以负载因子,则不会发生重新哈希操作。
这意味着,如果您提前知道 HashMap 应该存储多少条目,则可以通过选择适当的初始容量和加载因子来防止重新散列。然而:
作为一般规则,默认负载系数 (.75) 在时间和空间成本之间提供了良好的折衷。较高的值会减少空间开销,但会增加查找成本(反映在 HashMap 类的大部分操作中,包括 get 和 put)。
【讨论】:
【参考方案6】:如果您不了解负载系数/容量内部结构,则经验法则:
initialCapacityToUse = (Expected No. of elements in map / 0.75) + 1
使用此初始容量值,将不会发生重新哈希以存储给定的预期数量。地图中的元素。
【讨论】:
您能解释一下为什么我们需要 +1 吗?以上是关于索引列表时的最佳 HashMap 初始容量的主要内容,如果未能解决你的问题,请参考以下文章