HashMap底层数据结构详细解析

 更新时间:2023年11月18日 10:03:01   作者:智由静生  
这篇文章主要介绍了HashMap底层数据结构详细解析,HashMap作为开发中常用的数据结构,也是面试中经常被问的知识点,因此作为开发者应该尽可能多的理解其底层的数据结构,需要的朋友可以参考下

一、HashMap的底层数据结构

HashMap作为开发中常用的数据结构,也是面试中经常被问的知识点,因此作为开发者应该尽可能多的理解其底层的数据结构。

创建一个HashMap很简单,假设创建一个人员毕业院校的HashMap

Map<String, String> map = new HashMap<>();
map.put(”张三”: “南京大学”);
map.put(“李四”, “西北工业大学”);

你可能以为数据是这样存储的:

{
        “张三”:  “南京大学”,
        “李四”: ”西北工业大学”
}

但其实它的底层是数组,是这样存储的:

[<”张三”, “南京大学”>, <”李四”,”西北工业大学”>]

但元素并不是顺序放入数组的,它的计算方式是:对key值计算出一个hash值,然后用这个hash值对数组长度取模,根据取模计算结果定位到数组的位置。

假设数组长度是16,对”张三”的hash取模计算结果是4,那么它就放在数组的第5个位置上。实际的存储大约是这样:

[<>, <>, <>, <>, <”张三”, “南京大学”>, <>, <>, <”李四”,”西北工业大学”>, <>, <>, <>, <>, <>, <>, <>, <>]

取出元素的计算过程类似,比如map.get(“张三”),先对”张三”计算出一个hash值,然后用这个hash值对数组长度取模,根据模计算结果定位到数组中的位置,将该位置的元素取出。

二、JDK1.8对HashMap算法的优化

1、对寻址算法的优化

由hash值对数组长度n取模运算,改为hash值与数组长度n减1进行与运算,即hash&(n-1)。这两者在数学上,计算结果是等价的,但从计算机角度来说,后者的运算性能要比前者高很多。

2、对hash算法的优化

不是直接用hashcode值进行运算,而是使用了新的算法,以下是jdk1.8的一段源码:

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

hashCode()的返回值是一个32位整数,这个算法的意思就是用hashCode()值右移16位后的值与hashCode()原值进行异或运算。由于右移16位后左侧补0,而与0异或的结果是原值,所以hashCode()的高16位不变,因此此运算相当于hashCode()将的高16位与低16位进行异或运算。

例如:假设有如下一个key值

假设数组长度是16,它的计算过程如下:

那么为什么要进行这样的计算呢?

这是因为,数组长度一般比较小,因此,它的高16位一般都是0。0与任何数进行与运算,结果都是0,因此key值的高16位相当于没起作用,因为结果都一样,实际上就只看低16位的计算结果,这样就增加了计算结果重复的概率。从而增加了hash冲突。改与以上优化算法,让高16位也参与了进来,就能一定程度上减少这种冲突。

三、HashMap如何解决hash碰撞问题

无论hash算法如何优化,对不同的key算出来的hash值是有可能相同的,这种情况叫hash碰撞或者hash冲突。

两个不同的元素不可能放到数组的同一个位置。HashMap的解决方法是,在这个位置放一个链表,链表里可以存多个元素,将相同hash值的元素都存放到这个链表中。当通过get方法读取数据时,当定位到这个位置发现是个链表,就对这个链表进行遍历查询,找到需要的元素。

链表遍历查询的时间复杂度是O(N),当链表比较长时,也就是hash冲突比较多时,性能比较差。因此HashMap对此做了优化,当达到一定条件时,就会将链表转为红黑树。红黑树遍历查询的时间复杂度是O(logN),性能有很大提升。

在JDK1.8之后,HashMap中的链表在同时满足以下两个条件时,将会转化为红黑树(即自平衡的排序二叉树):

1. 条件一:数组 arr[i] 处存放的链表长度大于8;

2. 条件二:数组长度大于64。

满足以上两个条件,数组 arr[i] 处的链表将自动转化为红黑树,其他位置如 arr[i+1] 处的数组元素仍为链表,不受影响。

四、HashMap如何进行扩容

HashMap底层是数组,当数组满了之后,它就会自动进行扩容,变成一个更大的数组,扩容方式就是2倍扩容,数量直接翻倍。由于数组长度变了,而数组长度是参与hash运算的,因此扩容后需要重新进行hash运算,这就可能会产生内容的变化。比如原来有hash冲突需要产生链表,但re-hash运算后没有冲突了,不需要链表了。或者某个位置的链表里有三个元素,进行re-hash运算后,可能变成了两个。

