最近一直在忙于项目开发的事情,没有时间去学习一些新知识,但用忙里偷闲的时间把jdk8的hashMap源码看完了,也做了详细的笔记,我会把一些重要知识点分享给大家。大家都知道,HashMap类型也是面试题百分之九十的概率会考到的一种类。如果有人想要看我在源码中的详情笔记的话,可以去我的gitHub上面去下载看(https://github.com/javJoker/jdk8/blob/master/src/main/java/java/util/HashMap.java)。知识在于分享,进步在于学习和讨论,各取所长,这样我们才能更加高效率的学习,这样我们才能走的更远。如果有什么写的不对的地方,也希望大家指出来。在看源码中我也遇到了一些不懂的地方,如果有人知道的话也希望解扰我的困惑。

1.HashMap的key、value可以null,并且它也不是线程安全的,它也是无序的,这些要点在hashMap源码的类头上注释部分都可以知道的。那为什么HashMap的key、value可以为null存储到HashMap中去呢?因为在hashMap的put方法没有对这两个进行null值得校验。下面图一为hashMap的putVal方法,其实我们使用的put方法底层调用的就是putVal方法,图二是ConCurrentHashMap的putVal,它会先进行null的校验,key和value为空的时候会跑出异常NullPointerException。图一为:HashMap的putVal方法,图二为:ConcurrentHashMap的putVal方法。

图一

图二

我也整理Map几种子类型中哪些类的key、value可以为null,哪些不可以为null。如下:

2.通过key值获取HashMap中的value的值,hashMap的key和穿过来的参数是通过hashCode值和equals方法比较的,如果你需要声明一个你自己的对象作为hashMap的key的话,需要重写作为key对象的生成hashCode值和equals的方法,这样我们才能通过get方法获取你想要的value的值。说到这里HashMap生成HashCode的方法太机智了,使用2的幂次掩码仅在当前掩码上方的位中变化的哈希集将始终发生冲突,它将哈希的较高位(XOR)扩展为较低,向下传播较高位的影响。这样减少哈希值的碰撞次数,均匀分布在桶里面。

3.我们着重讲hashMap的putVal方法,HashMap的put方法底层调用的是putVal方法,因为许多知识都会在这个方法中展现出来,如hashMap什么时候扩容?什么时候树化(HashMap其实就是Node的节点数组和链表或者红黑树的形式构成的,Node数组我们成为桶,数组中每个节点称为桶槽。树化这个词可以理解为将链表转化为树,链化可以认为是树转化为链表的形式)?注意JDK8之后put的新值是在链表尾部进行插入的,JDK7是在头部进行插入,在此点上面两者不同,至于为什么吗?曾看到过一篇文章说尾部插入可以防止引用链式循环,我也没有明白个所以然来,如果有谁知道的可以留言给我。下面这个图在网上找的,方便大家理解HashMap的结构是怎样的的。

(1)我整理一下hashMap的put方法的机制

a.判断table是否初始化了并且length是否为零,如果未进行初始化,会去调用resize()方法进行初始化大小,用户没有手动设置容器大小(cap)会去使用默认值为16,threshold(是下次需要调整大小的值)为 cap * DEFAULT_INITIAL_CAPACITY = 16 * 0.75 = 12,threshold是和hashMap的size进行比较大小,如果size大于threshold的值就会调用resize()方法进行扩容。resize()之后map的容量大小是原来的2倍(除了初始化的时候),原来的node节点位置可能发生变化,这里需要重新计算index的下标。我们都知道每个node的index值是根据hashCode与容量大小进行取模得到的,这里实在佩服那些大佬想出来的方法,扩容之后不需要重新计算hash值,而是只需要当前节点的hash值与扩容前的容量大小的二进制进行与运算就可以了,因为扩容的容量是原来的2倍,也就是左移一位增加一位数(二进制的时候),扩容之后就是在原来的基础上多一位数,新添加的这位数判断是0还是1就可以决定在新数组上面的位置了,0表示原位置,1表示在原来的位置上加上一个原来数组的长度(原索引+oldCap)。发生HashMap扩容后,遍历hashmap每个桶里的链表,每个链表可能会被拆分成两个链表,不需要移动的元素置入loHead为首的链表,需要移动的元素置入hiHead为首的链表,loHead在新table的原来的index桶槽里面,hiHead则放在新tab的(原索引+oldCap)的桶槽中。如下图:

注意如果节点类型Node和TreeNode两者的计算方式有些不同,如果是Node类型的话,说明为链表形式,但是如果TreeNode类型化是树形的,那么就会调用((TreeNode<K,V>)e).split(this, newTab, j, oldCap);这个方法,也是当前桶槽下面遍历所有的树形节点,不需要移动的元素置入loHead
为首的链表,需要移动的元素置入hiHead为首的链表,计算两个链表的node大小,如果小于等于UNTREEIFY_THRESHOLD(值为6)就将树转化为链表,且类型从TreeNode转化为Node类型,如果大于阀值的话需要进行树化。

这个treeify()方法中有一个知识点需要说明一下,treeify()方法中调用了moveRootToFront(tab, root)里面是将root移动tab数组index位置插入到第一个位置,原来tab数组index位置放到root的next的位置中,而不是把需要插入index的root和tab中index位置的原来的数据一起进行
树化重排序之后插入到index的位置上面。

b.计算当前插入值得key的hashCode值(hash(Object key)),插入值得时候分一下情况:

1)如果在hash值在tab的桶槽中没有节点,直接插入就可以了

