一文理清HashMap的实现及细节

前言

最近阅读了许多HashMap实现及源码分析的文章,特意此文记录HashMap的知识点。
HashMap 底层由 数组 + 链表 组成,在 jdk1.7 和 1.8 中具体略有不同。

JDK1.7的HashMap

数据结构:图片来源
在这里插入图片描述

核心成员变量

图片来源
在这里插入图片描述

  1. 初始化桶大小(1<<4,即:16),因为底层是数组,所以这是数组默认的大小。
  2. 桶容量最大值。
  3. 默认的负载因子(0.75)
  4. table 真正存放数据的数组。
  5. Map 中存放元素数量。
  6. 桶的容量大小,可在初始化时显式指定。
  7. 负载因子,可在初始化时显式指定。

负载因子

存放的键值对数量(size) = 桶容量(threshold) * 负载因子(loadFactor)时,会发生扩容,而扩容这个过程涉及到 rehash、复制数据等操作,所以非常消耗性能。因此最好提前预估 HashMap 的大小,尽量的减少扩容带来的性能损耗。

Entry

Entry是HashMap的一个内部类,用于保存键值对,实现HashMap中的链表,主要成员变量:

  • key:写入的键。
  • value: 写入的值。
  • next:开始的时候就提到 HashMap 是由数组和链表组成,所以这个 next 就是用于实现链表结构。
  • hash: 存放的是当前 key 的 hashcode。

桶初始大小为16的原因

要解释这个问题,首先要知道这个容量的用途。容量就是一个HashMap中"桶"的个数(数组的大小),当想要往一个HashMap中put一个元素的时候,需要通过一定的算法计算出应该把他放到哪个桶中。HashMap中通过以下两个方法实现计算一个元素对应的桶(数组的索引)

  • int hash(Object k):该方法主要是将Object转换成一个整型。
  • int indexFor(int h, int length):该方法主要是将hash生成的整型转换成链表数组中的下标。jdk1.8没有此方法,不过计算的方式相同。
static int indexFor(int h, int length) {return h & (length-1);
}

在保证length(容量)是2^n 的前提下,h & (length-1)相当于h % (length-1),即用位运算(&)代替取模运算(%)

Java之所有使用位运算(&)来代替取模运算(%),最主要的考虑就是效率。
位运算(&)效率要比代替取模运算(%)高很多,主要原因是位运算直接对内存数据进行操作,不需要转成十进制,因此处理速度非常快。

为什么保证容量为2^n即使用位运算(&)来实现取模运算(%)

总结:因为位运算直接对内存数据进行操作,不需要转成十进制,所以位运算要比取模运算的效率更高,所以HashMap在计算元素要存放在数组中的index的时候,使用位运算代替了取模运算。而等价代替,前提是要求HashMap的容量一定要是2^n

由上述分析,容量只要为2^n即可,HashMap选择16的原因可能是个经验值。

既然一定要设置一个默认的2^n 作为初始值,那么就需要在效率和内存使用上做一个权衡。这个值既不能太小,也不能太大。太小了就有可能频繁发生扩容,影响效率。太大了又浪费空间,不划算。(官方未给出原因)

扩容

由上述分析:HashMap必须保证容量为2^n。因此在扩容时,HashMap会进行成倍的扩容(容量变为原来的2倍)。
扩容的步骤为:

  • 新建数组:创建一个新的Entry空数组,长度是原数组的2倍。
  • 重新计算hash:遍历原Entry数组,把所有的Entry重新Hash到新数组。

Put方法(头插法)

JDK1.7下的put方法添加新元素时使用头插法:即新来的值会成为头节点。

public V put(K key, V value) {//判断当前数组是否需要初始化if (table == EMPTY_TABLE) {inflateTable(threshold);}//如果 key 为空,则 put 一个空值进去。if (key == null)return putForNullKey(value);//计算键值hash值int hash = hash(key);//查找对应的桶的索引int i = indexFor(hash, table.length);//遍历链表for (Entry<K,V> e = table[i]; e != null; e = e.next) {Object k;//遍历判断里面的 hashcode、key 是否和传入 key 相等,//如果相等则进行覆盖,并返回原来的值。if (e.hash == hash && ((k = e.key) == key || key.equals(k))) {V oldValue = e.value;e.value = value;e.recordAccess(this);return oldValue;}}modCount++;//添加新键值对(头插法),会判断是否需要扩容addEntry(hash, key, value, i);return null;
}

头插法的问题

使用头插法,在扩容时会反转链表上元素的顺序。在多线程及需要扩容的条件下,可能出现环形链表,造成死循环。
jdk1.7HashMap出现环路(有个例子,但我感觉不是特别清楚)
在这里插入图片描述

JDK1.8的HashMap

JDK1.7的HashMap在 Hash 冲突严重时,桶上形成的链表会变的越来越长,这样在查询时的效率就会越来越低;时间复杂度为 O(N)。
因此JDK1.8重点解决的此问题。
数据结构:图片来源
在这里插入图片描述

