HashSet 散列码与 hashCode 方法解析
1). 简述
java.lang.Object: int hashCode() ;返回对象的散列码,散列码可以是任意的整数,包括正数或负数;可被重写。
值得注意的是两个对象相等,那么它们两个应该有相同的散列码(即当我们重写实现 hashCode 方法时,我们应当注意与 equals 方法的兼容);
例如:如果 x.equals(y) == true 或 y.equals(x) == true ,那么 x.hashCode() == y.hashCode()
2). hashCode 方法的底层实现原理
a. 每个对象都有一个默认的散列码,即 Object.hashCode():返回的散列码是当前对象的存储地址(以 int 类型返回) ,我们亦可以自己重写 hashCode 方法,自己规定散列码的计算方式。
b. String 类的 hashCode 方法:String 类就重写了 Object 的 hashCode 方法,重新规定了计算其对象的散列码的方式
int hash = 0;
for(int i = 0; i < length; i++)
hash = 31*hash + charAt(i); // str.charAt(indx) 返回的是字符串 str 的第 index 个位置的字符
3). 与 HashSet 的 add 方法相关的一个问题
Set 接口的实现类无法向集合内添加重复的元素,因此判断下方的元素能否添加成功( Node 是自定义类,没有重写 hashCode 方法)
HashSet<String> strings = new HashSet<>();
System.out.println(strings.add("Winters")); // true
System.out.println(strings.add("Winters")); // false
System.out.println(strings.add(new String("Easy Company"))); // true
System.out.println(strings.add(new String("Easy Company"))); // false
HashSet<Node> nodes = new HashSet<>();
System.out.println(nodes.add(new Node("Winters", null))); // true
System.out.println(nodes.add(new Node("Winters", null))); // true
HashSet 的 add 方法的原理是先计算所添加的对象元素的散列码,再根据散列码来决定并寻找所添加的对象元素在集合中的位置,一旦发现目标位置已经存在元素了(散列冲突),那么便会将所要添加的对象与当前位置中(即所谓的“桶”中,实则是数组中的链表元素)的所有对象进行比较,查看这个对象是否已经存在(调用 equals 方法来进行比较)。
如果不重复,那么便添加到桶中(实际上因为 HashSet 是数组链表,数组中的每个元素都是链表的头节点),即添加到当前位置的链表的尾部;
如果重复了则放弃添加。如下图所示
(补充:如果链表的长度达到一定值,出于查询效率的原因,链表会”树化“,转变成一颗搜索树)
因此上述问题的关键就在于 HashSet 中添加的元素的散列码是否相同,正如前文所说 String 类的 hashCode 方法被重写了,因此一旦字符串的内容是一致的,那么散列码就是一样的,这样便会造成所添加的元素重复,无法重复添加;而反观上面代码中向 HashSet 中添加 Node 节点,Node 中的 hashCode 方法没有重写,因此获取散列码的方式就是返回其对象的存储地址,因此即便 Node 中的 item 都为 “Winters” ,但由于是两个不同的对象,散列码不同因此可以添加成功。