栏目分类:
子分类:
返回
名师互学网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
名师互学网 > IT > 软件开发 > 后端开发 > Java

Java 中关于 Hash 的整理:HashCode() 、HashMap、一致性 Hash(负载均衡)等

Java 更新时间: 发布时间: IT归档 最新发布 模块sitemap 名妆网 法律咨询 聚返吧 英语巴士网 伯小乐 网商动力

Java 中关于 Hash 的整理:HashCode() 、HashMap、一致性 Hash(负载均衡)等

目录

一、HashCode() 与 equals()

1、默认情况

2、重写后

3、HashCode 与 equals 返回结果的关系总结

4、HashCode 与 equals 未被统一重写情况

【经典面试题】:为什么重写对象的 equals 方法时,要重写 HashCode 方法?

二、HashCode 在 HashMap 中的应用

1、为什么 HashMap 先比较 hash 值而不是直接用 equals?

2、关于 HashMap 的几个小点:

为什么HashMap线程不安全?

什么导致 HashMap 出现死循环?

三、一致性Hash

1、通过一致性 Hash 构建分布式缓存服务器

2、Hash 环常见问题:数据倾斜、缓存雪崩

3、节点缩扩容常见问题

解决扩容延时

解决缩容延时

四、Hash 算法的其他应用


一、HashCode() 与 equals()

对于没有重写 HashCode 和 equals 方法的引用对象,默认继承 Object 类中的 HashCode 和 equals 方法。

1、默认情况

equals:比较两个对象是否为同一个对象,即内存地址是否一样;

HashCode:返回该对象的 Hash 值,是一个整型数据(int,32位,4个字节),这个值可以是内存地址,也可以不是内存地址,这取决于 HashCode 的生成策略,可以通过 jvm 的启动参数来配置不同的 HashCode 生成算法,Java 一共提供了 6 种生成策略。默认生成策略不是内存地址,并且不能保证不发生Hash冲突。

hashCode真的是内存地址吗_wuychn的博客-CSDN博客_hashcode是内存地址吗本文内容基于 JAVA 8 HotSpothttps://blog.csdn.net/qmqm011/article/details/117928412Object 类中也对这两种方法做了解释:

注意:

1)hashcode 的一般约定:equals 为 true,hashcode 一定相等;equals 为 false,hashcode 最好是不相等,这样的效率最高,但32位的 hashcode 唯一性毕竟有限,总会不可避免的出现相等,即产生了hash冲突。

2)重写 hashCode 方法时,通常都需要重写 equals 方法,以便维护 hashCode 方法的一般约定,即相同的对象必须具有相同的 hash 值。

2、重写后

自动重写生成的 HashCode 和 equals 方法,都是依赖对象属性生成的。

equals:先通过 == 判断是否为同一对象,若不是,则判断两个对象是否来自同一个类,如果是,则继续比较两个对象的成员变量是否相等。

HashCode:按照成员变量的值计算出一个32位的hash值输出,这种方式会有较大概率发生 hash 冲突。

@Override
public boolean equals(Object o) {
    if (this == o) return true;
    if (o == null || getClass() != o.getClass()) return false;

    Test test = (Test) o;

    if (a != test.a) return false;
    if (b != test.b) return false;
    return c == test.c;
}

@Override
public int hashCode() {
    int result = a;
    result = 31 * result + (int) (b ^ (b >>> 32));
    result = 31 * result + c;
    return result;
}

3、HashCode 与 equals 返回结果的关系总结

前提:按照 hashCode 约定,统一被重写,或者都未被重写。

HashCode 相等 ---> equals 不一定(考虑是否存在hash冲突)

HashCode 不相等 ---> equals 一定为 false

equals 为 true ---> HashCode 一定相等(都是同一个对象了,一定相等)

equals 为 false ---> HashCode 可能相等(考虑是否存在hash冲突)

4、HashCode 与 equals 未被统一重写情况

只重写了 equals (从判断对象是否为同一对象,重写为,判断对象内容是否相等):

【没变】 HashCode 相等 ---> equals 不一定(仍要考虑是否存在hash冲突)

【变了】 HashCode 不相等 ---> equals 不一定

(equals 返回 true:是同类,对象内容一样,但不是同一对象,hashCode 不一样

   equals 返回 false:不是同类,hashCode 不一样)

【变了】 equals 为 true ---> HashCode 不一定

(hashCode 相等:是同一对象

   hashCode 不相等:是同类,对象内容一样,但不是同一对象)

【没变】 equals 为 false ---> HashCode 可能相等(仍要考虑是否存在hash冲突)

只重写了 HashCode(从由指定策略生成 HashCode,变为根据对象内容生成 HashCode):

【没变】 HashCode 相等 ---> equals 不一定

【没变】 HashCode 不相等 ---> equals 一定为 false

