关于HashSet添加数据在table表中位置的问题,以及对象的hash值计算是按地址值计算吗的问题

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. 问题总结

  1. table表中元素在哪个位置应该时按hash值计算并转换过来的,转化方式都是一样的,那不同对象的位置只受hash值影响,那为什么两个dog对象在没有重写hashCode方法的时候在table表中计算的位置不一样,(1)这里计算hash值是以什么为基础计算的?对象的地址值吗?(2)重写了toString方法后是按toString转化后的字符串计算吗?那为什么两个dog对象计算的hash值会不一样?
  2. 为什么会出现第二个dog对象和lucy在同一个位置的情况?
hashset·java·hashmap
152 views
Comments
登录后评论
Sign In
·

搜索了一下,https://cloud.tencent.com/developer/article/1622192。

这个时候看文档/源码比较好,你可以按照文中的方法看你下你使用的 Java 中是如何实现的。

3.2 的问题很简单啊,既然它是哈希计算的,那么任何东西都可能映射到同一个位置吧。

a 和 b 在哈希表的同一个位置不代表它们的哈希值一样,只能说明它们的哈希值会映射到同一个位置,这个和映射算法以及 set 内部桶的数量都是有关系的。

·

首先,toString方法的作用仅仅是将对象以字符串形式描述,让程序员看得快乐一点,与哈希值的计算和对象相等与否的判断无关,没必要测试toString

你的第二个Dog对象和lucy在一个位置的原因可能是Dog对象的哈希值计算结果正好和lucy对象的哈希值映射在同一个数组下标,也就是存在同一个列表,但因为不相同,所以不覆盖

但是为什么你的图上所有对象生成的哈希值都一样了。。。好神奇

·

为什么两个dog对象在没有重写hashCode方法的时候在table表中计算的位置不一样?

因为所有的 Java 对象默认继承 java.lang.Object,Object 的 hashcode 方法返回对象地址,hashCode() 不一样所以计算的位置不一样,Object 的 equals 也是比较地址

重写了toString方法后是按toString转化后的字符串计算吗?

hashCode 跟 toString 方法无关

为什么会出现第二个dog对象和lucy在同一个位置的情况?

毕竟只有 16 个桶,很容易就取余到同一个位置了

·

Hash 表里两个核心点,

  1. 定位哪个桶靠 hashCode
  2. 判定是不是一个对象靠 equals
·

直接把 HashMap 计算桶位置的代码拿出来看

public class HashSetTest {
    public static void main(String[] args) {
        int h;
        Dog dog1 = new Dog("tom");
        System.out.println(((h = dog1.hashCode()) ^ (h >>> 16)) & 15);
        Dog dog2 = new Dog("tom");
        System.out.println(((h = dog2.hashCode()) ^ (h >>> 16)) & 15);
        Dog dog3 = new Dog("tom");
        System.out.println(((h = dog3.hashCode()) ^ (h >>> 16)) & 15);
    }
}

class Dog {
    private String name;

    public Dog(String name) {
        this.name = name;
    }
}

我试了一下,直接跑跟 debug 的结果确实不一样,换不同的 JDK 也会不一样