HashMap源码中的成员变量你还不懂? 来来来!!!整理好的成员变量源码解析

前言

点赞在看,养成习惯。java

点赞收藏,人生辉煌。web

点击关注【微信搜索公众号:编程背锅侠】,防止迷路。编程

HashMap集合简介

概述

HashMap基于哈希表的Map接⼝口实现,是以key-value存储形式存在,即主要⽤用来存放键值对。它的key、value均可觉得null。 HashMap 的实现不是同步的,这意味着它不是线程安全的。此外, HashMap中的映射不是有序的,位置由hashcode通过运算决定。数组

数据结构
  • [ ] 在JDK1.8 以前 HashMap 由 数组+ 链表 数据结构组成的。
  • [x] 在JDK1.8 以后 HashMap 由 数组+链表 +红⿊树数据结构组成的。
数据结构解析

JDK1.8 以前 HashMap 由数组+链表组成的,数组是 HashMap 的主体,链表则是主要为了了解决哈希冲突**(两个对象调用的hashCode⽅法计算的哈希码值一致而致使计算的数组索引值相同)而存在的(“拉链法”解决冲突).JDK1.8 之后在解决哈希冲突时有了较大的变化,当链表长度大于阈值(或者红黑树的边界值,默认为 8)而且当前数组的长度大于64**时【同时知足这两个条件】,此时此索引位置上的全部数据改成使用红⿊树存储。缓存

补充:将链表转换成红黑树前会判断,即便阈值大于8,可是数组长度小于64,此时并不会将链表变为 红黑树。⽽是选择进行数组扩容。安全

为何要知足以上两个条件?

这样作的目的是由于数组比较小的时候要尽量避开红黑树结构,这种状况下变为红黑树结构,反而会下降效率,由于红⿊树须要进行左旋,右旋,变色这些操做来保持平衡 。同时数组⻓度⼩于64时,搜索时间相对要快些。因此综上所述为了提升性能和减小搜索时间,底层在阈值大于8而且数组长度大于64时, 链表才转换为红黑树。具体能够参考 treeifyBin 方法。微信

固然虽然增了了红黑树做为底层数据结构,结构变得复杂了,可是阈值大于8而且数组长度大于64时,链表转换为红黑树时,效率也变的更⾼效。数据结构

特色分析
  • 存取是无序的。
  • 键和值都均可以是null,可是这些键中仅只能有一个是null。
  • 键位置是惟一的,底层的数据结构控制键的位置。
  • jdk1.8以前数据结构是:链表 + 数组, jdk1.8以后是 : 链表 + 数组 + 红黑树。
  • 阈值(边界值) > 8 而且数组长度大于64,才将链表转换为红黑树,变为红黑树的⽬的是为了高效的查询。

HashMap继承关系