【没变】 equals 为 true ---> HashCode 一定相等

【没变】 equals 为 false ---> HashCode 可能相等

只重写 HashCode,无意义,相当于改变了 HashCode 生成策略,对结果无影响,重写只会增加 hash 冲突的可能。


【经典面试题】:为什么重写对象的 equals 方法时,要重写 HashCode 方法?

答:违反 Object.hashCode 的通用约定,从而导致该类无法结合所有基于散列的集合一起正常运作,这样的集合包括 HashMap、HashSet 和 Hashtable。如:一个类有两个对象A1,A2,他们的A1.equals(A2) 为 true,但 A1.hashcode 和 A2.hashcode 不一样,当将 A1 和 A2 都作为 HashMap 的 key 时,HashMap 会认为它两不相等,会同时作为 key 存入。


二、HashCode 在 HashMap 中的应用

以 HashMap 的插入操作为例,插入步骤如下:

1、由 HashCode 获取插入对象的 hash 值;

2、对 hash 值按 map 容量取模,计算插入对象在数组中的存储位置;

3、该存储位置无其他对象,直接插入;有其他对象,则遍历该位置中的对象,比较 hash 值是否相等;

4、无相等的对象,则直接插入;有 hash 值相等的对象,则调用 equals 比较对象内容;

5、内容不相等,则直接插入;内容相等,则更新 val。

1、为什么 HashMap 先比较 hash 值而不是直接用 equals?

如果 Map 已有 1000 个 key,在插入第 1001 个 key 时,直接用 equals,就需要比较 1000 次;而通过 hash 值先计算存储位置,则只需要单独比较当前存储位置的 key 即可,提高效率。

2、关于 HashMap 的几个小点:

1、JDK7 中的底层实现是数组+链表,JDK8 中使用的是数组+链表+红黑树;

2、在 JDK8 之前,HashMap 等其他基于 Map 类都是用链地址法解决冲突,它们使用单向链表来存储相同索引值的元素,最坏的情况下,这种方法会使得 HashMap 的 get 方法的性能从 O(1) 降低到 O(n)。为了提高性能,JDK8 中使用平衡树来替代链表存储冲突的元素,那么最坏的性能从O(n) 提高到 O(logn);

3、JDK7 中使用 Entry 来代表每个 HashMap 中的数据节点,JDK8 中使用 Node,基本没有区别,都是 key,value,hash 和 next 这四个属性,不过,Node 只能用于链表的情况,红黑树的情况需要使用 TreeNode。

4、JDK8 中 HashMap 默认容量为16,若当前容量大于等于了扩容的阈值时会自动进行扩容;

5、HashMap 的扩容机制是很影响效率的,所以如果事先能确定有多少个元素需要存储,那么建议在初始化 HashMap 时对数组的容量也进行初始化,防止扩容。

详见:

Java开发面试官终结者!HashMap高频面试题总结,必拿下!https://baijiahao.baidu.com/s?id=1703973277855654462

 Java 8中HashMap和linkedHashMap如何解决冲突_木霖森77的博客-CSDN博客什么时候会产生冲突??HashMap中调用hashCode()方法来计算hashCode。由于在Java中两个不同的对象可能有一样的hashCode,所以不同的键可能有一样hashCode,从而导致冲突的产生。解决:在Java 8 之前,HashMap和其他基于map的类都是通过链地址法解决冲突,它们使用单向链表来存储相同索引值的元素。从Java 8开始,HashMap,Concurr...https://blog.csdn.net/mulinsen77/article/details/84879833

为什么HashMap线程不安全?

1、如果多个线程同时使用 put 方法添加元素,而且假设正好存在两个 put 的 key 发生了碰撞(根据 hash 值计算的 bucket 一样),那么根据 HashMap 的实现,这两个 key 会添加到数组的同一个位置,这样最终就会发生其中一个线程的 put 的数据被覆盖;

2、如果多个线程同时检测到元素个数超过阈值,这样就会发生多个线程同时对数组进行扩容,都在重新计算元素位置以及复制数据,但是最终只有一个线程扩容后的数组,也就是说其他线程的都会丢失。

什么导致 HashMap 出现死循环?

因为多线程会导致 HashMap 的节点形成环链,这样当遍历集合时 Entry 的 next 节点用于不为空,从而形成死循环。

三、一致性Hash

一致性 Hash 通过构建环状的 Hash 空间代替线性 Hash 空间,解决集群的数量 N 发生变化时,之前的所有 Hash 映射就会全部失效的问题。环形 Hash 空间由 2^32 个点组成,称为 Hash 环。

1、通过一致性 Hash 构建分布式缓存服务器

步骤如下:

1、首先求出memcached服务器(节点)的哈希值,并将其配置到 0~2^32 的圆上。

2、然后采用同样的方法求出存储数据的键的哈希值,并映射到相同的圆上。

