HashMap就是这么简单【源码剖析】

前言

声明,本文用得是jdk1.8

前面已经讲了Collection的总览和剖析List集合以及散列表、Map集合、红黑树的基础了:

本篇主要讲解HashMap,以及涉及到一些与hashtable的比较~

看这篇文章之前最好是有点数据结构的基础:

当然了,如果讲得有错的地方还请大家多多包涵并不吝在评论去指正~

一、HashMap剖析

首先看看HashMap的顶部注释说了些什么:

再来看看HashMap的类继承图:

下面我们来看一下HashMap的属性:

成员属性有这么几个:

再来看一下hashMap的一个内部类Node:

我们知道Hash的底层是散列表,而在Java中散列表的实现是通过数组+链表的~

再来简单看看put方法就可以印证我们的说法了:数组+链表-->散列表

我们可以简单总结出HashMap:

  • 无序,允许为null,非同步
  • 底层由散列表(哈希表)实现
  • 初始容量和装载因子对HashMap影响挺大的,设置小了不好,设置大了也不好

1.1HashMap构造方法

HashMap的构造方法有4个:

在上面的构造方法最后一行,我们会发现调用了tableSizeFor(),我们进去看看:

这是位运算算法,具体流程可参考:

  • https://www.cnblogs.com/loading4/p/6239441.html
  • https://blog.csdn.net/fan2012huan/article/details/51097331

看完上面可能会感到奇怪的是:为啥是将2的整数幂的数赋给threshold

  • threshold这个成员变量是阈值,决定了是否要将散列表再散列。它的值应该是:capacity * load factor才对的。

其实这里仅仅是一个初始化,当创建哈希表的时候,它会重新赋值的:

至于别的构造方法都差不多,这里我就不细讲了:

1.2put方法

put方法可以说是HashMap的核心,我们来看看:

我们来看看它是怎么计算哈希值的:

为什么要这样干呢??我们一般来说直接将key作为哈希值不就好了吗,做异或运算是干嘛用的??

我们看下来:

我们是根据key的哈希值来保存在散列表中的,我们表默认的初始容量是16,要放到散列表中,就是0-15的位置上。也就是tab[i = (n - 1) & hash]。可以发现的是:在做&运算的时候,仅仅是后4位有效~那如果我们key的哈希值高位变化很大,低位变化很小。直接拿过去做&运算,这就会导致计算出来的Hash值相同的很多。

而设计者将key的哈希值的高位也做了运算(与高16位做异或运算,使得在做&运算时,此时的低位实际上是高位与低位的结合),这就增加了随机性,减少了碰撞冲突的可能性!

下面我们再来看看流程是怎么样的:

新值覆盖旧值,返回旧值测试:

接下来我们看看resize()方法,在初始化的时候要调用这个方法,当散列表元素大于capacity * load factor的时候也是调用resize()

1.3get方法

接下来我们看看getNode()是怎么实现的:

1.4remove方法

再来看看removeNode()的实现:

二、HashMap与Hashtable对比

从存储结构和实现来讲基本上都是相同的。它和HashMap的最大的不同是它是线程安全的,另外它不允许key和value为null。Hashtable是个过时的集合类,不建议在新代码中使用,不需要线程安全的场合可以用HashMap替换,需要线程安全的场合可以用ConcurrentHashMap替换

Hashtable具体阅读源码可参考:

  • https://blog.csdn.net/panweiwei1994/article/details/77427010
  • https://blog.csdn.net/panweiwei1994/article/details/77428710

四、总结

在JDK8中HashMap的底层是:数组+链表(散列表)+红黑树

在散列表中有装载因子这么一个属性,当装载因子*初始容量小于散列表元素时,该散列表会再散列,扩容2倍!

装载因子的默认值是0.75,无论是初始大了还是初始小了对我们HashMap的性能都不好

  • 装载因子初始值大了,可以减少散列表再散列(扩容的次数),但同时会导致散列冲突的可能性变大(散列冲突也是耗性能的一个操作,要得操作链表(红黑树)
  • 装载因子初始值小了,可以减小散列冲突的可能性,但同时扩容的次数可能就会变多!

初始容量的默认值是16,它也一样,无论初始大了还是小了,对我们的HashMap都是有影响的:

  • 初始容量过大,那么遍历时我们的速度就会受影响~
  • 初始容量过小,散列表再散列(扩容的次数)可能就变得多,扩容也是一件非常耗费性能的一件事~

从源码上我们可以发现:HashMap并不是直接拿key的哈希值来用的,它会将key的哈希值的高16位进行异或操作,使得我们将元素放入哈希表的时候增加了一定的随机性

还要值得注意的是:并不是桶子上有8位元素的时候它就能变成红黑树,它得同时满足我们的散列表容量大于64才行的~


明天要是无意外的话,可能会写TreeMap,敬请期待哦~~~~

文章的目录导航:https://zhongfucheng.bitcron.com/post/shou-ji/gong-zhong-hao-wen-zhang-zheng-li

原文发布于微信公众号 - Java3y(java3y)

原文发表时间:2018-04-10

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏用户2442861的专栏

Java 8系列之重新认识HashMap

作者:美团点评技术团队 链接:https://zhuanlan.zhihu.com/p/21673805 来源:知乎 著作权归作者所有。商业转载请联系作者...

801
来自专栏QQ音乐技术团队的专栏

Android Native 开发之 NewString 与 NewStringUtf 解析

本文将从一个 Native Crash 分析入手,带大家了解我们平时开发中,那些容易忽略但又很值得学习的底层源码知识。

1.2K9
来自专栏阿凯的Excel

八种方式实现多条件匹配

之前在Excel内部的分享交流群和别的讲师探讨了多条件匹配有哪些实现方式。 围观的市民刘先生表示:我活了二十多年,看见斗图的比较多,这么无聊斗Excel使用技巧...

3014
来自专栏牛客网

微店2016技术岗面试题

第一部分 简答(请选择一个题目解答) 1.请写出C/C++ 语言中的相关问题。 A. 请简述C/C++ 语言中找空间和对空间的主要区别。 B. C++处理异常可...

35512
来自专栏Ryan Miao

Java8-理解Collector

2854
来自专栏趣谈编程

外部排序

当我们要排序的文件太大以至于内存无法一次性装下的时候,这时候我们可以使用外部排序,将数据在外部存储器和内存之间来回交换,以达到排序的目的

1350
来自专栏高爽的专栏

HashMap深度解析(二)

上一篇比较深入的分析了HashMap在put元素时的整体过程,Java Collections Framework中实际操作的都是数组或者链表,而我们通常...

2230
来自专栏积累沉淀

hash算法原理详解

一.概念 哈希表就是一种以 键-值(key-indexed) 存储数据的结构,我们只要输入待查找的值即key,即可查找到其对应的值。 哈希的思路很简单,如果所有...

6265
来自专栏逆向技术

逆向课程第四讲逆向中的优化方式,除法原理,以及除法优化上

           逆向课程第四讲逆向中的优化方式,除法原理,以及除法优化上 除法原理,涉及到了数学公式,而且在汇编中的体现形式也有10几种 这里首先讲解前4...

2338
来自专栏Java爬坑系列

【Java入门提高篇】Day22 Java容器类详解(五)HashMap源码分析(上)

准备了很长时间,终于理清了思路,鼓起勇气,开始介绍本篇的主角——HashMap。说实话,这家伙能说的内容太多了,要是像前面ArrayList那样翻译一下源码,稍...

2245

扫码关注云+社区