你能说一说为什么实现equals方法的时候需要实现hashcode吗,读过hashmap的源码吗
面试的时候面试官可能会问你hashmap的实现原理,在这里我以一种简单而形象的表达说一下hashmap的主要实现。
首先,让我们联想一下hashmap的结构,这里可以先给一张结构图
可以看到hashmap里面的主要存储结构是table数组,数组里面存放的可以是单一的节点,也可以是链表节点。
数组table的定义如下
transient Node<K,V>[] table;
节点定义如下
static class Node<K,V> implements Map.Entry<K,V> {
final int hash;
final K key;
V value;
Node<K,V> next;
Node(int hash, K key, V value, Node<K,V> next) {
this.hash = hash;
this.key = key;
this.value = value;
this.next = next;
}
/**
* 省略一些方法
*/
}
每个table存放的都是这样结构的节点,现在的问题是如何存放这样的节点呢,这就需要根据key的hashcode值来决定摆放的位置了,hashcode的值计算代码如下
static final int hash(Object key) {
int h;
return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
}
看到了把,这里就需要调用每个key的hashCode方法。
然后得到hash值之后,就需要put进table里,这里就需要判断了,到底对应table哪个下标呢,这里源码给出的计算如下
p = tab[i = (n - 1) & hash]
然后判断这里的p如果为null,说明table数组当前下标没有节点,可以安心放入,但是如果发生了hash碰撞了,即里面已经存放了一个节点怎么办呢,我们这时候才需要判断已经存在的节点的key的值和将要放入的节点的key值是否相等,源码有这样的判断
(k = p.key) == key || (key != null && key.equals(k)))
ok,这时候如果key相等了,就直接进行值的覆盖,既新值盖旧值,源码的赋值如下
if (e != null) { // existing mapping for key
V oldValue = e.value;
if (!onlyIfAbsent || oldValue == null)
e.value = value;
afterNodeAccess(e);
return oldValue;
}
e.value = value,就是对节点赋上了新值了。
如果key不相等的话,那就在节点后面新建一个节点,形成一个链表。注意一下当链表长度大于默认值8的时候,会自动转化为红黑树,来缩短查询时间。
所以这里hashmap的原理大概清楚这几点就差不多了,可以看出equals方法只是在比较hash发现有冲突后才调用的,这样能大大缩短比较次数,加快查找速度。
hashmap在查找性能上还是相当高的哈。
最后解释一下为什么需要重写hashcode方法
- 如果两个对象相等,则hashcode一定也是相同的
- 两个对象相等,对两个对象分别调用equals方法都返回true
- 两个对象有相同的hashcode值,它们也不一定是相等的
- 因此,equals 方法被覆盖过,则 hashCode 方法也必须被覆盖
- hashCode() 的默认行为是对堆上的对象产生独特值。如果没有重写 hashCode(),则该 class 的两个对象无论如何都不会相等(即使这两个对象指向相同的数据)