3、然后从数据映射到的位置开始顺时针查找,将数据保存到找到的第一个服务器上。如果超过 2^32 仍然找不到服务器,就会保存到第一台memcached服务器上。

 若要新增节点,则会影响到新增节点的顺时针方向上的下一个节点的保存内容。删除节点同理。

2、Hash 环常见问题:数据倾斜、缓存雪崩

数据倾斜:指分布式节点在环上分布不均匀,导致某些节点需要负责更多的 key,大量请求到来时,导致超负荷。

缓存雪崩:删除节点时,当前节点负责的 key,全部交给顺时针方向上的下一个节点处理,以此类推,若删除过多节点,会导致某些节点超负荷,造成缓存雪崩。

解决方案:引入虚拟节点,一个实际节点映射出多个虚拟节点,能够在 Hash 环上分布更加均匀;且在删除节点时,能够更加均匀的将自身所负责的 key 分配给其他节点。

3、节点缩扩容常见问题

缩扩容过程存在延时,在缩扩容数据准备过程中,可能会出现访问不到某些缓存,缓存击穿等问题。

缓存穿透:指缓存和数据库中都没有的数据,而用户不断发起请求,如发起为id为“-1”的数据或id为特别大不存在的数据。

缓存击穿:指缓存中没有但数据库中有的数据,这时由于并发用户特别多,同时读缓存没读到数据,又同时去数据库去取数据,引起数据库压力瞬间增大。

解决扩容延时

1、新节点提前预热高频 Key,但预热也需要时间,高频 Key 有效时间短,此时容易出现脏读。

2、保留旧 Hash 环,在扩容完成前,存在两个 Hash 环,新 Hash 环是扩容后的,旧 Hash 换是扩容前的,如果在新环找不到数据,就会转而访问旧环。该方案可以随意扩容多台机器,而不会产生大面积的缓存失效,但增加了缓存读取时间。

解决缩容延时

1、熔断机制,作为兜底方案,给每个节点设立对应熔断机制来保护服务的稳定性,防止删除节点后某些节点因为压力陡增而宕机。

2、节点已经删除,但负载均衡服务器没有来得及更新,导致请求转发给被删除的节点上,出现缓存击穿,解决这个问题主要有以下几种思路:

(a)缓慢缩容,等到Hash环完全同步后再操作。可以通过监听退出集群的访问QPS来实现这一点,等到该集群几乎没有QPS时再将其撤下。

(b)手动删除,如果Hash环上对应的节点找不到了,就手动将其从Hash环上删除,然后重新进行调度,这个方式有一定的风险,对于网络抖动等异常情况兼容的不是很好。

(c)主动拉取和重试,当Hash环上节点失效时,主动从ZK上重新拉取集群状态来构建新Hash环,在一定次数内可以进行多次重试。

一致性 hash 主要参考以下两篇文章:

面试必问的一致性Hash在负载均衡中的应用 - 云+社区 - 腾讯云一致性Hash是一种特殊的Hash算法,由于其均衡性、持久性的映射特点,被广泛的应用于负载均衡领域,如nginx和memcached都采用了一致性Hash来作为...https://cloud.tencent.com/developer/article/1798049一致性哈希算法原理 - lpfuture - 博客园一致性Hash算法背景 一致性哈希算法在1997年由麻省理工学院的Karger等人在解决分布式Cache中提出的,设计目标是为了解决因特网中的热点(Hot spot)问题,初衷和CARP十分类似。一致https://www.cnblogs.com/lpfuture/p/5796398.html

四、Hash 算法的其他应用

HashMap、HashSet 和 Hashtable 是 Hash 算法在 散列集合 的应用;

一致性 Hash 是 Hash 算法在 负载均衡 的应用;

除此之外,还有:

安全加密

MD5 算法 : Message Digest Algorithm MD5消息摘要算法

SHA 算法 : Secure Hash Algorithm 安全散列算法

DES 算法 : Data Encryption Standard 数据加密标准

AES 算法 : Advanced Encryption Standard 高级加密标准

对用于加密的哈希算法,有两个特别重要的要求: 1、很难通过Hash值推导出原始的字符串;2、散列冲突的概率要很小。

唯一标识

例如可以把每个图片的唯一标识,图片的路径都存储到散列表中,要查看什么图片的时候可以根据唯一标识去查到对应的图片。

数据校验

检验一个文件是否被修改过,可以通过对比 Hash 值是否变化,来校验数据的完整性和正确性。

数据分片

对海量数据的存储场景,比如,分布式缓存(Redis),判断该条数据分配在哪台 Redis 中。

转载请注明:文章转载自 www.mshxw.com
本文地址:https://www.mshxw.com/it/780775.html
我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 (c)2021-2022 MSHXW.COM

ICP备案号:晋ICP备2021003244-6号