HashMap确定key的存储位置的源码分析

 更新时间:2023年07月24日 10:42:48   作者:单程车票  
HashMap 作为 Java 中最常用的数据结构之一,用于存储和管理键值对,HashMap 基于哈希函数实现,能通过将 key 映射到特定的位置来实现快速存储、查找和删除数据,接下来将从源码角度分析以通俗易懂的方式向大家讲解一下 HashMap 如何确定 key 的存储位置的

前言

HashMap 通过哈希函数确定 key 的存储位置这一映射过程,可使得 HashMap 在常数时间内完成插入、查找和删除操作,那么大家是否有了解过 HashMap 底层是如何使用哈希函数实现映射的呢?是如何高效确认 key 的存储位置的呢?

接下来将从源码角度分析以通俗易懂的方式向大家讲解一下 HashMap 如何确定 key 的存储位置的。

源码分析

下面以 JDK 1.8 版本查看 HashMap 中最为常用的方法之一 put(K key, V value) 方法来看看 key 是如何确定存储位置的。

可以看到这里 key 会先通过 hash() 方法进行处理,进入 hash() 方法一探究竟。

扰动函数 #hash()

JDK 1.8 中的 hash() 方法

源码:

static final int hash(Object key) {
    int h;
    return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
}

第一次看到 (h = key.hashCode()) ^ (h >>> 16) 这个式子可能有点不太好理解,我们分开来看:

  • 可以看到式子中的 key.hashCode(),说明 HashMap 会将传入的 key 调用自身父类 ObjecthashCode() 来进行哈希计算。
  • 然后将计算得到的哈希值 h 通过 h ^ (h >>> 16) 把高 16 位异或(^)低 16 位形成新的哈希值(hashCode() 方法返回的是 int 类型,也就是 32 位的数据)。

(h = "key".hashCode() ^ (h >>> 16)) 为例:

将计算后的哈希值的高 16 位与低 16 位异或(^)的目的是为了通过把原本的低 16 位变成高 16 位和低 16 位的混合结果来使得低 16 位的随机性增大,这样在数组长度较小时,能起到保证高 16 位也参与到 Hash 计算中(这个在后面的取模操作中很好的体现),同时不会造成太大的性能开销。

JDK1.8 中 HashMap 的 hash() 方法也叫做扰动函数,其目的就是为了增加随机性,让数据元素更加均衡的散列,减少碰撞。

补充 JDK 1.7 的 hash() 方法

static int hash(int h) {
    int h = hashSeed;
        if (0 != h && k instanceof String) {
            return sun.misc.Hashing.stringHash32((String) k);
        }
    h ^= k.hashCode();
    h ^= (h >>> 20) ^ (h >>> 12); 
    return h ^ (h >>> 7) ^ (h >>> 4);
}

JDK 1.7 中对 hashCode() 方法计算出的哈希值进行了四次扰动,所以在性能上要差一点。在 JDK 1.8 对其优化了扰动算法,使得计算的性能开销降低许多。这里也是 JDK 1.7 和 1.8 在计算 key 的存储位置上不同的地方了。

取模操作

在扰动函数 hash() 方法中可以看到返回的是一个 int 类型的值,即值的范围在 [-2147483648, 2147483647] 内,也就是说通过 hash() 映射到的位置可以在近 40 亿的长度的数组中。

但是实际上的内存不允许数组达到这么大,那么 HashMap 是如何将这个哈希值映射到数组中呢,相信读者们的第一反应一定是取模了,那么 HashMap 是怎样进行取模操作的呢,深入 putVal() 方法可以看到:

可以看到方法中通过 (n - 1) & hash 来确定最终 key 的存储位置,其实 (n - 1) & hash 这个式子就是取模操作,把通过 hash() 方法计算后得到的 hash 和当前 HashMap 的数组长度 - 1 进行与运算,相当于 hash % n,从而将其映射到数组特定的索引中。那么为什么 (n - 1) & hash 等价于 hash % n 呢?

其实这个等价关系有一个必要的前提,这个前提就是 n 总是 2n2^n2n,即数组的长度总是 2 的幂次方。当数组长度为 2 的幂次方时,可以保证 (n - 1) & hash 等价于 hash % n

