1.底层结构

Java 7及之前版本

在Java 7及之前的版本中,HashMap的底层数据结构主要是数组加链表。具体实现如下:

  1. 数组:HashMap的核心是一个Entry数组(Entry<K,V>[] table),这个数组的大小总是2的幂。每个数组元素是一个单一的Entry节点,或者是一个链表的头节点。

  1. 链表:当两个或更多的键经过哈希运算后映射到数组的同一个索引上时,就会形成链表。Entry节点包含了键值对以及指向下一个Entry的引用,以此来解决哈希冲突。

Java 8及之后版本

从Java 8开始,HashMap的底层结构除了数组加链表之外,还引入了红黑树,以优化在链表过长时的查找性能。结构变为数组加链表加红黑树

  1. 数组:同样是一个Entry数组(Java 8中称为Node),大小仍然是2的幂,用于快速定位。
  2. 链表:在哈希冲突时,键值对仍会以链表形式链接在一起。但与Java 7不同的是,Java 8对链表的处理增加了转换为红黑树的条件。
  3. 红黑树:当一个桶中的链表长度超过8且HashMap的容量大于64时(不大于64会先对数组进行扩容resize()),链表会被转换成红黑树。这种转换提高了在大量哈希冲突情况下的查找效率,因为红黑树的查找时间复杂度为O(log n),相较于链表的O(n)有显著提升。

2.数据插入

在JDK1.7的时候,采用的是头插法

在JDK1.8改成了尾插法,这是因为头插法在多线程环境下扩容时可能会产生循环链表问题

线程不安全

无论是JDK1.7还是1.8都是线程不安全的,下图是1.8中的put方法

tab是就是HashMap的数组table,第一个if判断时做了赋值。框起来的部分表示如果没有hash冲突就直接在数组上插入元素,但是如果两个线程hash一样且都进入到了这个if下,线程1先执行的插入数据,线程2会覆盖1插入的数据。

那么什么是循环链表问题呢?这就不得不介绍一下HashMap的扩容机制了。

3.扩容机制

首先讲几个HashMap的属性

  • table:数组,存放链表或红黑树的头节点
  • Node:节点,属性有hash、key、value、next(下一个节点)
  • size:元素个数,即节点node个数,非数组长度
  • Capacity:当前数组长度
  • loadFactor:加载因子,默认为0.75f,简称loadFactor
  • threshold:扩容门槛,值为capacity*loadFactor,size达到这个门槛就扩容

当size大于threshold时,就会进行扩容resize()

扩容分为两步

  1. 创建一个新的数组,长度为原数组的两倍
  2. 遍历所有Node节点,重新计算index值(Java8首先会重新计算hash值),放到新数组里,存在hash冲突的就放到链表或红黑树

为什么要重新计算index值,直接把张三这条链表复制到新的数组中不行吗?

答案是不行的,因为index规则是根据数组长度来的,如图

所以index 的计算公式是这样的:

  • index = HashCode(key) & (Length - 1)

4.循环链表问题

循环链表问题理解起来比较麻烦,如果理解不了就知道JDK1.7HashMap扩容的时候有这么回事就行。但是可能是我自己太笨了万一大家一看就懂了呢

我们来看一下Java1.7扩容的源码

void resize(int newCapacity) {
Entry[] oldTable = table;
int oldCapacity = oldTable.length;
if (oldCapacity == MAXIMUM_CAPACITY) {
threshold = Integer.MAX_VALUE;
return;
} Entry[] newTable = new Entry[newCapacity];
transfer(newTable);
table = newTable;
threshold = (int)(newCapacity * loadFactor);
} void transfer(Entry[] newTable) {
Entry[] src = table;
int newCapacity = newTable.length;
for (int j = 0; j < src.length; j++) {
Entry<K,V> e = src[j];
if (e != null) {
src[j] = null;
do {
Entry<K,V> next = e.next;
//重新计算元素在数组中的索引
int i = indexFor(e.hash, newCapacity);
e.next = newTable[i];
newTable[i] = e;
e = next;
} while (e != null);
}
}
}

重点在于transfer方法,作用是重新计算index值然后将旧数组中的数据迁移到新数组

循环链表的产生:

原因:

假设我们有两个Thread都在执行resize方法,Thread1第一步刚执行完第23行Entry<K,V> next = e.next;就卡住了,这时Thread2执行完了resize方法。

过程:

  1. Thread1第一次执行完Entry<K,V> next = e.next后,e=张三,next=李四,也就是说第二步执行李四的插入
  2. Thread2执行完resize后,李四的next变成了张三
  3. 此时又回到Thread1,第二次执行Entry<K,V> next = e.next后,e=李四,next=张三,也就是说又要执行张三的插入,循环链表产生!

由此我们知道了循环链表产生的关键在于头部插入元素A时,元素A的next元素B的next是元素A本身,所以Java8采用了尾插法,避免了循环链表。

求赞!求关注!!以后会更新更多有用的内容!!!꒰⑅•ᴗ•⑅꒱