五、ConcurrentHashMap实现线程安全的底层原理

ConcurrentHashMap是线程安全的HashMap,两者都继承自AbstractMap。在需要线程安全的场合操作HashMap需要使用synchronized关键字加锁,性能很低。而ConcurrentHashMap本身就是线程安全,无需再加synchronized关键字,且已经做了优化,可以直接使用。

ConcurrentHashMap的数据结构与HashMap基本相同,底层都是数组。JDK1.7以前采用的是分段加锁,底层不是一个数组,而是分成多个数组。写数据时内部还是加锁的,但是只对所在段的数组加锁,不同段的操作互不影响,所以·可以并行操作,提高了性能。

JDK1.8以后进一步优化和改进,和HashMap一样使用一个大数组的形式。但对某个元素进行put操作时,使用的是CAS操作,这样如果有多个线程操作这个位置的元素,同一时刻只有一个会成功。因此大多数情况下都是无锁操作,性能很高。只有对于有hash冲突而采用链表+红黑树进行处理的位置进行操作时,ConcurrentHashMap内部才需要对这个位置进行synchronized加锁处理。

到此这篇关于HashMap底层数据结构详细解析的文章就介绍到这了,更多相关HashMap数据结构内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

相关文章

  • Spring Boot利用Thymeleaf发送Email的方法教程

    Spring Boot利用Thymeleaf发送Email的方法教程

    spring Boot默认就是使用thymeleaf模板引擎的,下面这篇文章主要给大家介绍了关于在Spring Boot中利用Thymeleaf发送Email的方法教程,文中通过示例代码介绍的非常详细,对大家具有一定的参考学习价值,需要的朋友们下面来一起看看吧。
    2017-08-08
  • Java OpenSSL生成的RSA公私钥进行数据加解密详细介绍

    Java OpenSSL生成的RSA公私钥进行数据加解密详细介绍

    这篇文章主要介绍了Java OpenSSL生成的RSA公私钥进行数据加解密详细介绍的相关资料,这里提供实例代码及说明具体如何实现,需要的朋友可以参考下
    2016-12-12
  • java 启动exe程序,传递参数和获取参数操作

    java 启动exe程序,传递参数和获取参数操作

    这篇文章主要介绍了java 启动exe程序,传递参数和获取参数操作,具有很好的参考价值,希望对大家有所帮助。一起跟随小编过来看看吧
    2021-01-01
  • 解决kafka消息堆积及分区不均匀的问题

    解决kafka消息堆积及分区不均匀的问题

    这篇文章主要介绍了解决kafka消息堆积及分区不均匀的问题,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教
    2021-09-09
  • Java反射机制详解

    Java反射机制详解

    Java的反射机制是在运行状态中,对于任何一个类,都可以知道这个类的所有属性和方法,对于任何一个对象,都可以调用它所有的方法和属性,修改部分类型信息,这种动态获取信息以及动态调用对象方法的功能称为Java的反射机制
    2022-09-09
  • Java中Jackson的序列化与反序列化详解

    Java中Jackson的序列化与反序列化详解

    这篇文章主要介绍了Java中Jackson的序列化与反序列化详解,Jackson被认为是"Java JSON库"或"Java最好的JSON解析器",Jackson 还是一套用于 Java(和 JVM 平台)的数据处理工具,需要的朋友可以参考下
    2024-01-01
  • 简单的用java实现读/写文本文件的示例

    简单的用java实现读/写文本文件的示例

    同时也展示了如果从输入流中读出来内容写入输出流中(仅限文本流) 三个例子可以独立存在,所以根据需要只看其中一个就行了。
    2008-07-07
  • 使用Idea简单快速搭建springcloud项目的图文教程

    使用Idea简单快速搭建springcloud项目的图文教程

    这篇文章主要介绍了使用Idea简单快速搭建springcloud项目,本文通过图文并茂的形式给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下
    2021-01-01
  • Java获取当前时间并转化为yyyy-MM-dd HH:mm:ss格式的多种方式

    Java获取当前时间并转化为yyyy-MM-dd HH:mm:ss格式的多种方式

    这篇文章主要介绍了Java获取当前时间并转化为yyyy-MM-dd HH:mm:ss格式的多种方式,每种方式结合实例代码给大家介绍的非常详细,感兴趣的朋友跟随小编一起看看吧
    2024-03-03
  • Caused by: java.lang.NumberFormatException: For input string: “port“(问题解决)

    Caused by: java.lang.NumberFormatException: For input s

    这篇文章主要介绍了Caused by: java.lang.NumberFormatException: For input string: “port“,本文给大家分享完美解决方法,需要的朋友可以参考下
    2023-01-01

最新评论