当我们需要自定义HashMap这种散列数据结构(HashSet,HashMap,LinkedHashMap,LinkedHashSet)的Key时候:
需要重写hashCode()和equals(Object o)方法。
public class Key {
private Integer id;
public Integer getId(){
return id;
}
public Key(Integer id){
this.id = id;
}
@Override
public boolean equals(Object o) {
if(o == null || !(o instanceof Key)){
return false;
}else {
return this.getId().equals(((Key)o).getId());
}
}
@Override
public int hashCode() {
int hashCode = id.hashCode();
System.out.println(hashCode);
return hashCode;
}
}
public static void main(String[] args) {
Key k1 = new Key(1);
Key k2 = new Key(1);
HashMap<Key,String> hm= new HashMap<Key,String>();
hm.put(k1,"key with id is 1");
System.out.println(hm.get(k2));
}
我们想通过hm.get(k2)来获取得到“key with id is 1”的打印。
1.HashMap为了查询效率,存放的时候是通过散列表来存储的。所以需要重写hashCode来保证存储的数组下标一致。
2.equals方法是用来保证相等的。
正常我们查询是O(n)。
例如:
在长度为n(假设是500)的线性表(Arraylist)存放着无序的数字。
如果我们要找到一个指定的数字,就不得不通过从头到位一次遍历来查找,这样的平均查找次数是n除以2(250)
散列表用的是数组支持按照下标随机访问数据的特性,所以散列表其实就是数组的一种扩展,由数组演化而来。可以说,如果没有数组,就没有散列表。
利用数组支持根据下标随机访问的时候,时间复杂度是O(1)。
例如我们生活中的一个场景:
学校需要点名,学校3年2班的学号25定义为:030225,这个人是张三。
如果我们想要找到张三,我们只需大喊一声030225,则张三就会喊一声到!
我们很快就可以找到张三这个人。
030225的编号我们叫作键(key)或者关键字
张三我们称作为散列值。
030225转化为数组下标的映射方法就叫作散列函数。
散列函数在散列表中起着非常关键的作用。正常情况下散列函数的计算是比较复杂的。
我们看稍微复杂一点:
存放元素和存放位置是用hash来关联的。hash函数是x*x%5
6这个元素会放到索引为1的位置。7的hash函数结果为4。
我们想要从散列表中找6这个元素。我们可以先对元素进行一次hash运算。然后再从结果中找到这个位置。
如果再放入8的话,就会产生Hash冲突。
hash冲突通过一个链来将7和8关联起来。这个方法就是链地址法。
java中Object类会有HashCode方法。这个HashCode方法就是我们所说的散列函数。HashCode返回的值就是散列值。
我们再回头看上面的结论:
1.HashMap为了查询效率,存放的时候是通过散列表来存储的。所以需要重写hashCode来保证存储的数组下标一致。
就明白了。
Object.equals方法只是比较的对象的地址。
```java
public class Key {
private Integer id;
public Integer getId(){
return id;
}
public Key(Integer id){
this.id = id;
}
@Override
public boolean equals(Object o) {
if(o == null || !(o instanceof Key)){
return false;
}else {
return this.getId().equals(((Key)o).getId());
}
}
@Override
public int hashCode() {
int hashCode = id.hashCode();
return hashCode;
}
}
public static void main(String[] args) {
Key k1 = new Key(1);
Key k2 = new Key(1);
HashMap<Key,String> hm= new HashMap<Key,String>();
hm.put(k1,"key with id is 1");
System.out.println(hm.get(k2));
}
没有重写hahcode方法的时候,此调用是不对的调用结果是null。
原因:
当我们向hashcode里面放k1的时候,首先会调用Key这个类的HashCode方法来计算它的Hash值,随后把这个值放入这个值所索引的内存的位置。
没有重新定义HashCode的方法,它就会调用Object类的Hashcode方法。而Object里面返回的Hash值,是Key1的内存地址。
k2也是如此,但是k1和k2的内存地址是不一样的。
如果我们用k2去拿
hm.get(k2)
自然拿不到k1的值。
如果重写了hash值,则调用的hashCode是一样的。但是比较两个数是否一样,光hashCode值一样是不行的。并不保证k1和k2真正的相等。还需要重写equals方法。因为hashcode相等的时候,我们仅仅保证hashmap的数组下标相等。hash过程中会有散列冲突。真正保证相等的是equals方法。
如果不重写equals方法,它会调用equals方法的内存地址是否是一样。由于k1和k2是new出来的。他们的内存地址是绝对不一样的。
重写hashcode方法时候,我们判断定义可以就是如果两个对象一样,他们的id就是一样的。
equals不为空或者是Object才来判断内存地址。
@Override
public boolean equals(Object o) {
if(o == null || !(o instanceof Key)){
return false;
}else {
return this.getId().equals(((Key)o).getId());
}
}
@Override
public int hashCode() {
int hashCode = id.hashCode();
return hashCode;
}
我们终于知道了这个使用:
当我们需要自定义HashMap这种散列数据结构(HashSet,HashMap,LinkedHashMap,LinkedHashSet)的Key时候:
需要重写hashCode()和equals(Object o)方法。
《Java编程思想》 17.9散列与散列码