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. Unity热更学习toLua使用--[1]toLua的导入和默认加载执行lua脚本

    [0]toLua的导入 下载toLua资源包,访问GitHub项目地址,点击下载即可. 将文件导入工程目录中: 导入成功之后会出现Lua菜单栏,如未成功生成文件,可以点击Generate All 重新 ...

  2. angualr2+ 性能优化-trackBy

    1.使用trackBy提高性能 为什么使用trackBy进行性能优化,在平时的开发中,我们对数组的处理基本都是通过接口获取新的数组进行替换或push,但是在这个过程中,Angular不知道你是要做什么 ...

  3. .eslintrc.js 文件语法规则定义

    添加某个全局变量: globals: { 'ActiveXObject': true },

  4. uni-app写微信小程序,data字段循环引用

    在写程序过程中,需要使用到 globalData里的内容,而这个全局变量,在uni-app上需要通过: var app=getApp(); app.globalData.xxx=xxx来使用. 我觉得 ...

  5. Vue.js条件渲染与列表渲染指南

    title: Vue.js条件渲染与列表渲染指南 date: 2024/5/26 20:11:49 updated: 2024/5/26 20:11:49 categories: 前端开发 tags: ...

  6. shell脚本的调试

    参数: -n :读一遍脚本中的命令但不执行,用于检查脚本中的语法错误 -v :一边执行脚本,一边将执行过的脚本命令打印到标准错误输出 -x :提供跟踪执行信息,将执行的每一条命令和结果依次打印出来 使 ...

  7. 设置 ASP.NET Core Web API 中响应数据的格式 AddNewtonsoftJson 使用NewtonsoftJson替换掉默认的System.Text.Json序列化组件

    #region 使用NewtonsoftJson替换掉默认的json序列化组件 .AddNewtonsoftJson(options => { 修改属性名称的序列化方式,首字母小写 //opti ...

  8. ASP.NET Core SignalR 概述

    什么是 SignalR? ASP.NET Core SignalR 是一个开放源代码库,可用于简化向应用添加实时 Web 功能. 实时 Web 功能使服务器端代码能够将内容推送到客户端. 适合 Sig ...

  9. MySQL学习笔记-多表查询(下)

    多表查询(下) 一. 联合查询 联合查询:将多次查询结果合并,形成新的查询结果集 select {字段列表} from {表A} ... union [all] select {字段列表} from ...

  10. 搭建高可用k8s

    搭建k8s高可用 高可用只针对于api-server,需要用到nginx + keepalived,nginx提供4层负载,keepalived提供vip(虚拟IP) 系统采用openEuler 22 ...