2)如果有节点话,又分情况,返回值为key对应的旧值(e为旧值):

aa.此桶槽中的第一个节点key相等的时候,判断条件p.hash == hash &&((k = p.key) == key || (key != null && key.equals(k)),这里先比较比较hash值是否相等,再进行比较key((k = p.key) == key判断key是不是指向同一块内存, (key != null && key.equals(k)是key不指向同一块内存key不为空的时候判断值是不是相等),为True的时候把这个节点复制给e返回去

ab.第一个节点的key不相等的时候,判断这个节点类型是TreeNode的时候,通过putTreeVal方法将此节点转化树节点插入树中, 如果有key相等的树节点,覆盖value并返回旧的树节点,否则返回null,putTreeVal()方法有一个细节,key不仅通过了

((pk = p.key) == k || (k != null && k.equals(pk)))判断是否相等,还判断key是否实现了Comparable比较方法,实现了还用这个方法判断key的大小。

ac.第一个节点的key不相等的时候,这个节点类型是Node的时候,遍历此节点的下个节点,如果有key相等的节点则赋值给e,没有的话就插入到末尾,并且判断桶槽节点个数是否大于树化阀值8(TREEIFY_THRESHOLD),大于8(其实是第九个节点进行树化,下标从0开始,新添加的节点后此次循环中计算器未加一)进行树化treeifyBin方法,下图一。注意treeifyBin此方法中table的length小于64(MIN_TREEIFY_CAPACITY,下图二)的时候只是进行了resize(),没有进行树化,当大于等于64的时候才把node节点转化为TreeNode === 》TreeNode<K,V> p = replacementTreeNode(e, null),再进行treeify树化。

图一

图二

3)e不为空的时候,说明有key相等的节点,覆盖旧值并返回

c.执行下面的步骤说明e为空的时候,没有相等的节点,就是把这个key、value当做新增到map中去,modCount、size自增,并判断size > threshold为true就resize()大小,并返回为null

HashMap先讲到这里,还有一些内容没有介绍,我会出第二篇关于HashMap。我怕写的内容太多了,大家不容易消化掉。大家先把上面的知识点消化消化吧,我认为上面写的每一个都是知识点。如果你去面试的话,上面每个点都可能作为考点被问到,即使没有被问到,你也可以在面试到hashMap的时候,自己进行扩展,可能面试官自己都不知道,比如说大部分的人都知道转化为链表和转化为树的阀值为6和8,但是有谁知道64这个数字呢,hashMap的size不大于等于64,桶槽下面的链表不会进行树化,只是进行扩容而已,链表转化为树的阀值是8,其实是节点为第9个的时候才会调用树化的方法。

												

JDK8源码解析 -- HashMap(一)的更多相关文章

  1. JDK8源码解析 -- HashMap(二)

    在上一篇JDK8源码解析 -- HashMap(一)的博客中关于HashMap的重要知识点已经讲了差不多了,还有一些内容我会在今天这篇博客中说说,同时我也会把一些我不懂的问题抛出来,希望看到我这篇博客 ...

  2. JDK源码解析---HashMap源码解析

    HashMap简介 HashMap是基于哈希表实现的,每一个元素是一个key-value对,其内部通过单链表解决冲突问题,容量不足(超过了阀值)时,同样会自动增长. HashMap是非线程安全的,只是 ...

  3. [源码解析]HashMap和HashTable的区别(源码分析解读)

    前言: 又是一个大好的周末, 可惜今天起来有点晚, 扒开HashMap和HashTable, 看看他们到底有什么区别吧. 先来一段比较拗口的定义: Hashtable 的实例有两个参数影响其性能:初始 ...

  4. OpenJDK1.8.0 源码解析————HashMap的实现(二)

    上一篇文章介绍了HashMap的一部分的知识,算是为下面HashMap的进一步学习做准备吧. 然后写的时候一直在思考的一个问题是,这方面的知识网上的资料也是一抓一大把,即使是这样我为什么还要花费时间去 ...

  5. OpenJDK1.8.0 源码解析————HashMap的实现(一)

    HashMap是Java Collection Framework 的重要成员之一.HashMap是基于哈希表的 Map 接口的实现,此实现提供所有可选的映射操作,映射是以键值对的形式映射:key-v ...

  6. Java源码解析|HashMap的前世今生

    HashMap的前世今生 Java8在Java7的基础上,做了一些改进和优化. 底层数据结构和实现方法上,HashMap几乎重写了一套 所有的集合都新增了函数式的方法,比如说forEach,也新增了很 ...

  7. JDK8源码解析 --- Long 类型

    最近都在看JDK8的源码,想把记录下来与大家一起共享,每天 积累一点,每天成长一点.看了装箱Long类型,有好多以前没有注意到或者不知道的内容,慢慢懂得.废话不多说,直接上代码讲解... 1.缓存区L ...

  8. JDK8集合类源码解析 - HashMap

    java为数据结构中的映射定义了一个接口java.util.Map,此接口主要有四个常用的实现类,分别是HashMap.Hashtable.LinkedHashMap和TreeMap HashMap ...

  9. JDK8集合类源码解析 - HashSet

    HashSet 特点:不允许放入重复元素 查看源码,发现HashSet是基于HashMap来实现的,对HashMap做了一次“封装”. private transient HashMap<E,O ...

随机推荐

  1. 基础学习笔记之opencv(3):haartraining生成.xml文件过程[转]

    1.准备正负样本: 在上一讲http://www.cnblogs.com/tornadomeet/archive/2012/03/27/2420088.html 中,我们已经收集到了训练所用的正样本. ...

  2. https://pingcap.com/blog-cn/flame-graph/

    https://pingcap.com/blog-cn/flame-graph/ 因为 TiKV 是自己内部使用了 jemalloc,并没有用系统的 malloc,所以我们不能直接用 perf 来探查 ...

  3. blaze advisor模型部署工具

    python信用评分卡建模(附代码,博主录制) https://study.163.com/course/introduction.htm?courseId=1005214003&utm_ca ...

  4. linux内核中i2c驱动中slave模式接口的调用

    1. 关注unreg_slave接口 1.1 这个接口在哪里被调用呢? 在drivers/i2c/i2c-core-slave.c中 int i2c_slave_unregister(struct i ...

  5. Docs-.NET-C#-指南-语言参考-关键字-值类型:内置数值转换

    ylbtech-Docs-.NET-C#-指南-语言参考-关键字-值类型:内置数值转换 1.返回顶部 1. 内置数值转换(C# 参考) 2019/10/22 C# 提供了一组整型和浮点数值类型. 任何 ...

  6. 软件定义网络基础---SDN控制平面

    一:SDN控制平面 一个或多个SDN控制器组成,是网络的大脑.  对底层网络交换设备进行集中管理,状态监测.转发决策以及处理和调 度数据平面的流量:  通过北向接口向上层应用开放多个层次的可编程能 ...

  7. C语言中结构体的构造函数

    示例代码: #include <iostream> using namespace std; struct Node { int x, y, z; Node(int _x, int _y, ...

  8. ng2中 如何使用自定义属性data-id 以及赋值和取值操作

    项目环境:ng4.x 写法说明: [attr.data-nurseKey] <div [attr.data-nurseKey]="k.nurseKey"></di ...

  9. Cas(09)——通过Proxy访问其它Cas应用

    通过Proxy访问其它Cas应用 目录 1.1     原理 1.2     配置 1.2.1    代理端 1.2.2    被代理端 1.3     请求示例 考虑这样一种场景:有两个应用App1 ...

  10. selenium IDE下载安装(For Chrome and firefox)

    安装好Firefox/cheome之后,接下来就到了正式安装Selenuim IDE的时候了. 步骤一:下载Selenuim IDE       方法一:之前从网上查到很多安装教程,都是从http:/ ...