6月10号在Java交流频道请教了一个问题,感谢@库米豪斯巴达锅铲祖师 和@HD Superman 的回复.
这里详细把问题描述一下,希望能够讨论一下.
1. 运行情况
1.1 代码
import java.util.HashSet;
public class HashSet01 {
public static void main(String[] args) {
HashSet set = new HashSet();
set.add("lucy");//添加成功
set.add("lucy");//加入不了
set.add(new Dog("tom"));//OK
set.add(new Dog("tom"));//Ok
System.out.println("set=" + set);
}
}
class Dog {
private String name;
public Dog(String name) {
this.name = name;
}
@Override
public String toString() {
return "Dog{" +
"name='" + name + '\'' +
'}';
}
}
1.2 运行情况
首先正如上面的注释情况,可以添加进去一个lucy,两个dog.
2. 问题描述
2.1.1 dog在数据表中的存放位置
在输出set时插入断点,观察到底层的数据表中,两个dog对象各自使用了表中的一个位置.
2.1.2 源码中对存放数据在table表中位置的计算
仍选一个set.add(new Dog("tom")),在这里插入断点,并步入查看源码,可以看到这里HashSet和HashMap的底层传入的key都是"Dog{name='tom'}"
思考1:这样的话,那是不是应该两个dog对象在table表中在同一个索引,但由于没有重写Dog类的equals()方法,新dog对象可以添加进去,但应该是下图这种,而不是2.1.1的图?
思考2:只在输出set集合的位置添加断点,可以看到三个元素在table表中的索引分别为是10 12 13,但是在添加dog对象的位置增加一个断点,第二个dog的索引怎么和lucy一样了?(第二个dog对象的hashCode和lucy一样了?)
2.1.3 注释掉原码中toString方法再观察
注释掉toString()方法后,除了HashSet和HashMap的底层传入的key变为对象的地址值外,其余情况均一致,第二个dog对象仍挂在lucy下.
2.1.4 重写toString和hashCode方法
此时,可以看到第二个dog对象和第一个对象在table表中的位置一致.
2.1.5 注释掉toString,重写hashCode方法
情况同2.1.4,第二个dog对象和第一个对象在table表中的位置一致.
3. 问题总结
- table表中元素在哪个位置应该时按hash值计算并转换过来的,转化方式都是一样的,那不同对象的位置只受hash值影响,那为什么两个dog对象在没有重写hashCode方法的时候在table表中计算的位置不一样,(1)这里计算hash值是以什么为基础计算的?对象的地址值吗?(2)重写了toString方法后是按toString转化后的字符串计算吗?那为什么两个dog对象计算的hash值会不一样?
- 为什么会出现第二个dog对象和lucy在同一个位置的情况?
搜索了一下,https://cloud.tencent.com/developer/article/1622192。
这个时候看文档/源码比较好,你可以按照文中的方法看你下你使用的 Java 中是如何实现的。
3.2 的问题很简单啊,既然它是哈希计算的,那么任何东西都可能映射到同一个位置吧。
a 和 b 在哈希表的同一个位置不代表它们的哈希值一样,只能说明它们的哈希值会映射到同一个位置,这个和映射算法以及 set 内部桶的数量都是有关系的。