HashMap在JDK1.8发生的变化
前言
在开始之前,先说一下文章的讲解顺序:首先,说一下HashMap解决冲突的四个办法之其中一种,剩下三种诸位可以自行了解;其次,说明JDK1.8关于HashMap的变化;最后说明发生这样的变化的原因。
本人程序小白,如有纰漏,希望各位朋友指正。
闲话少说,直奔主题!
在截止到JDK1.7,HashMap一直由数组、链表组成。而到了JDK1.8,则由数组、链表或红黑树组成。接下来说一下本文讲解逻辑的一些必备知识点。
必备知识点说明
必备知识点说明中会使用Java底层由数组、链表的常用类来解释提供一个学习Java数据结构的小方向(博主很懒,自己看源码,哈哈哈~)。同时,也会说明一个关于HashMap底层加入红黑树的一个小知识点。
Java中底层使用数组、链表的代表类
数组
在Java集合中,我们所熟知的三个大哥:List、Set、Map,这老三位中底层使用数组实现的就是我们经常使用的ArrayList,Java中的数组大家都知道是使用顺序表的方式实现的,而顺序表的特点就是:查询快,插入和删除慢,至于为什么请看《数据结构》。
链表
同样,在Java集合中,List的其中一个子类与ArrayList完全不同,毕竟龙生九子,子子不同,LinkedList底层实现原理就是链表(PS:毕竟两个儿子都干同一类工作迟早分家……)。链表的实现原理与顺序表不同,它可以理解为动态长度的集合,新来的节点只需要被最后一个节点的指针指向存储单元就连接在一起了(这句话是重点,期末要考)。
阈值
敲黑板,新的知识点。阈值:就是我们常说的临界值,在数学中,可以理解为函数曲线即将转换斜率的位置。而在编程中,我们通常用阈值来描述状态发生某种变化的关键点。(看不明白?没事,后面会解释)。
解决Hash冲突的四种方法(之一)
只讲一种,本文目标不是这个……,撒娇也没用!
单独链表法
简单来说,就是当我们想HashMap中存放数据的时候如果发现地址冲突,那么我们就将当前冲突的存储单元设为头结点,将与原本数据冲突的新加入的数据做为该链表的第二个节点。这就是单独链表法,(图中仅展示了Key存放的方式,不要抬杠还有Value,博主懒得画图……)
这个时候我们会发现一个问题:链表的遍历效率很低,那么当链表长度超过一定数值后,遍历HashMap的时间复杂度将高的离谱。(能提出这个问题的同学晚饭加鸡腿,看到这还不明白时间复杂度为什么会高的同学晚上和老八一桌)。
JDK1.8使用红黑树作为底层实现的原因
原因
上文说到了,如果使用单独链表法解决冲突的时候,链表长度过长,则会引起整个HashMap遍历的时间复杂度。那么为了解决这个问题,我们在某种条件下将解决冲突的链表结构改为红黑树结构,毕竟树形结构是一个非常优秀的结构,很美(为啥优秀不做解释,认识我的人知道这个问题该去问谁……嘿嘿)。那么所谓的某种条件究竟是什么条件?接下来,有请我们的新产品,啊呸,新知识点登场,上文提到过的阈值。
阈值
在HashMap散列的负载因子为默认值——0.75时,单个Hash存储单元中元素的个数为8个的时候(也就是说某一条链表长度为8时)的概率约为0.00000006,四舍五入后大约为百万分之一,也就是说概率很低。不过,在这时我们遍历这条链表的时候已经开始影响时间复杂度了,所以我们将链表转换为红黑树。
其中要注意的是,树型结构的每个节点占用的存储空间一般来说是链表的两倍左右,所以当冲突数据较少时,链表转化为树形结构无法提现遍历效率,所以才会使用阈值这一概念对结构转化时设定一个条件。
同时,我们也可以将红黑树重新转换为链表,即当阈值为6时,结构将再次变为链表。
至于为什么链表——>红黑树时阈值为8;
红黑树——>链表使阈值为6……,其实是为了避免HashMap解决冲突且当阈值为8时,冲突的结构在链表与红黑树之间反复横跳……。
结尾
阈值其实是Java8中HashMap的作者通过大量实验计算出来的数值,大家只需要知道他的概率计算遵从泊松分布就可以了,
OKK,本篇文章仅仅简单阐述了一下HashMap在JDK1.8发生的变化,如果文章有什么纰漏,希望大家在评论区里一起探讨。