Java 为什么HashMap要求初始容量为2的幂?

Java 为什么HashMap要求初始容量为2的幂?,java,hashmap,hashtable,hash,Java,Hashmap,Hashtable,Hash,我正在浏览Java的HashMap源代码时,看到了以下内容 //The default initial capacity - MUST be a power of two. static final int DEFAULT_INITIAL_CAPACITY = 16; 我的问题是,为什么这一要求首先存在?我还看到,允许创建具有自定义容量的HashMap的构造函数将其转换为二次幂: int capacity = 1; while (capacity < initialCapacity)

我正在浏览Java的HashMap源代码时,看到了以下内容

//The default initial capacity - MUST be a power of two.
static final int DEFAULT_INITIAL_CAPACITY = 16;
我的问题是,为什么这一要求首先存在?我还看到,允许创建具有自定义容量的HashMap的构造函数将其转换为二次幂:

int capacity = 1;
while (capacity < initialCapacity)
  capacity <<= 1;
int容量=1;
同时(容量<初始容量)

容量映射必须计算出对任何给定键使用哪个内部表索引,将任何
int
值(可能是负数)映射到
[0,table.length)
范围内的值。当
table.length
是二的幂时,这可以非常便宜地实现,在
indexFor
中:

static int indexFor(int h, int length) {
    return h & (length-1);
}
对于不同的表长度,您需要计算余数并确保它不是负的。这肯定是一个微观优化,但可能是一个有效的优化:)

另外,当执行自动重灰化时,会发生什么?哈希函数是否也发生了更改


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

理想的情况实际上是对backi使用素数大小ng一个
HashMap
的数组。这样你的密钥将更自然地分布在整个数组中。然而,这与mod division一起工作,并且随着Java的每一个版本,该操作变得越来越慢。 从某种意义上说,2方法的威力是你能想象到的最糟糕的表大小,因为糟糕的hashcode实现更可能在数组中产生键讨论


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

正是我想要的,谢谢。还有一个疑问,为什么条目表是暂时的,即使它保留了所有的数据?@Sushant:表中的数据在writeObject中被显式序列化(这样所有的空条目都不会被写出来)。使字段暂时停止正常序列化代码在调用
defaultWriteObject
@JonSkeet时也将其写出。h&(length-1)处理负片?假设长度=16,h=-7@Jon我试图将您的答案和它在这里并不重要联系起来,但Hashmap使用的键的散列不是
key.hashCode()
。散列是应用于
key.hashCode()之上的补充散列函数
。这样做是为了防止糟糕的哈希代码实现,这可能会导致不必要的冲突。是的,这很有意义,但是作为一个额外的帮助,您可以谈谈哈希(int)是如何实现的吗函数开始改进原始哈希代码。我看到它使用了一些位的xor,但我还没有完全理解它。基本上,使用两种方法的威力使哈希代码的低位成为重要的位。对于糟糕的哈希代码实现,这不会有太大的不同(例如:10110111和00000111)因此,随着位元的移动,越高的位元就越重要。“随着Java的每一个版本,mod操作变得越来越慢”的说法这是相当误导的。相反,是位掩码操作以更快的速度变得更快,最终这两种操作都开始反映实际硬件的底层性能。在这个级别上,位掩码的性能肯定要高得多——足以使整个设置,包括附加的哈希码置乱步骤,仍然是一个问题快多了。