摘要:本文结合图解和问题,教你一次性搞定HashMap

本文分享自华为云社区《java中HashMap的设计精妙在哪?用图解和几个问题教你一次性搞定HashMap》,作者:breakDawn。

HashMap核心原理

HashMap完整的put过程

以下是对上图的详细解释:

  1. 首先,要获取key的哈希值。
    如果为空,就统一是0
    否则,调用对象的.hashCode()方法,接着再与自己的右移16位进行异或,以便充分利用高位信息。
  2. 接着判断内部node数组是否为空,如果是,先进行初始化扩容。默认为16。
  3. 根据(n-1)&hash值,获取哈希表索引位置。
  4. 哈希表的node数组中,存放的是每组链表的头节点。
    先检查头节点是否和自己要存放的key完全匹配 (hash值相同,key值相同,先hash再key,是因为hash的判断简单,key的equals判断可能会复杂)。如果匹配,得到需要替换的节点。
  5. 头节点和自己要放的key不匹配,则判断一下这个头节点是否是红黑树节点,如果是,说明已经升级成红黑树了,调用putTree插入到红黑树中。
  6. 如果不是红黑树, 那就是遍历链表,完全匹配就得到需要替换的节点。如果到尾部了,也没匹配的,则插入新节点。
  7. 如果前面找到了要替换的节点,则判断一下是否可以替换(是否没要求putIfAbsent,或者value为null),是就替换,不是就结束
  8. 如果前面是插入了新节点,非替换, 则要modCount++(方便迭代器确认map是否更新), 同时++size, 然后和扩容阈值做判断, 如果太大,就resize进行扩容

hashMap的扩容过程,java7和8扩容的区别

java7:

  • 当resize时,新建一个数组newTable
  • 遍历原table中的每个链表和节点,重新hash,找到新的位置放入
  • 放入的方式是头插法,即始终插在链表的头节点。

java8:

  • 不再每个点rehash放置,而是最高位是0则坐标不变,最高位是1则坐标变为“10000+原坐标”,即“原长度+原坐标. 避免了频繁的哈希计算和搬移过程。
  • 使用尾插法在链表上插入节点
  • 桶内元素超过8个,链表转成红黑树

为什么java8要改成尾插法?

A:多线程时,java7的map-put可能造成死循环。
A线程扩容到那一半, 还处在遍历链表做头插法搬移的过程时,存了2个局部变量,当前链点now指向a, next指向b,正准备搬移(a->b->c这样的链表,a是头节点)

B线程则同时完成线程扩容,但是map里都是引用,浅拷贝,** 因为是头插法, 会导致顺序变化**, 原本a->b->c 变成了c->b->a。
因此A恢复时, 链点还是a,next还是b, 于是往下走到了b, 取bbs的next时,已经变成了a, 于是发生了a->b->a的循环
导致后续操作的next都是错误操作,引发环形指针。

java8里改成尾插法,这样做resize时,a->b->c 如果仍然哈希到同一个节点, 顺序是不会发生变化的。

虽然解决了死循环问题, 但java8的hashMap仍然是线程不安全的,为什么?

A:因为缺乏同步,导致同节点发生哈希碰撞时,if条件的判断都可能是有问题的,导致本该插在链表头节点后面的,结果直接作为链表头覆盖到数组上了。

具体到底满足什么情况,才会resize扩容呢?

A:HashMap负载因子 LoadFactor,默认值为0.75f。
衡量HashMap是否进行Resize的条件如下:
HashMap.Size >= Capacity * LoadFactor

另一种情况。JDK1.8源码中,执行树形化之前,会先检查数组长度,如果长度小于64,则对数组进行扩容,而不是进行树形化

扩容后,capacity扩容多少倍呢?为什么

A:哈希表每次扩容是两倍。
初始长度为2的幂次方,随后以2倍扩容的方式扩容,元素在新表中的位置要么不动,要么有规律的出现在新表中(二的幂次方偏移量),这样会使扩容的效率大大提高。
另外,hashmap采用二倍扩容还有另外一个好处:可以使元素均匀的散布hashmap中,减少hash碰撞。

点击关注,第一时间了解华为云新鲜技术~