主要区别

  • 新的成员变量 TREEIFY_THRESHOLD 用于判断是否需要将链表转换为红黑树的阈值。链表长度大于等于该值时,会尝试转为红黑树(还需判断数组长度是否大于MIN_TREEIFY_CAPACITY
  • 新的成员变量 UNTREEIFY_THRESHOLD 用于判断是否需要红黑树转为链表的阈值。
  • 用Node代替Entry,在达到红黑树阈值时,将链表转为红黑树提高查询效率。
  • put方法添加新的元素时,由头插法改为尾插法。使用尾插,在扩容时会保持链表元素原本的顺序,就不会出现链表成环的问题。但在 HashMap 扩容的时候会调用 resize() 方法,此时并发操作仍然可能在一个桶上形成环形链表。
    JDK1.8下的HashMap依旧是线程不安全的,只是用尾插法代替头插法解决了JDK1.7时,容易出现环形链表的问题

转为红黑树的条件

默认情况下:链表长度大于 8(TREEIFY_THRESHOLD), 表的长度大于 64(MIN_TREEIFY_CAPACITY) 的时候会转化红黑树。

参考

  • HashMap? ConcurrentHashMap? 相信看完这篇没人能难住你!
  • 《吊打面试官》系列-HashMap
  • HashMap 为什么线程不安全?

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/330122.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

java提高篇之详解内部类

转载自 java提高篇之详解内部类内部类是一个非常有用的特性但又比较难理解使用的特性(鄙人到现在都没有怎么使用过内部类&#xff0c;对内部类也只是略知一二)。第一次见面内部类我们从外面看是非常容易理解的&#xff0c;无非就是在一个类的内部在定义一个类。123456789101112…

rabbitmq-通配符模式

【README】 本文介绍 通配符模式&#xff0c;及代码示例 【1】intro to rabbitmq通配符模式 0&#xff09;通配符模式-交换机类型为 Topic&#xff1b; 1&#xff09;与路由模式相比&#xff0c;相同点是 两者都可以通过 routingkey 把消息转发到不同的队列&#xff1b; 不同…

中海达手部链接电脑安装软件_山东水文局:较大含沙量条件下中海达ADCP外接测深仪测流系统试验成功...

近日&#xff0c;黄委山东水文水资源局与中海达海洋公司联合&#xff0c;在黄河泺口水文站较大含沙量环境下进行了ADCP外接测深仪、GNSS测流试验&#xff0c;试验取得了圆满成功。泺口水文站简介1泺口水文站概况泺口水文站位于山东省济南市天桥区黄河泺口浮桥南岸&#xff0c;隶…

一文搞懂ThreadLocal及相关的内存泄露问题

首先&#xff0c;看一张整体的结构图&#xff0c;来帮助理解 什么是ThreadLocal ThreadLocal用于创建线程局部变量&#xff0c;如果创建一个ThreadLocal变量&#xff0c;那么访问这个变量的每个线程都会有这个变量的一个副本&#xff0c;在实际多线程操作的时候&#xff0c;…

resnet50加入fpn_FPN+SSD同时兼顾速度和精度的检测器(二)

本文首发于知乎专栏“人工智能从入门到逆天杀神”&#xff0c;本文以及本专栏所有算法源代码都可以在神力AI平台获取&#xff0c;如果你没有GPU但需要预训练模型或者你想获取更多开箱即用的AI算法&#xff0c;欢迎加入我们的会员&#xff0c;一杯咖啡即可带你入门AI&#xff0c…

Java秒杀系统实战系列~RabbitMQ死信队列处理超时未支付的订单(转)

转自&#xff1a; https://juejin.cn/post/6844903903130042376 文末有源代码&#xff0c;非常棒 摘要&#xff1a; 本篇博文是“Java秒杀系统实战系列文章”的第十篇&#xff0c;本篇博文我们将采用RabbitMQ的死信队列的方式处理“用户秒杀成功生成订单后&#xff0c;却迟…

主流Java数据库连接池比较及前瞻

转载自 主流Java数据库连接池比较及前瞻主流数据库连接池 常用的主流开源数据库连接池有C3P0、DBCP、Tomcat Jdbc Pool、BoneCP、Druid等 C3p0: 开源的JDBC连接池&#xff0c;实现了数据源和JNDI绑定&#xff0c;支持JDBC3规范和JDBC2的标准扩展。目前使用它的开源项目有Hibern…

有序数组中查找第一个比target大的数

思路&#xff1a;二分法 时间复杂度&#xff1a;O(logn) 空间复杂度&#xff1a;O(1) 代码&#xff1a; public class Solution{public int search(int[] nums, int target){int lf0, rtn.length-1;while(lf<rt){int mid lf(rt-lf)/2;if(n[mid]>t){rt mid-1;}else{lf…

2020最新Java线程池入门(超详细)

转 https://blog.csdn.net/weixin_43893397/article/details/104361154 【1】代码示例 /*** 线程池测试-自定义线程池创建方式* since 2021/03/23 */ public class ThreadPoolMain2 {public static void main(String[] args) throws Exception {newMethod();}public static…

HDFS的诞生

转载自 HDFS的诞生 1牛刀小试 张大胖找了个实习的工作&#xff0c; 第一天上班Bill师傅给他分了个活儿&#xff1a;日志分析。张大胖拿到了师傅给的日志文件&#xff0c;大概有几十兆&#xff0c;打开一看&#xff0c; 每一行都长得差不多&#xff0c;类似这样&#xff1a;212.…

项目背景怎么描述_产品经理写简历,如何让「项目经验」更出众?

项目经验怎么写更出众&#xff1f;时间长但效果一般的项目经验要不要写&#xff1f;没有项目经验怎么办&#xff1f;本文凭借作者自己长期招聘产品、阅读大量简历所积累的经验解答了这三个问题&#xff0c;希望对你有所帮助。产品经理写简历时&#xff0c;都会通过项目案例来证…

一致性Hash算法原理

前言 当在需要将数据分发到多个数据库/缓存&#xff0c;或将请求分发给多个服务节点时&#xff0c;不可避免的会遇到以下问题&#xff1a; 如何将数据均匀的分散到各个节点中&#xff0c;并且尽量的在加减节点时能使受影响的数据最少。 选择节点的方法 随机放置 从多个节点…

转: java多线程-ThreadPoolExecutor的拒绝策略RejectedExecutionHandler

转自&#xff1a; https://blog.csdn.net/qq_25806863/article/details/71172823 概述 原文地址 http://blog.csdn.net/qq_25806863/article/details/71172823 在分析ThreadPoolExecutor的构造参数时&#xff0c;有一个RejectedExecutionHandler参数。 RejectedExecutionH…

已知两点坐标如何快速增加其他坐标_「测绘精选」坐标转换概述

引言&#xff1a;这篇“坐标转换概述”献给各位&#xff0c;可以对坐标转换有一个大致地、整体地了解。文中有些名词是为了便于表达而自创的&#xff0c;大家不用考据、较真。一、静态坐标和动态坐标(1)静态坐标传统大地测量没有考虑板块运动对坐标的影响。虽然板块运动客观存在…

什么是G1垃圾回收算法

转载自 什么是G1垃圾回收算法为解决CMS算法产生空间碎片和其它一系列的问题缺陷&#xff0c;HotSpot提供了另外一种垃圾回收策略&#xff0c;G1&#xff08;Garbage First&#xff09;算法&#xff0c;通过参数 -XX:UseG1GC来启用&#xff0c;该算法在JDK 7u4版本被正式推出&am…

一文理清RocketMQ顺序消费、重复消费、消息丢失问题

前言 在使用消息队列时不可避免的会遇到顺序消费、重复消费、消息丢失三个问题。在一次面试字节的时候&#xff0c;面试官问到如何保证顺序消费&#xff0c;当时回答不太准确&#xff0c;特意此文回顾如何解决顺序消费、重复消费、消息丢失三个问题。 重复消费 解决重复消费…

一道丧心病狂的java面试题

转载自 一道丧心病狂的java面试题无意中了解到如下题目&#xff0c;觉得蛮好。 题目如下&#xff1a; public class TestSync2 implements Runnable {int b 100; synchronized void m1() throws InterruptedException {b 1000;Thread.sleep(500); //6System.out.pri…

水晶报表图形位置_看了我用Excel做的年度报表,老板直夸好

2020年前5个月&#xff0c;最火爆的莫过于口罩。口罩的整条产业链都变得炙手可热&#xff0c;口罩、口罩机、炒熔喷布、聚丙烯等等相关企业的业务数据往往都是去年的几倍。那我们现在作为一家“表姐牌”的口罩厂的员工&#xff0c;老板叫我用Excel做一个既酷炫又简洁的年度报表…

Mysql优化(三):优化order by

MySQL中的两种排序方式 .通过有序索引顺序扫描直接返回有序数据 因为索引的结构是B树&#xff0c;索引中的数据是按照一定顺序进行排列的&#xff0c;所以在排序查询中如果能利用索引&#xff0c;就能避免额外的排序操作。EXPLAIN分析查询时&#xff0c;Extra显示为Using inde…

漫画:什么是服务熔断

转载自 漫画&#xff1a;什么是服务熔断什么是服务熔断&#xff1f;熔断这一概念来源于电子工程中的断路器&#xff08;Circuit Breaker&#xff09;。在互联网系统中&#xff0c;当下游服务因访问压力过大而响应变慢或失败&#xff0c;上游服务为了保护系统整体的可用性&#…