0.README
本文首先介绍了每一个对象都继承的hashCode()
和equals()
函数,然后说明HashTable
如何利用这两个函数将键值对存入其底层数据结构,最后介绍了重写Key对象的hashCode()
和equals()
(compareTo()
)函数的基本要求。
1.hashCode()
和equals()
函数
java.lang.Object
类有两个公共函数hashCode()
和equals()
。
int hashCode()
方法生成的散列码(hashcode),默认是使用对象的地址计算散列码,即使克隆后的对象,拥有相同的字段,也会具有不同的hashCode()值。这样显然是不合理的,因此通常需要根据字段重写。
boolean equals()
方法默认比较两个对象的hashCode()值。Object的子类会重写equals()方法,如String,比较的是字符串内容。
对基于散列的数据结构(HashSet, HashMap, LinkedHashSet, LinkedHashMap
)来说,在区分Key时,都是基于HashTable的底层原理,需要同时覆盖这两个方法(原因见第二部分)。例如:
public class Groundhog{
protected int number;
public Groundhog(int n){number = n;}
public int hashCode(){ // 使用字段生成散列码
return number;
}
public boolean equals(Object o){ // 比较字段number的值,若o为null,则返回false
return (o instanceof Groundhog) && (number == ((Groundhog)o).number);
}
public String toString(){
return "Groundhog #" + number;
}
}
2.HashTable的机理
这里以HashMap存放键值对的过程为例,介绍HashTable。
HashTable底层有两层数据结构,第一层是bucket数组,数组元素是存储键值对的链表结构,第二层就是这个存储键值对的链表结构。(如下图所示)
键值对添加到HashMap容器中时,首先计算Key的散列值(hashCode()
),然后根据散列值计算bucket数组下标(桶位),需要注意的是即使散列值不同,仍有可能被分配到同一个桶位。比如一种简单的方式是用hashCode() % bucket.length
来计算下标。
键值对进入桶位中的第二层链表结构时,用equals()
方法判断Key值是否已近存在量表中,若存在,则用新的Value值取代旧值,若不存在则向链表中添加这个键值对对象。
3.函数重写的基本要求
重写hashCode()
函数的基本要求是:
- 同一对象每一次计算出的散列值都相同。
- 不同的Key对象,它们地址不同,但如果它们具有相同的有标志意义的字段,应该也保证它们散列值相同,即基于对象的内容生成散列码。
- 为使bucket数组负载均匀,好的hashCode函数应该产生分布均匀的散列值,不会集中在某一桶位附近。
重写equals()
函数,要根据有标志意义的字段唯一确定对象,通常是直接比较这些字段的值。
有的Key需要排序,因此要实现Comparable
接口,与equals()
类似,在覆盖compareTo()
函数时,也要尽量根据有意义的识别内容来比较对象。
参考文献
[1] 《Java编程思想(第4版)》. https://book.douban.com/subject/2130190/