在实际应用中,很多时候我们Map存储的元素是不需要讲究顺序,key也不需要具备可比较性的。接下来我们就来了解一下哈希表(Hash Table)。
这些数据由 Key 和 Value 组成,哈希表底层是数组,Key 通过哈希函数计算(O(1)级别的计算)后,得到数组的索引,然后在数组索引位置放入 Value。如:
哈希表中哈希函数的实现步骤大概如下:
不同种类的 key, 哈希值的生成方式不一样,但目标是一致的
尽量让每个key的哈希值是唯一的
尽量让key的所有信息参与运算
将存储的二进制格式转为整数值(也就是说浮点数在计算机里也有一个对应的二进制数,它也能转化成相应的int类型的hashCode值)
自定义对象本身是继承自Object方法的,它本身就实现了hashCode的方法,但是它是以地址值作为哈希值的,所以即使是两个对象的属性值是一致的,但是该对象的hashCode的值也是不一致的。
所以在实际开发中,我们一般需要重写hashCode的方法以达到我们的需求。
public class Person {
private int age;
private float height;
private String name;
public Person(int age, float height, String name) {
this.age = age;
this.height = height;
this.name = name;
}
//计算出每个属性的hash值,并把该对象看成一个字符串
@Override
public int hashCode() {
int hashCode = Integer.hashCode(age);
hashCode = hashCode * 31 + Float.hashCode(height);
hashCode = hashCode * 31 + (name != null ? name.hashCode() : 0);
return hashCode;
}
}
比如我们把对象的属性看成字符串的组成,通过各个值运算后相加得到最终的hash值。这样上面两个对象对应的hash值就是相同的了。
除了重写hashCode() 方法外,还需要重写 equal() 方法 (HashMap的key 必须实现 hashCode, equals 方法)
主要作用:hashCode() 主要是为了定位索引值,equals() 主要是为了解决hash冲突时的值覆盖
@Override
public boolean equals(Object obj){
//内存地址
if(this == obj) return true;
if(obj == null || obj.getClass() != getClass()) return false;
//if(obj == null || !(obj instanceof Person)) return false;
//比较成员变量
Person person = (Person) obj;
return person.age == age
&& person.height == height
&& (person.name == null ? name == null : person.name.equals(name));
}
public static void main(String[] args) {
Person p1 = new Person(10, 1.67f, "jack");
Person p2 = new Person(10, 1.67f, "jack");
System.out.println(p1.hashCode());
System.out.println(p2.hashCode());
Map<Object, Object> map = new HashMap<>();
map.put(p1, "abc");
map.put("test","ccc");
map.put(p2, "bcd");
System.out.println(map.size());
}
假如我们没有重写hashCode和 equals 方法
那么我们得到 map的容量为3,因为Object类型的hashCode方法比较的是地址,所以是两个不同的Person 对象,添加后map的容量为3
假如我们重写equals方法,没有重写hashCode方法
那么我们map容量的值为2或3,因为两个Person的hash值虽然不一样,但他们定位的索引值可能一样,如果一样的情况,我们又实现了equals的方法,那么p2会覆盖p1,则容量为2;如果是不一样的情况,那么两个person的索引值自然不同,也就不存在覆盖现象,那么mpa的容量就为3.
假如我们重写hashCode方法,没有重写equals方法
那么我们map容量的值为3, 因为重写了hashCode后,两个Person对象的hash值是相同的,定位的索引值也是相同的,但是我们解决哈希冲突时调用的equals()方法默认是通过地址比较的,由于地址不同,所以不会覆盖,那么map的容量为3.
两个方法之间的联系
自定义对象作为 key, 最好同时重写 hashcode, equals方法 (hashcode是用来确定索引的位置的,equals是来解决hash冲突时的覆盖问题)