通俗解释hash碰撞是什么以及如何解决及hashcode重写
Hash如何存数据
hash表的本质其实就是数组,hash表中通常存放的是键值对Entry。
如下图:
这里的学号是个key,哈希表就是根据key值来通过哈希函数计算得到一个值,这个值就是下标值,用来确定这个Entry要存放在哈希表中哪个位置。
Hash碰撞
hash碰撞指的是,两个不同的值(比如张三、李四的学号)经过hash计算后,得到的hash值相同,后来的李四要放到原来的张三的位置,但是数组的位置已经被张三占了,导致冲突。
解决方法
hash碰撞的解决方式是开放寻址法和拉链法。
开放寻址法指的是,当前数组位置1被占用了,就放到下一个位置2上去,如果2也被占用了,就继续往下找,直到找到空位置。
拉链法采用的是链表的方式,这个时候位置1就不单单存放的是Entry了,此时的Entry还要额外保存一个next指针,指向数组外的另一个位置,将李四安排在这里,张三那个Entry中的next指针就指向李四的这个位置,也就是保存的这个位置的内存地址。如果还有冲突,就把又冲突的那个Entry放到一个新位置上,然后李四的Entry指向它,这样就形成一个链表。
总结起来:开放寻址法和拉链法都是想办法找到下一个空位置来存发生冲突的值。
@源:https://blog.csdn.net/zsyoung/article/details/114505480
HashMap以及重写hashCode()和equals()方法
几条基本原则:
-
同一个对象(没有发生过修改)无论何时调用hashCode()得到的返回值必须一样。
如果一个key对象在put的时候调用hashCode()决定了存放的位置,而在get的时候调用hashCode()得到了不一样的返回值,这个值映射到了一个和原来不一样的地方,那么肯定就找不到原来那个键值对了。 -
hashCode()的返回值相等的对象不一定相等,通过hashCode()和equals()必须能唯一确定一个对象。
不相等的对象的hashCode()的结果可以相等。hashCode()在注意关注碰撞问题的时候,也要关注生成速度问题,完美hash不现实 -
一旦重写了equals()函数(重写equals的时候还要注意要满足自反性、对称性、传递性、一致性),就必须重写hashCode()函数。而且hashCode()的生成哈希值的依据应该是equals()中用来比较是否相等的字段。
如果两个由equals()规定相等的对象生成的hashCode不等,对于hashMap来说,他们很可能分别映射到不同位置,没有调用equals()比较是否相等的机会,两个实际上相等的对象可能被插入不同位置,出现错误。其他一些基于哈希方法的集合类可能也会有这个问题
Effective Java Programming Language Guide中建议的hashCode写法
package com.test.test;
import java.util.HashMap;
import java.util.Objects;
/**
* @author :wdl
* @date :Created in 2020-05-21 8:44
* @description:
*/
public class A {
private int num ;
private String abc ;
public void setNum(int num) {
this.num = num;
}
public int getNum() {
return num;
}
public String getAbc() {
return abc;
}
public void setAbc(String abc) {
this.abc = abc;
}
@Override
public boolean equals(Object object) {
if (this == object) return true;
if (object == null || getClass() != object.getClass()) return false;
A a = (A) object;
return Objects.equals(abc, a.abc);
}
@Override
public int hashCode() {
return abc.hashCode() + num;
}
@Override
public String toString() {
return "A{" +
"num=" + num +
", abc='" + abc + '\'' +
'}';
}
public static void main(String[] args) {
HashMap<A, Integer> hashMap = new HashMap<>();
HashMap<Integer, A> hashMap2 = new HashMap<>();
A a = new A();
a.setAbc("123");
a.setNum(123);
int i = 10;
hashMap.put(a, i);
hashMap2.put(i,a);
a.setNum(10);
a.setAbc("abc");
System.out.println("hashMap : " + hashMap.get(a));
System.out.println("hashMap2 : " + hashMap2.get(i));
}
}
Result :
hashMap : null //a对象 的 hashcode 发生变化, hashMap.get 时 找不到原来那个键值对了
hashMap2 : A{num=10, abc='abc'}