HashMap原理, hashCode()和equals()的关系

规定/规则:通用约定是:相等的对象应该具有相同的hashCode

在每个覆盖了equals方法的类中,也必须覆盖hashCode方法,

如果不这样做的话,就会违反Object.hashCode的通用约定。

从而导致该类无法结合所有基于散列的集合(比如HashSet\HashMap\HashTable)一起正常运作。

简单的例子:定义HashCodeID8,比如我们的ID998的余数为1,那么我们就把该类存在1这个位置,如果ID13,求得的余数是5,那么我们就把该类放在5这个位置。依此类推。  

如果两个类有相同的HashCode,例如9除以817除以8的余数都是1,(917有相同的哈希值,但他们不是同一个对象)也就是说,我们先通过 HashCode来判断两个类是否存放某个桶里,但这个桶里可能有很多类,那么我们就需要再通过equals在这个桶里找到我们要的类。  

 

比较两个元素是否相同时,首先调用hashCode方法,如果没有重写hashCode方法,任何对象的hashCode方法是不相等的。(那就用不了hashCode判重(内容是否重复)了)

若不同,直接加入;若相同,再去调用equals方法,若相同,不加入,否则,加入。

HashMap工作:

HashMap是由数组和链表组成的存储数据的结构。

那么是如何确定一个数据存储在数组中的哪个位置呢?

就是通过hashCode方法进行计算出存储在哪个位置,

产生冲突的话就会调用equals方法进行比对,

如果不同,那么就将其加入链表尾部,如果相同就替换原数据。

计算位置当然不是上面简单的一个hashCode方法就计算出来,中间还有一些其他的步骤,这里可以简单的认为是hashCode确定了位置。

put方法执行流程:

 

  1. 检查哈希表是否是个空表,如果是空表就调用inflateTable方法进行初始化
  2. 判断key是否为null,如果为null,就调用putForNullKey方法, keynullkey-value存储在哈希表的第一个位置中
  3. 如果key不为null,则调用hash方法计算keyhash
  4. 根据hash值和Entry数组的长度定位到Entry数组的指定槽位i
  5. 判断Entry数组指定槽位的值e是否为null, 如果e不为null, 则遍历e指向的单链表, 如果传入的key在单链表中已经存在了, 就进行替换操作, 否则就新建一个Entry并添加到单链表的表头位置
  6. 如果enull, 就新建一个Entry并添加到指定槽位
public V put(K key, V value) {
	    // 如果哈希表没有初始化就进行初始化
	    if (table == EMPTY_TABLE) {
	        // 初始化哈希表
	        inflateTable(threshold);
	    }
	 
	    // 当key为null时,调用putForNullKey方法,保存null于table的第一个位置中,这是HashMap允许为null的原因
	    if (key == null) {
	        return putForNullKey(value);
	    }
	 
	    // 计算key的hash值
	    int hash = hash(key);
	    // 根据key的hash值和数组的长度定位到entry数组的指定槽位
	    int i = indexFor(hash, table.length);
	    // 获取存放位置上的entry,如果该entry不为空,则遍历该entry所在的链表
	    for (Entry<K, V> e = table[i]; e != null; e = e.next) {
	        Object k;
	        // 通过key的hashCode和equals方法判断,key是否存在, 如果存在则用新的value取代旧的value,并返回旧的value
	        if (e.hash == hash && ((k = e.key) == key || key.equals(k))) {
	            V oldValue = e.value;
	            e.value = value;
	            e.recordAccess(this);
	            return oldValue;
	        }
	    }
	 
	    // 修改次数增加1
	    modCount++;
	    // 如果找不到链表 或者 遍历完链表后,发现key不存在,则创建一个新的Entry,并添加到HashMap中
	    addEntry(hash, key, value, i);
	    return null;
	}

 

void addEntry(int hash, K key, V value, int bucketIndex) {
	    // 如果键值对的总数大于等于阀值,且当前要插入的key-value没有发生hash碰撞,则进行扩容
	    if ((size >= threshold) && (null != table[bucketIndex])) {
	        // 扩容到原来容量的两倍
	        resize(2 * table.length);
	        // 扩容后重新计算hash值
	        hash = (null != key) ? hash(key) : 0;
	        // 扩容后重新确定Entry数组的槽位
	        bucketIndex = indexFor(hash, table.length);
	    }
	 
	    // 创建一个Entry对象,并添加到Entry数组的指定位置中
	    createEntry(hash, key, value, bucketIndex);
	}
static class Entry<K, V> implements Map.Entry<K, V> {
    final K key;
    V value;
    Entry<K, V> next;    // 下一个Entry对象的引用
    int hash;            // 元素的hash, 其实就是key的hash值   
}

