zoukankan      html  css  js  c++  java
  • 源码解剖HashMap

     我们将通过以下几个问题进行解答分析:

    (1)HashMap 底层实现原理是什么?JDK8 做了哪些优化?

    (2)HashMap 的工作原理?HashMap中链表的作用?

    (3)为什么要添加红黑树?

    (4)加载因子是什么?为什么是0.75?

    (5)HashMap 是如何导致死循环的?

    HashMap 底层实现原理是什么?JDK8 做了哪些优化?

    在 JDK 1.7 中 HashMap 是以数组加链表的形式组成的,JDK 1.8 之后新增了红黑树的组成结构,当链表长度大于 8 时并且容量大于 64 时,链表结构会转换成红黑树结构。

    组成结构如下图:

    数组中的元素我们称之为哈希桶,每个哈希桶中包含了四个字段:hash、key、value、next,其中 next 表示链表的下一个节点。它的定义如下:

     

    HashMap 的工作原理?

    HashMap 底层是 hash 数组和单向链表实现,数组中的每个元素都是链表,由 Node 内部类(实现 Map.Entry接口)实现,HashMap 通过 put & get 方法存储和获取。

    存储对象时,将 K/V 键值传给 put() 方法:

    ①、调用 hash(K) 方法计算 K 的 hash 值,然后结合数组长度,计算得数组下标;

    ②、调整数组大小(当容器中的元素个数大于 capacity * loadfactor 时,容器会进行扩容resize 为 2n);

    ③、i.如果 K 的 hash 值在 HashMap 中不存在,则执行插入;

    ii.如果 K 的 hash 值在 HashMap 中存在(发生碰撞),且它们两者 equals 返回 true,则更新键值对;

    iii. 如果 K 的 hash 值在 HashMap 中存在(发生碰撞),且它们两者 equals 返回 false,则插入链表的尾部(JDK 1.7 之前使用头插法、JDK 1.8 使用尾插法)或者红黑树中。

    获取对象时,将 K 传给 get() 方法:

    ①、计算 K 的 hash 值从而获取该键值所在链表的数组下标;

    ②、顺序遍历链表,equals()方法查找相同 Node 链表中 K 值对应的 V 值。

    hashCode 是定位的,存储位置;equals是定性的,比较两者是否相等。

    HashMap中链表的作用?

    从HashMap的工作原理我们可知,HashMap是利用“拉链法”处理Hash的碰撞问题。

    补充:Hash碰撞冲突可以使用开放地址法、再哈希法、链地址法(拉链法)、建立一个公共溢出区、等方法解决。

    为什么要添加红黑树?为什么链表大于 8才转?

    详细介绍请查阅我的另一篇文章 -- 链表转红黑树的原因?为什么阈值为8?

    HashMap源码分析

    主流的JDK版本1.8为例

     

     加载因子是什么?为什么是0.75?

    加载因子也叫扩容因子或负载因子,用来判断什么时候进行扩容的,假如加载因子是0.5,HashMap的初始化容量是16,那么当HashMap中有16*0.5=8个元素时,HashMap就会进行扩容。

    那加载因子为什么是 0.75 而不是 0.5 或者 1.0 呢?

    这其实是出于容量和性能之间平衡的结果:

    (1)当加载因子设置比较大的时候,扩容的门槛就被提高了,扩容发生的频率比较低,占用的空间会比较小,但此时发生Hash冲突的几率就会提升,因此需要更复杂的数据结构来存储元素,这样对元素的操作时间就会增加,运行效率也会因此降低;

    (2)而当加载因子值比较小的时候,扩容的门槛会比较低,因此会占用更多的空间,此时元素的存储就比较稀疏,发生哈希冲突的可能性就比较小,因此操作性能会比较高。

    所以综合了以上情况就取了一个 0.5 到 1.0 的平均数 0.75 作为加载因子。

    HashMap 是如何导致死循环的?

    发生死循环的原因是JDK1.7链表插入方式为首部倒序插入,这个问题在JDK1.8得到了改善,变成了尾部正序插入。

    有人曾经把这个问题反馈给了 Sun 公司,但 Sun 公司认为这不是一个问题,因为 HashMap 本身就是非线程安全的,如果要在多线程下,建议使用 ConcurrentHashMap 替代,但这个问题在面试中被问到的几率依然很大,所以在这里需要特别说明一下。.

    希望本文章对您有帮助,您的转发、点赞是我的创作动力,十分感谢。更多好文推荐,请关注我的微信公众号--JustJavaIt
  • 相关阅读:
    常用安卓开发技巧汇总
    安卓开发30:AsyncTask的用法
    JBoss AS7 快速配置
    抓包 把笔记本改造成无线路由器 —— 手机抓包牛刀小试
    Android系统手机端抓包方法
    ApkTool反编译apk,去除广告或者汉化后重新打包apk,并签名
    Android中如何像 360 一样优雅的杀死后台Service而不启动
    android service 的各种用法(IPC、AIDL)
    Android shell 下 busybox,clear,tcpdump、、众多命令的移植
    Servlet3.0中Servlet的使用
  • 原文地址:https://www.cnblogs.com/liaowenhui/p/15049401.html
Copyright © 2011-2022 走看看