hashMap默认起始容量是16 为啥。

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了hashMap默认起始容量是16 为啥。相关的知识,希望对你有一定的参考价值。

为后来者解惑!
先抛出俩个问题:
1.为什么hashmap的容量约定是the power of 2 size呢
2.基于问题1的前提下,为什么不是32,或者8呢
回答:hashmap是基于数组的,源码: transient Node<K,V>[] table;
table俗称hash桶(hash bin),将一个元素放到桶里时,不是像arraylist那样按顺序放,而是根据key的hash值来计算index。
这个时候就会产生hash碰撞,即如果算法不合法,key会大概率的计算到同一个index,从而使元素都放在同一个桶中。
为了减少哈希碰撞的几率,我们需要一个算法,该算法能让元素比较平衡的放到不同的桶中,最简单的方式就是key.hash % table.length,为了效率,使用了位与&运算符。源码中使用了tab[i = (n - 1) & hash]。
当n=the power of 2时,n-1的二进制的后几位全是1,这时上文提到的【&】操作更均匀。
问题2,为什么是16呢,源码中是这么写的,Maybe a tradeoff between speed, utility, and quality of bit-spreading.就是考虑到速度,实用性和位拓展质量后的一个【最佳实践】。仅仅是一个最佳实践。可以根据自己的实际业务场景来设置不同的值,比如4,8等
参考技术A 源码中有这么一句,static final int DEFAULT_INITIAL_CAPACITY = 1 << 4;
无参构造函数就是使用这个来进行初始化的。

为啥 HashMap 要求初始容量是 2 的幂?

【中文标题】为啥 HashMap 要求初始容量是 2 的幂?【英文标题】:Why does HashMap require that the initial capacity be a power of two?为什么 HashMap 要求初始容量是 2 的幂? 【发布时间】:2012-01-11 05:58:56 【问题描述】:

我在翻Java的HashMap源码的时候看到下面的

//The default initial capacity - MUST be a power of two.
static final int DEFAULT_INITIAL_CAPACITY = 16;

我的问题是为什么首先存在这个要求?我还看到允许创建具有自定义容量的 HashMap 的构造函数将其转换为 2 的幂:

int capacity = 1;
while (capacity < initialCapacity)
  capacity <<= 1;

为什么容量总是必须是二的幂?

另外,当执行自动重新散列时,究竟会发生什么?哈希函数也改变了吗?

【问题讨论】:

【参考方案1】:

映射必须计算出用于任何给定键的内部表索引,将任何int 值(可能为负)映射到[0, table.length) 范围内的值。当 table.length 是 2 的幂时,可以真的廉价地完成 - 并且在 indexFor 中:

static int indexFor(int h, int length) 
    return h & (length-1);

使用不同的表长度,您需要计算余数并确保它是非负数。这绝对是一个微优化,但可能是有效的:)

另外,当执行自动重新散列时,究竟会发生什么?哈希函数也改变了吗?

我不太清楚你的意思。使用了相同的哈希码(因为它们只是通过在每个键上调用 hashCode 来计算的),但是由于表长度的变化,它们在表中的分布不同。例如,当表长度为16时,5和21的哈希码最终都存储在表条目5中。当表长度增加到32时,它们将在不同的条目中。

【讨论】:

正是我想要的,谢谢。还有一个疑问,为什么 Entry 表是瞬态的,即使它保留了所有数据? @Sushant:表中的数据在 writeObject 中显式序列化(因此所有空条目都不会被写出)。使字段瞬态停止正常的序列化代码在对defaultWriteObject的调用中将其写出。 @JonSkeet h & (length-1) 如何处理底片?假设长度 =16 和 h = -7 @Jon 我正在尝试将您的答案与accepted answer here联系起来 这里不重要,但是Hashmap使用的key的hash不是key.hashCode()。散列是在key.hashCode() 之上应用的补充散列函数。这样做是为了防止糟糕的 hashCode 实现可能导致超出预期的冲突。【参考方案2】:

理想的情况实际上是使用素数大小作为HashMap 的后备数组。这样,您的密钥将更自然地分布在整个阵列中。但是,这适用于 mod 划分,并且随着 Java 的每个版本,该操作变得越来越慢。 从某种意义上说,2 的力量是你能想象到的最差的表大小,因为糟糕的哈希码实现更有可能在数组中产生键冲突。

因此,您会在 Java 的 HashMap 实现中找到另一个非常重要的方法,即 hash(int),它可以弥补糟糕的哈希码。

【讨论】:

是的,这很有意义,但作为额外的帮助,您能否多谈谈 hash(int) 函数如何改进原始哈希码。我看到它需要 xor 一些位,但我还没有完全理解它。 基本上,使用两个方法的力量使 hashCode 的低位成为重要的。对于糟糕的 hashCode 实现,这不会有太大差异(例如:10110111 和 00000111)。因此,随着所有位的移位,较高的位变得更重要。 “mod 操作随着 Java 的每一个版本变得越来越慢”的说法是相当具有误导性的。相反,位掩码操作以更快的速度变得更快,最终这两者都开始反映实际硬件的基本性能。在那个级别上,位掩码的性能肯定要高得多——足以让整个设置,包括额外的哈希码加扰步骤,仍然快得多。

以上是关于hashMap默认起始容量是16 为啥。的主要内容,如果未能解决你的问题,请参考以下文章

Map...---HashMap和Hashtable的区别-----...LinkedHashMap

HashMap的初始容量和加载因子

HashMap默认加载因子为什么选择0.75?(阿里)

hashMap记录

关于HashMap容量的初始化,还有这么多学问。

Hashmap,Set,Map,List,ArrayList的区别