这是因为当 n 为 2 的幂次方时,n - 1 可以保证高位为 0,低位全 1 的效果,所以,按位与运算的结果相当于保留了 hash 的二进制表示的低位部分,而将高位部分全部置为 0,从而确保了 (n - 1) & hash 等价于 hash % n

举个例子:上图可以知道 "key" 的哈希值计算结果为 0000 0000 0000 0001 1001 1110 0101 1110 即 106078,假设当前数组长度为 16 即 0001 0000,那么正常取模为 106078 % 16 = 14

106078 & (16 - 1) 结果如图:

至于为什么要使用位运算呢?这是因为取模运算的性能开销较大,替换成位运算可以得到更高的计算效率,提高性能。并且数组长度为 2 的幂次方可以起到散列均匀分布,减少哈希碰撞的可能性。

总结

上面就是 HashMap 如何确定 key 的存储位置的整个源码分析过程了,过程可以分为三步:

  • 将传入的参数 key 调用自身的方法 hashCode() 得到哈希值 h。
  • 根据哈希值 h 调用扰动函数 hash() 计算 h ^ (h >>> 16) 得到扰动后的哈希值 hash。
  • 根据哈希值 hash 取模操作 hash & (n - 1) 从而确定 key 的存储位置。

以上就是本篇文章的全部内容了。

到此这篇关于HashMap确定key的存储位置的源码分析的文章就介绍到这了,更多相关HashMap确定key位置内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

相关文章

  • Eclipse将Maven项目打成jar包的方法

    Eclipse将Maven项目打成jar包的方法

    这篇文章主要介绍了Eclipse将Maven项目打成jar包的方法,本文通过实例代码给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下
    2007-09-09
  • springcloud-gateway整合jwt+jcasbin实现权限控制的详细过程

    springcloud-gateway整合jwt+jcasbin实现权限控制的详细过程

    这篇文章主要介绍了springcloud-gateway整合jwt+jcasbin实现权限控制,基于springboot+springcloud+nacos的简单分布式项目,项目交互采用openFeign框架,单独提取出来成为一个独立的model,需要的朋友可以参考下
    2023-02-02
  • 通过实例解析java8中的parallelStream

    通过实例解析java8中的parallelStream

    这篇文章主要介绍了通过实例解析java8中的parallelStream,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下
    2019-11-11
  • IDEA中WebService生成Java代码并调用外部接口实现代码

    IDEA中WebService生成Java代码并调用外部接口实现代码

    这篇文章主要介绍了IDEA中WebService生成Java代码并调用外部接口实现,本文给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下
    2021-05-05
  • SpringBoot整合ES-Elasticsearch的实例

    SpringBoot整合ES-Elasticsearch的实例

    这篇文章主要介绍了SpringBoot整合ES-Elasticsearch的实例,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教
    2022-05-05
  • springmvc 中dao层和service层的区别说明

    springmvc 中dao层和service层的区别说明

    这篇文章主要介绍了springmvc 中dao层和service层的区别说明,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教
    2021-08-08
  • 使用maven实现有关Jsoup简单爬虫的步骤

    使用maven实现有关Jsoup简单爬虫的步骤

    这篇文章主要介绍了使用maven实现有关Jsoup简单爬虫的步骤,文中附含详细示例代码,有需要的朋友可以借鉴参考下,希望能够有所帮助
    2021-09-09
  • 使用Spring Boot创建Web应用程序的示例代码

    使用Spring Boot创建Web应用程序的示例代码

    本篇文章主要介绍了使用Spring Boot创建Web应用程序的示例代码,我们将使用Spring Boot构建一个简单的Web应用程序,并为其添加一些有用的服务,小编觉得挺不错的,现在分享给大家,也给大家做个参考。一起跟随小编过来看看吧
    2018-05-05
  • SpringBoot开发实战之自动配置

    SpringBoot开发实战之自动配置

    SpringBoot的核心就是自动配置,自动配置又是基于条件判断来配置Bean,下面这篇文章主要给大家介绍了关于SpringBoot开发实战之自动配置的相关资料,需要的朋友可以参考下
    2021-08-08
  • Java之JFrame输出Helloworld实例

    Java之JFrame输出Helloworld实例

    这篇文章主要介绍了Java之JFrame输出Helloworld的方法,以输出Helloworld的实例分析了JFrame的简单入门技巧,需要的朋友可以参考下
    2015-02-02

最新评论