java中HashMap的设计精妙在哪?的更多相关文章

  1. 【转】 java中HashMap详解

    原文网址:http://blog.csdn.net/caihaijiang/article/details/6280251 java中HashMap详解 HashMap 和 HashSet 是 Jav ...

  2. java中HashMap详解(转)

    java中HashMap详解 博客分类: JavaSE Java算法JDK编程生活       HashMap 和 HashSet 是 Java Collection Framework 的两个重要成 ...

  3. java集合(2)- java中HashMap详解

    java中HashMap详解 基于哈希表的 Map 接口的实现.此实现提供所有可选的映射操作,并允许使用 null 值和 null 键.(除了非同步和允许使用 null 之外,HashMap 类与 H ...

  4. Java中HashMap遍历的两种方式

    Java中HashMap遍历的两种方式 转]Java中HashMap遍历的两种方式原文地址: http://www.javaweb.cc/language/java/032291.shtml 第一种: ...

  5. Java中HashMap的实现原理

    最近面试中被问及Java中HashMap的原理,瞬间无言以对,因此痛定思痛觉得研究一番. 一.Java中的hashCode和equals 1.关于hashCode hashCode的存在主要是用于查找 ...

  6. JAVA中hashmap的分析

    从http://blog.csdn.net/luanlouis/article/details/41576373?utm_source=tuicool&utm_medium=referral学 ...

  7. JAVA中HashMap相关知识的总结(一)

    Java中HashMap在jdk1.7和jdk1.8中的区别点: 在jdk1.7中是用数组+链表形式存储,1.8采用数组+链表/红黑树形式 Jdk1.8中由链表转为红黑树是长度大于8,由红黑树转为链表 ...

  8. Java中HashMap的数据结构

    类声明: 概述: 线程不安全: <Key, Value>两者都可以为null: 不保证映射的顺序,特别是它不保证该顺序恒久不变: HashMap使用Iterator: HashMap中ha ...

  9. 《转》Java中HashMap详解

    HashMap 和 HashSet 是 Java Collection Framework 的两个重要成员,其中 HashMap 是 Map 接口的常用实现类,HashSet 是 Set 接口的常用实 ...

随机推荐

  1. React报错之Cannot assign to 'current' because it is a read-only property

    正文从这开始~ 总览 当我们用一个null值初始化一个ref,但在其类型中不包括null时,就会发生"Cannot assign to 'current' because it is a r ...

  2. Luogu2420 让我们异或吧 (熟练剖分)

    \(dis[u] \bigoplus dis[v] = dis[u] \bigoplus dis[v] \bigoplus dis[lca\{x,y\}] \bigoplus dis[lca\{x,y ...

  3. Web 前端实战:JQ 实现树形控件

    前言 这是一篇个人练习 Web 前端各种常见的控件.组件的实战系列文章.本篇文章将介绍个人通过 JQuery + 无序列表 + CSS 动画完成一个简易的树形控件. 最终实现的效果是: 这样结构比较复 ...

  4. QQ高级功能

    本篇文章为微信公众号:酿俗 教学内容请跟着小编一起探索吧! 第一步解锁微信豆影藏内容 随后下载需要的材料,注意!手机可能会提示有病毒!这里使用这些功能报病毒很正常 其实并没有病毒只是手机厂商的安全系统 ...

  5. java基础———break,continue

    break通常用在循环语句之中用来跳出循环: continue终止某次循环过程,跳过尚未执行的语句:接着执行下次是否执行循环的判定:

  6. 实时降噪(Real-time Denoising):Spatio-Temporal Filtering

    目录 空间滤波(Spatial Filtering) 基于距离的高斯滤波 双边滤波(Bilateral filtering) 联合双边滤波(Joint Bilateral filtering)[201 ...

  7. JDBC连接池&JDBCTemplate

    今日内容 1. 数据库连接池 2. Spring JDBC : JDBC Template 数据库连接池 1. 概念:其实就是一个容器(集合),存放数据库连接的容器. 当系统初始化好后,容器被创建,容 ...

  8. 安装配置华为Fusion access(Windows AD)桌面云并对接Fusion Compute与Windows_server_2012

    安装Fusion access虚拟机 根据自己情况自定义 点击编辑虚拟机设置 添加镜像 点击确定并开启此虚拟机 选择第二个 Install 添加Network:修改hostname:修改密码 回车添加 ...

  9. 通过Quartz 进行定时任务

    小记一下通过Quartz 进行轮询数据库从而进行自动打印的需求. 一:首先通过NuGet引用Quartz,Quartz依赖Common.Logging和Common.Logging.Log4Net12 ...

  10. 回溯剪枝,dfs,bfs

    dfs: 给定一个整数n,将数字1~n排成一排,将会有很多种排列方法. 现在,请你按照字典序将所有的排列方法输出. 输入格式 共一行,包含一个整数n. 输出格式 按字典序输出所有排列方案,每个方案占一 ...