保佑大家代码永无bug ╰(´︶`)╯

Java进阶:HashMap底层原理(通俗易懂篇)的更多相关文章

  1. HashMap底层原理分析(put、get方法)

    1.HashMap底层原理分析(put.get方法) HashMap底层是通过数组加链表的结构来实现的.HashMap通过计算key的hashCode来计算hash值,只要hashCode一样,那ha ...

  2. 最简单的HashMap底层原理介绍

    HashMap 底层原理  1.HashMap底层概述 2.JDK1.7实现方式 3.JDK1.8实现方式 4.关键名词 5.相关问题 1.HashMap底层概述 在JDK1.7中HashMap采用的 ...

  3. [转]java 的HashMap底层数据结构

    java 的HashMap底层数据结构   HashMap也是我们使用非常多的Collection,它是基于哈希表的 Map 接口的实现,以key-value的形式存在.在HashMap中,key-v ...

  4. 面试官再问你 HashMap 底层原理,就把这篇文章甩给他看

    前言 HashMap 源码和底层原理在现在面试中是必问的.因此,我们非常有必要搞清楚它的底层实现和思想,才能在面试中对答如流,跟面试官大战三百回合.文章较长,介绍了很多原理性的问题,希望对你有所帮助~ ...

  5. 详解 Java 8 HashMap 实现原理

    HashMap 是 Java 开发过程中常用的工具类之一,也是面试过程中常问的内容,此篇文件通过作者自己的理解和网上众多资料对其进行一个解析.作者本地的 JDK 版本为 64 位的 1.8.0_171 ...

  6. Java面试& HashMap实现原理分析

    1. HashMap的数据结构 数据结构中有数组和链表来实现对数据的存储,但这两者基本上是两个极端.  数组 数组存储区间是连续的,占用内存严重,故空间复杂的很大.但数组的二分查找时间复杂度小,为O( ...

  7. hashMap 底层原理+LinkedHashMap 底层原理+常见面试题

    1.源码 java1.7    hashMap 底层实现是数组+链表 java1.8 对上面进行优化  数组+链表+红黑树 2.hashmap  是怎么保存数据的. 在hashmap 中有这样一个结构 ...

  8. 【JAVA】HashMap的原理及多线程下死循环的原因

    再次翻到以前工作中遇到的一个问题,HashMap在多线程下会出现死循环的问题,以前只是知道会死循环,导致CPU100%把机器拖跨,今天来彻底看看 首先来看下,HashMap的原理:HashMap是一个 ...

  9. HashMap底层原理

    原文出自:http://zhangshixi.iteye.com/blog/672697 1.    HashMap概述: HashMap是基于哈希表的Map接口的非同步实现.此实现提供所有可选的映射 ...

  10. 1.Java集合-HashMap实现原理及源码分析

    哈希表(Hash  Table)也叫散列表,是一种非常重要的数据结构,应用场景及其丰富,许多缓存技术(比如memcached)的核心其实就是在内存中维护一张大的哈希表,而HashMap的实现原理也常常 ...

随机推荐

  1. Linux基础-02:Linux目录操作命令

    Linux中目前可以识别的命令有上万条,如果没有分类,那么学习起来一定痛苦不堪. 所以我们把命令分门别类,主要是为了方便学习和记忆. 下面我们先来学习最为常用的和目录相关的操作命令 最近无意间获得一份 ...

  2. golang向上取整、向下取整和四舍五入

    一.概述 官方的math 包中提供了取整的方法,向上取整math.Ceil() ,向下取整math.Floor() 二.用法 package main import ( "fmt" ...

  3. rails 写入日志函数

    json_object={ "ip"=> "127.0.0.1", "ports"=> '80,135', "data ...

  4. $KMP$学习记

    <不浪漫罪名>--王杰 没有花 这刹那被破坏吗 无野火都会温暖吗 无烟花一起庆祝好吗 若爱恋 仿似戏剧那样假 如布景一切都美化 连相拥都参照主角吗 你说我未能定时 令你每天欢笑一次 我没说 ...

  5. 初识上位机(下):C#读写PLC数据块数据

    大家好,我是Edison. 作为一个工业自动化领域的程序员,不懂点PLC和上位机,貌似有点说不过去.这里我用两篇小文带你快速进入上位机开发领域.后续,我会考虑再出一个系列文章一起玩工控上位机. 上一篇 ...

  6. redis持久化存储数据(rdb和aof)

    rdb持久化存储数据 总的 redis持久化 防止数据丢失,持久化到本地,以文件形式保存 持久化的方式 ,两种 aof和 rdb模式 1.触发机制, - 手动执行save命令 - 或者配置触发条件 s ...

  7. Pytorch:以单通道(灰度图)加载图片

    以单通道(灰度图)加载图片 如果我们想以单通道加载图片,设置加载数据集时的transform参数如下即可: from torchvision import datasets, transforms t ...

  8. sass的几种输出格式,你都知道吗

    输出格式说明 Sass编译输出的CSS格式可以自定义. 有4种输出格式: :nested – 嵌套格式 :expanded – 展开格式 :compact – 紧凑格式 :compressed – 压 ...

  9. 鸿蒙HarmonyOS实战-Stage模型(信息传递载体Want)

    前言 应用中的信息传递是为了实现各种功能和交互.信息传递可以帮助用户和应用之间进行有效的沟通和交流.通过信息传递,应用可以向用户传递重要的消息.通知和提示,以提供及时的反馈和指导.同时,用户也可以通过 ...

  10. grafan+cadvisor+prometheus监控docker

    grafan+cadvisor+prometheus监控docker: 运行cadvisor: docker run \ --volume=/:/rootfs:ro \ --volume=/var/r ...