根据Sun Java实施,在扩展过程中,ArrayList的初始容量增长到3/2,而对于HashMap,扩展速度是原来的两倍。 这是什么原因呢?
根据实现,对于HashMap,容量应始终为2的幂。 这可能是HashMap行为的原因。 但是在那种情况下,问题是,对于HashMap,为什么容量应该始终是2的幂?
StringBuffer / StringBuilder也增长2倍,并且不需要大小必须是2的幂。
这大概就是两个不同的程序员为ArrayList和HashMap的实现编码的事实,他们俩都任意决定了不同的增长值。
增加ArrayList容量的昂贵部分是将后备阵列的内容复制到新的(更大)阵列中。
对于HashMap,它将创建一个新的后备数组,并将所有映射条目放入该新数组中。并且,容量越高,发生碰撞的风险越低。这更昂贵,并解释了为什么膨胀系数更高。 1.5与2.0的原因?我认为这是"最佳实践"或"良好的权衡"。
甚至ArrayList都可以将容量乘以2。它有没有害处?
危害在于ArrayList的大小越大,分配给它的内存就越多(如果不使用空间,这可能会浪费掉)。由于增加ArrayList的容量要比增加HashMap的容量便宜得多,因此随着ArrayList容量的增加而变得更加保守是有道理的。本质上,@ Andreas_D解释了为什么HashMap的因子应该大于ArrayList的因子。为什么特别是2.0和1.5?这可能是基于使用情况测试的,但是我想我不得不问Java开发人员自己。
@Arnab Biswas:另一个原因:ArrayList中未使用的内存被浪费了,与HashMap中的内存不同,在HashMap中,它使冲突率降低并因此加快了访问速度。
同样,由于几乎在每个实现中都使用了优化,所以哈希表通常增加2倍(并且支持它的桶列表的大小为2的幂):计算哈希时,进行模运算(< x4>)用于查找存储桶以将值放入:bucketIndex = hash % numBuckets。仅当n为2的幂时,才能将昂贵的x % n操作减少为按位x & (n - 1)。哈希图/表每次必须增长2倍,以维持支持它的存储桶的2幂数大小。请参阅下面的其他答案。
x&(n-1)stil即使n不是2的幂也可以生成正确的bucetIndex,不是吗? @Arka Majumdar
@Saint它将产生严格小于n的索引(只要n为正数),但是它可以跳过存储桶,可能会跳过大多数存储桶(例如,如果n是2的幂加1)
好,我了解,您完全正确。 @哈罗德
for HashMap why the capacity should always be in power of two?
我可以想到两个原因。
您可以快速确定哈希码进入的存储桶。您只需要按位与且不需要昂贵的模。 int bucket = hashcode & (size-1);
假设我们的增长因子为1.7。如果我们从大小11开始,下一个大小将是18,然后是31。没问题。对?但是Java中String的哈希码是用31的素数来计算的。字符串进入的存储区hashcode%31仅由String的最后一个字符如果存储所有以/结尾的文件夹,请再见O(1)。如果使用的大小例如为3^n,则增大n不会使分布变差。从大小3到9,存储区2中的每个元素现在将进入存储区2,5或7,具体取决于较高的数字。这就像将每个存储桶分成三部分。因此,整数增长因子的大小将是首选。 (当然,这全都取决于您如何计算哈希码,但是任意的增长因素并不会带来"稳定"的感觉。)
关于第二个参数:1.避免31很容易。 2.由于负值,表达式hashcode%31无法工作。 3.诸如HashMap.hash中的一些"哈希增强"可能会有所帮助。 4.模数可以用(int) ((size * (h & 0xFFFFFFFFL)) >> 32)之类的东西代替,这在我的计算机上快两倍以上。 5.所有人都说,+ 1。
HashMap的设计/实现方式中,其基础存储桶数必须为2的幂(即使您为其指定不同的大小,它也会使其为2的幂),因此每次都以2的倍数增长。 ArrayList可以是任何大小,并且在增长方式上可以更为保守。
避免在地图上发生碰撞的一般规则是将最大负载系数保持在0.75左右
为了减少冲突的可能性并避免昂贵的复制过程,HashMap的增长速度更大。
就像@Peter所说的,它必须是2的幂。
我不能给您说明为什么会这样(您必须询问Sun开发人员),但是要了解这种情况如何发生,请查看源代码:
HashMap:看看HashMap如何调整为新大小(源代码行799)
resize(2 * table.length);
ArrayList:源,行183:
int newCapacity = (oldCapacity * 3)/2 + 1;
更新:我错误地链接到Apache Harmony JDK的源-将其更改为Sun的JDK。
谢谢彼得,我已经检查了源代码。但这并没有帮助我了解API开发人员的意图。
顺便说一句:OpenJDK(以及Oracle JDK)使用一些完全不同的代码,但实际上也将其大小增加了一半。
对于ArrayList,现在使用效率稍高的int newCapacity = oldCapacity + (oldCapacity >> 1);计算新容量
散列利用将数据均匀分布到存储桶中的优势。该算法试图阻止存储桶中的多个条目("哈希冲突"),因为它们会降低性能。
现在,当HashMap的容量达到时,大小将扩展,现有数据将通过新存储桶进行重新分配。如果大小增加太小,那么这种空间的重新分配和重新分配将经常发生。
虽然这解释了基本原理,但并没有真正解释为什么HashMap将大小乘以2而不是像ArrayList那样乘以1.5。