HashCode
前言
Hash是什么?
哈希函数
把任意长度的输入通过散列算法变换成固定长度的输出,该输出就是散列值,是一种压缩映射。
hash是一个函数,该函数中的实现就是一种算法,就是通过一系列的算法来得到一个hash值。hash表,通过hash算法得到的hash值存在hash表中,也就是说,hash表就是所有的hash值组成的,hash函数可以有很多种,只要能实现得到的每一个值不重复就可以。
Hash函数特性
h(k1)≠h(k2)则k1≠k2,即散列值不相同,则输入值即预映射不同
如果k1≠k2,h(k1)=h(k2) 则发生碰撞;
如果h(k1)=h(k2),k1不一定等于k2;
Hash的使用场景
什么时候使用hash函数?
如果从互联网下载一个文件,文件的下载过程中会经过很多网络服务器、路由器的中转,如何保证这个文件就是我们所需要的呢?不可以通过文件名,文件大小,文件类型这些容易被伪装的信息来判断,因为在传输的过程中这些信息很容易被修改和伪造。此时可以用hash算法来进行加密。通过hash算法对文件计算得到一个值,在本地在运用hash算法将这个值解析出来,如果文件被修改过,得到的值会不一样从而对文件进行加密保护。
HashCode
每个对象都可以计算hashcode,首先一个对象肯定有物理地址,将对象的物理地址转换成一个整数,然后该整数通过hash函数的算法就得到了hashcode,hashcode就是在hash表中对应的位置。
物理地址指对象存放在内存中的地址,hashcode代表对象的地址在hash表中的位置。
例:有一个hash表,表中有 hashcode为1、hashcode为2…hashcode为5这样5个位置,有一个对象A,假设A的物理地址转化为整数是17,通过特定的hash函数算出来的hashcode值为2(假设用物理地址%5来计算hashcode),则对象A应该在hash表中的2的位置。如果有一个对象B,算出来的hashcode值为3,则B应该在hashcode表中3的位置。如果有一个对象C,他计算出来的hashcode也等于2,则证明C和A的物理地址是一样的(假设hash函数正确,既不同的x值算出来的h(x)不同),则对象C和对象A应该是相等的。
HashCode关键点
1、HashCode的存在主要是为了查找的快捷性,hashcode是用来在散列存储结构中确定对象的存储地址的,通过hash函数可以快速的确定一个对象的物理地址。
2、如果两个对象equals相等,那么这两个对象的hashcode一定也相同,既equals相等,则物理地址相同,则计算出来的hashcode也一定相同。
3、如果对象的equals方法被重写,那么对象的HashCode方法也尽量重写,
4、如果两个对象的HashCode相同,不代表两个对象就相同,只能说明这两个对象在散列存储结构中,存放于同一个位置
判断两个对象相等
常用的.equals()去判断两个对象是否相等是需要重写equals方法的,重写equals方法时应该也尽量重写hashcode()方法。
例子:定义一个用户类,包含属性ID、name和age
public class Users {
int id;
String name;
int age;
public Users() {
}
public Users(int id, String name, int age) {
this.id = id;
this.name = name;
this.age = age;
}
}
在主函数中创建两个对象user1和user2,直接调用java自带的.equals()方法去比较:
public class Test {
public static void main(String[] args) {
Users user1=new Users(3,"John",5);
Users user2=new Users(3,"John",5);
if(user1.equals(user2)){
System.out.println("相同");
}else{
System.out.println("不相同");
}
}
}
得到的结果为不相同,这是因为此时没有重写equals方法,用的是java本身的方法,可以看到它比较的是地址,因为两个对象都是new出来的所以地址是不一样的。
java中自带的equals方法:
public boolean equals(Object obj) {
return (this == obj);
}
这样是没有错误的,但是如果我们在实际运用中想让地址不同但是id,name,age都相同的两个对象相等怎么办,这时就需要重写equals方法,在Users类中重写equals方法:
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Users users = (Users) o;
return age == users.age && Objects.equals(id, users.id) && Objects.equals(name, users.name);
}
可以看到在重写的equals方法中,首先用==比较的是两个对象的物理地址,如果物理地址相同则两个对象一定相同。然后判断输入的对象是不是为空或者两个对象的类型不一样,如果不一样直接返回false。然后去比较两个对象上的属性是不是一样,如果属性都一样就返回相同。
此时在运行main函数可以看到结果为相同。因为重写了equals方法,虽然两个对象的物理地址不同但是是根据实际需求将id,name,age相同的两个对象判断为相等。
同理,对hashcode也是一样的,如果不重写hashcode,则此时的user1和user2用hashcode判断时还是会得到不相同的结果。此时就需要重写hashcode方法:
@Override
public int hashCode() {
return Objects.hash(id, name, age);
}
此时使用对象的所有属性id,name和age去计算hashcode,得到的结果一定是一样的,则会判断user1和user2相同。
为什么要重写hashcode:
前面已经讲过,如果判定两个对象equals,则他们的hashcode也一定要相同,这样就可以方便的进行存储和提升查找效率,比如user1和user2属性都一样,则他们判定为equals,此时可以只用一个地址去存储这个user对象,调用的时候得到的属性是一样的,这样就必须要求他们得到的hashcode值一样。如果不重写hashcode方法,运用java自带的方式得到的结果是不一样的,所以要重写hashcode方式使不同地址的但属性一样的两个对象hashcode也相等,他们在hash表中的位置一样。
不重写hashcode属性相同地址不同的两个对象方法得到的hashcode值:
重写hashcode方法以后属性相同地址不同的两个对象得到的结果:
可以看到两个user对象的hashcode值是一样的,这样就可以在hash表中放在同一位置,需要这个属性的user时就可以快速的通过hashcode找到地址并调用。
注意如果两个对象的hashcode相同不代表他们是equals的,不同的对象可能会生成相同的hashcode值,因为这只是说他们在hash表中的位置是相同的,要通过equals去判断是否相等。。虽然不能根据hashcode值判断两个对象是否相等,但是可以直接根据hashcode值判断两个对象不等,如果两个对象的hashcode值不等,则必定是两个不同的对象。如果要判断两个对象是否真正相等,必须通过equals方法。
也就是说对于两个对象,如果调用equals方法得到的结果为true,则两个对象的hashcode值必定相等;
如果equals方法得到的结果为false,则两个对象的hashcode值不一定不同;
如果两个对象的hashcode值不等,则equals方法得到的结果必定为false;
如果两个对象的hashcode值相等,则equals方法得到的结果未知。