作者:chuntianhuaji | 来源:互联网 | 2022-10-28 11:14
我正在尝试编写演示代码,以显示当地图大小超过负载因子阈值时,Hashmap中正在发生重新哈希。我如何证明内部进行了哈希处理。我也想证明即使在重新哈希期间将旧条目移到新存储桶中,我也可以使用旧键来获取旧元素(让我知道我的假设是正确的)。下面的示例代码。
import java.util.*;
class RehashDemo{
public static void main(String[] args){
Map numbers = new HashMap<>(10);
for(int i = 0; i<10;i++){
numbers.put(i,i+"");
}
System.out.println(numbers);
for(int j = 15; j<=20;j++){
numbers.put(j,j+"");
}
System.out.println(numbers);
}
}
Stuart Marks..
6
编写演示哈希的程序并不难,但是您必须了解很多有关HashMap的内部组织,对象的哈希码如何生成,哈希码如何与HashMap的内部结构相关以及如何影响迭代顺序的知识。
简而言之,HashMap由一系列存储桶(“表”)组成。每个存储桶都是键值对的链接列表。将一对其键哈希值添加到已被占用的存储桶中,将其添加到该存储桶的链接列表的末尾。铲斗通过调用键的确定hashCode()
方法,与它的高次异或它16位右无符号移了16(参照源),然后取表大小的模量。由于表的大小始终是2的幂,因此本质上是与(tablesize-1)的掩码进行“与”运算。Integer
对象的哈希码只是其整数值。(来源)。最后,HashMap的迭代顺序依次遍历每个存储桶,还依次遍历每个存储桶内的对的链接列表。
毕竟,您可以看到小的整数值将最终出现在相应的存储桶中。例如,Integer.valueOf(0).hashCode()
为0。在进行“异和”运算后,它将保持为0,并且任何表大小的模将保持为0。因此,整数0最终存储在存储区0中,整数1最终存储在存储区1中,依此类推。但是不要忘记,存储桶是表大小的模数。因此,如果表大小为8,则整数8将最终出现在存储区0中。
有了这些信息,我们可以用整数键填充HashMap,这些键将最终存储在可预测的存储桶中。让我们创建一个表大小为8且默认加载因子为0.75的HashMap,这意味着我们可以在重新哈希发生之前添加六个映射。
Map map = new HashMap<>(8);
map.put(0, 0);
map.put(8, 8);
map.put(1, 1);
map.put(9, 9);
map.put(2, 2);
map.put(10, 10);
{0=0, 8=8, 1=1, 9=9, 2=2, 10=10}
toString()
如上所述,打印出地图(实质上是使用其方法)依次迭代地图。我们可以看到0和8出现在第一个存储桶中,1和9出现在第二个存储桶中,而2和10出现在第三个存储桶中。现在让我们添加另一个条目:
map.put(3, 3);
{0=0, 1=1, 2=2, 3=3, 8=8, 9=9, 10=10}
迭代顺序已更改!添加新映射超出了重新哈希的阈值,因此表大小增加了一倍,达到16。重新哈希已完成,这次模数为16而不是8。而0和8之前都在存储区0中,现在它们位于独立的存储桶,因为有可用存储桶的两倍。与1/9和2/10相同。当表大小为16时,旧表大小为8的每个存储桶中的第二个条目现在散列到其自己的存储桶中。您可以看到这一点,因为迭代顺序地通过存储桶进行,并且每个存储桶中现在都有一个条目。
当然,我仔细选择了整数值,以使表大小为8时不会发生冲突,而表大小为16时不会发生冲突。这使我们可以非常清楚地看到重新哈希。对于更典型的对象,很难预测哈希码(因此也包括存储桶),因此,很难看到冲突以及发生哈希时发生的变化。
1> Stuart Marks..:
编写演示哈希的程序并不难,但是您必须了解很多有关HashMap的内部组织,对象的哈希码如何生成,哈希码如何与HashMap的内部结构相关以及如何影响迭代顺序的知识。
简而言之,HashMap由一系列存储桶(“表”)组成。每个存储桶都是键值对的链接列表。将一对其键哈希值添加到已被占用的存储桶中,将其添加到该存储桶的链接列表的末尾。铲斗通过调用键的确定hashCode()
方法,与它的高次异或它16位右无符号移了16(参照源),然后取表大小的模量。由于表的大小始终是2的幂,因此本质上是与(tablesize-1)的掩码进行“与”运算。Integer
对象的哈希码只是其整数值。(来源)。最后,HashMap的迭代顺序依次遍历每个存储桶,还依次遍历每个存储桶内的对的链接列表。
毕竟,您可以看到小的整数值将最终出现在相应的存储桶中。例如,Integer.valueOf(0).hashCode()
为0。在进行“异和”运算后,它将保持为0,并且任何表大小的模将保持为0。因此,整数0最终存储在存储区0中,整数1最终存储在存储区1中,依此类推。但是不要忘记,存储桶是表大小的模数。因此,如果表大小为8,则整数8将最终出现在存储区0中。
有了这些信息,我们可以用整数键填充HashMap,这些键将最终存储在可预测的存储桶中。让我们创建一个表大小为8且默认加载因子为0.75的HashMap,这意味着我们可以在重新哈希发生之前添加六个映射。
Map map = new HashMap<>(8);
map.put(0, 0);
map.put(8, 8);
map.put(1, 1);
map.put(9, 9);
map.put(2, 2);
map.put(10, 10);
{0=0, 8=8, 1=1, 9=9, 2=2, 10=10}
toString()
如上所述,打印出地图(实质上是使用其方法)依次迭代地图。我们可以看到0和8出现在第一个存储桶中,1和9出现在第二个存储桶中,而2和10出现在第三个存储桶中。现在让我们添加另一个条目:
map.put(3, 3);
{0=0, 1=1, 2=2, 3=3, 8=8, 9=9, 10=10}
迭代顺序已更改!添加新映射超出了重新哈希的阈值,因此表大小增加了一倍,达到16。重新哈希已完成,这次模数为16而不是8。而0和8之前都在存储区0中,现在它们位于独立的存储桶,因为有可用存储桶的两倍。与1/9和2/10相同。当表大小为16时,旧表大小为8的每个存储桶中的第二个条目现在散列到其自己的存储桶中。您可以看到这一点,因为迭代顺序地通过存储桶进行,并且每个存储桶中现在都有一个条目。
当然,我仔细选择了整数值,以使表大小为8时不会发生冲突,而表大小为16时不会发生冲突。这使我们可以非常清楚地看到重新哈希。对于更典型的对象,很难预测哈希码(因此也包括存储桶),因此,很难看到冲突以及发生哈希时发生的变化。