HashMap继承关系源码
public class HashMap<K,V> extends AbstractMap<K,V>  implements Map<K,V>, Cloneable, Serializable {  // 省略 } 复制代码
HashMap的继承图解

继承图解析
  • [x] Cloneable是空接口,表示能够克隆隆。 表明建立并返回HashMap对象的一个副本。
  • [x] Serializable 序列化接口。属于标记性接口。HashMap对象能够被序列化和反序列化。
  • [x] AbstractMap 父类提供了Map实现接口。以最大限度地减小实现此接口所需的⼯做。
奇怪问题

补充:经过上述继承关系咱们发现一个很奇怪的现象, 就是HashMap已经继承了AbstractMap而 AbstractMap类实现了Map接⼝口,那为什什么HashMap还要在实现Map接⼝口呢?一样在ArrayList中 LinkedList中都是这种结构。框架

小小插曲

据 Java集合框架的创始⼈人Josh Bloch描述,这样的写法是⼀个失误。在java集合框架中,相似这样的写法不少,最开始写Java集合框架的时候,他认为这样写,在某些地方多是有价值的,直到他意识到搞错了。显然的,JDK的维护者,后来不认为这个⼩小的失误值得去修改,因此就这样存在下来了。编辑器

HashMap成员变量

序列化版本号
private static final long serialVersionUID = 362498820763181265L;
复制代码
集合的初始化容量【16】( 必须是二的n次幂 )
// 默认的初始容量是16 -- 1<<4至关于1*2的4次方---1*16
static final int DEFAULT_INITIAL_CAPACITY = 1 << 4; 复制代码

1 << 4解析,就是讲1对应的二进制左移4位。0000 0001【1】左移4位为0001 0000【16】。

集合的最大容量【2的30次方】
// 集合最大容量的上限是:2的30次幂
static final int MAXIMUM_CAPACITY = 1 << 30; 复制代码
默认的负载因子,默认值是0.75
static final float DEFAULT_LOAD_FACTOR = 0.75f;
复制代码
当链表的值超过8则会转红黑树(JDK1.8新增)
static final int TREEIFY_THRESHOLD = 8;
复制代码
当链表的值小于6则会从红黑树转回链表
// 当桶(bucket)上的结点数小于这个值时树转链表
static final int UNTREEIFY_THRESHOLD = 6; 复制代码
转化为红黑树对应的数组长度最小值
// 桶中结构转化为红黑树对应的数组长度最小的值
static final int MIN_TREEIFY_CAPACITY = 64; 复制代码

当Map⾥面的数量超过这个值时,表中的桶才能进行树形化 ,不然桶内元素太多时会扩容,⽽不是树形化。目的是为了不进⾏扩容、树形化选择的冲突,这个值不能⼩于 4 * TREEIFY_THRESHOLD (8)

table⽤来初始化**(必须是二的n次幂)[重点]**
// 存储元素的数组
transient Node<K,V>[] table; 复制代码

table在JDK1.8中咱们了解到HashMap是由数组加链表加红⿊树来组成的结构其中table就是HashMap 中的数组,jdk8以前数组类型是Entry<K,V>类型。从jdk1.8以后是Node<K,V>类型。只是换了个名字, 都实现了同样的接口:Map.Entry<K,V>。负责存储键值对数据的。

⽤来存放缓存
// 存放具体元素的集合
transient Set<Map.Entry<K,V>> entrySet; 复制代码

java语言的关键字,变量修饰符,若是用transient声明一个实例变量,当对象存储时,它的值不须要维持。换句话说,用transient关键字标记的成员变量不参与序列化过程。

当一个对象被序列化的时候,transient型变量的值不包括在序列化的表示中,然而非transient型的变量是被包括进去的。

HashMap中存放元素的个数**[重点]**
// 存放元素的个数,注意这个不等于数组的长度。
transient int size; 复制代码

size为HashMap中K-V的实时数量,不是数组table的⻓度。

⽤来记录HashMap的修改次数
// 每次扩容和更改map结构的计数器
transient int modCount; 复制代码
⽤来调整大小下一个容量的值计算方式为(容量*负载因子)
// 临界值 当实际⼤小(容量*负载因⼦)超过临界值时,会进行扩容
int threshold; 复制代码
哈希表的加载因子**[重点]**
// 加载因子
final float loadFactor; 复制代码

loadFactor加载因子,是⽤来衡量 HashMap 中元素满的程度,表示HashMap的疏密程度,能够影响hash操做到同一个数组位置的几率,计算HashMap的实时加载因子的方法为:size/capacity,⽽不是占用桶的数量去除以capacity。capacity 是桶的数量,也就是 table 的⻓度length。size是集合中实际存储元素的个数。

loadFactor太⼤致使查找元素效率低,过小致使数组的利用率低,存放的数据会很分散。loadFactor 的默认值为0.75f是官方给出的⼀个比较好的临界值。

HashMap⾥面容纳的元素已经达到HashMap数组⻓度的75%时,表示HashMap太挤了,须要扩容,⽽而扩容这个过程涉及到 rehash、复制数据等操做,很是消耗性能。因此开发中尽量减小扩容的次数,能够经过建立HashMap集合对象时指定初始容量来尽量避免。

同时在HashMap的构造器中能够定制loadFactor。这个不经常使用。这个构造方法会在接下来的文章中讲解。

  • 创做不易, 很是欢迎你们的点赞、评论和关注(^_−)☆
  • 你的点赞、评论以及关注是对我最大的支持和鼓励,而你的支持和鼓励
  • 我继续创做高质量博客的动力 !!!
相关文章
相关标签/搜索