internal static readonly int[] primes =
3, 7, 11, 17, 23, 29, 37, 47, 59, 71, 89, 107,
131, 163, 197, 239, 293, 353, 431, 521, 631, 761,
919, 1103, 1327, 1597, 1931, 2333, 2801, 3371,
4049, 4861, 5839, 7013, 8419, 10103, 12143, 14591,
17519, 21023, 25229, 30293, 36353, 43627, 52361,
62851, 75431, 90523, 108631, 130363, 156437,
187751, 225307, 270371, 324449, 389357, 467237,
560689, 672827, 807403, 968897, 1162687, 1395263,
1674319, 2009191, 2411033, 2893249, 3471899,
4166287, 4999559, 5999471, 7199369
当要扩容的数组大小超过以上素数时,再使用素数生成算法来获取跟其两倍大小相近的素数。正常情况下,我们可能不会存储这么多内容。细心的你可能发现这样很耗内存。没错,这的确非常耗费内存资源。比如当我们要在容量为 11 的 Hashtable 中添加 8 个元素。因为 8 / 11 > 0.72,所以要扩容。根据算法,跟 2 * 11 相近的素数是 23。看出有多浪费了吧。即使通过构造函数把容量设置为 17,也浪费了 9 个空间。假如你有 Key - Value 映射的需求,同时对内存又比较苛刻,可以考虑使用由红黑树构造的词典或映射。
那 Dictionary<TKey, TValue> 又是什么情况呢?
它没有采用 Hashtable 类的探测方法,而是采用了一种更流行,更节约空间的方法:分离链接散列法(separate chaining hashing)。
采用分离链接法的 Dictionary<TKey, TValue> 会在内部维护一个链表数组。对于这个链表数组 L0,L1,…,LM-1,散列函数将告诉我们应当把元素 X 插入到链表的什么位置。然后在 find 操作时告诉我们哪一个表中包含了 X。这种方法的思想在于:尽管搜索一个链表是线性操作,但如果表足够小,搜索非常快(事实也的确如此,同时这也是查找,插入,删除等操作并非总是 O(1) 的原因)。特别是,它不受装填因子的限制。
这种情况下,常见的装填因子是 1.0。更低的装填因子并不能明显的提高性能,但却需要更多的额外空间。Dictionary<TKey, TValue> 的默认装填因子便是 1.0。Microsoft 甚至认为没有必要修改装填因子,所以我们可以看到 Dictionary<TKey, TValue> 的构造函数中找不到关于装填因子的信息。Java 的 HashMap<K, V> 默认装填因子是 0.75。它的理由是这样可以减少检索的时间。我在测试的时候,发现Java HashMap<K, V> 检索时间的确要比 .NET Dictionay<TKey, TValue> 的检索时间要少,但差距相当微小。同时 HashMap<K, V> 的插入时间却跟 Dictionary<TKey, TValue> 差了老大一截,几乎是后者的 3~8 倍。一开始,我以为是错觉。因为 HashMap<K, V> 没有采用取模操作,而是位移操作,而且它使用的容量大小也是以 2 的指数级增长。这些都是些加速操作。甚是疑惑,望达人解答。
分离链接散列法的吸引力不仅在于适度增加装填因子时,性能不受影响,而且可以在扩容时避免再次散列(这相当耗时)。
最后,当我们要在应用程序中使用 Hashtable 或 Dictionary<TKey, TValue> 时,请尽量评估要插入的元素数量,因为这可以有效避免扩容和再次散列操作。同时,装填因子尽量使用 1.0。
PS:实现代码就不给出了。待描述并发散列表时,一并给出吧。😃
HashMap默认加载因子为什么选择0.75?(阿里)
Hashtable 初始容量是11 ,扩容 方式为2N+1;
HashMap 初始容量是16,扩容方式为2N;
阿里的人突然问我为啥扩容因子是0.75,回来总结了一下; 提高空间利用率和 减少查询成本的折中,主要是泊松分布,0.75的话碰撞最小,
HashMap有两个参数影响其性能:初始容量和加载因子。
容量是哈希表中桶的数量,
初始容量只是哈希表在创建时的容量。
加载因子是哈希表在其容量自动扩容之前可以达到多满的一种度量。=当哈希表中的条目数超出了加载因子与当前容量的乘积时,则要对该哈希表进行扩容、rehash操作(即重建内部数据结构),也就是 rehash,因此这个 rehash 相当耗时,扩容后的哈希表将具有两倍的原容量。
通常,加载因子需要在时间和空间成本上寻求一种折衷。
- 加载因子过高,例如为1,虽然减少了空间开销,提高了空间利用率,但同时也增加了查询时间成本;
- 加载因子过低,例如0.5,虽然可以减少查询时间成本,但是空间利用率很低,同时提高了rehash操作的次数。
在设置初始容量时应该考虑到映射中所需的条目数及其加载因子,以便最大限度地减少rehash操作次数,所以,一般在使用HashMap时建议根据预估值设置初始容量,减少扩容操作。
选择0.75作为默认的加载因子,完全是时间和空间成本上寻求的一种折衷选择,
正文
前几天在一个群里看到有人讨论hashmap中的加载因子为什么是默认0.75。
HashMap源码中的加载因子