// 空的哈希表
static final Entry<?, ?>[] EMPTY_TABLE = {};
 
// 实际使用的哈希表
transient Entry<K, V>[] table = (Entry<K, V>[]) EMPTY_TABLE;

 

 

 

 

6、HashMap是如何通过key的hash值来定位到Entry数组的指定槽位的?

static int indexFor(int h, int length) {

        // hash值和length-1进行与运算来计算索引

        return h & (length - 1);

    }

数组的下标是根据传入的元素hashCode方法的返回值再和特定的值异或决定的。

 

问:为什么重载hashCode方法?

答:一般的地方不需要重载hashCode,只有当类需要放在HashTableHashMapHashSet等等hash结构的集合时才会重载hashCode,那么为什么要重载hashCode呢?

如果你重写了equals,比如说是基于对象的内容实现的,而保留hashCode的实现不变,那么很可能某两个对象明明是相等,而hashCode却不一样。

这样,当你用其中的一个作为键保存到hashMaphasoTablehashSet中,再以相等的找另一个作为键值去查找他们的时候 ,则根本找不到。

 

问:重写了equals没有重写hashcode会发生什么?

假如只重写equals而不重写hashcode,那么Student类的hashcode方法就是Object默认的hashcode方法,由于默认的hashcode方法是根据对象的内存地址经哈希算法得来的一个值

那么很可能某两个对象明明是相等,而hashCode却不一样。

这样,当你用其中的一个作为键保存到hashMaphasoTablehashSet中,再以相等的”找另一个作为键值去查找他们的时候,则根本找不到。导致HashSet、HashMap不能正常的运作

 

三、equals方法和hashcode的关系?

     通过前面这个例子,大概可以知道,先通过hashcode来比较,如果hashcode相等,那么就用equals方法来比较两个对象是否相等,用个例子说明:上面说的hash表中的8个位置,就好比8个桶,每个桶里能装很多的对象,对象A通过hash函数算法得到将它放到1号桶中,当然肯定有别的对象也会放到1号桶中,如果对象B也通过算法分到了1号桶,那么它如何识别桶中其他对象是否和它一样呢,这时候就需要equals方法来进行筛选了。

       1、如果两个对象equals相等,那么这两个对象的HashCode一定也相同

        2、如果两个对象的HashCode相同,不代表两个对象就相同,只能说明这两个对象在散列存储结构中,存放于同一个位置

  这两条你们就能够理解了。

四、为什么equals方法重写的话,建议也一起重写hashcode方法?

      (如果对象的equals方法被重写,那么对象的HashCode方法也尽量重写)

      举个例子,其实就明白了这个道理,

        比如:有个A类重写了equals方法,但是没有重写hashCode方法,看输出结果,对象a1和对象a2使用equals方法相等,按照上面的hashcode的用法,那么他们两个的hashcode肯定相等,但是这里由于没重写hashcode方法,他们两个hashcode并不一样,所以,我们在重写了equals方法后,尽量也重写了hashcode方法,通过一定的算法,使他们在equals相等时,也会有相同的hashcode值。

总结:重写hashcode是为了当equals相等时,这两对象的hashcode可以相等。但算法怎么实现,咱也不知道

 

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
HashMap是基于哈希表实现的,因此在使用HashMap时,需要重写hashCodeequals方法,以确保正确性和效率。 重写hashCode方法: hashCode方法是用来产生对象的哈希值,HashMap使用哈希值来定位元素在哈希表中的位置,因此hashCode方法的实现需要满足以下要求: 1. 如果两个对象的equals方法返回true,则它们的hashCode方法返回值必须相等; 2. 如果两个对象的equals方法返回false,则它们的hashCode方法返回值不要求相等,但是不相等的情况下,能够让哈希表的性能更好。 在重写hashCode方法时,建议使用对象的成员变量生成哈希值,确保不同对象的哈希值不同。 重写equals方法: equals方法用来比较两个对象是否相等,HashMap使用equals方法来判断两个对象是否是同一个对象,因此equals方法的实现需要满足以下要求: 1. 自反性:对于任意的对象x,x.equals(x)必须返回true; 2. 对称性:对于任意的对象x和y,如果x.equals(y)返回true,则y.equals(x)也必须返回true; 3. 传递性:对于任意的对象x、y和z,如果x.equals(y)返回true,y.equals(z)返回true,则x.equals(z)也必须返回true; 4. 一致性:对于任意的对象x和y,在x和y没有发生改变的情况下,多次调用x.equals(y)应该返回相同的结果; 5. 非空性:对于任意的非空对象x,x.equals(null)必须返回false。 在重写equals方法时,建议使用对象的成员变量进行比较,确保相同的对象返回true,不同的对象返回false。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值