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

前言

点赞在看,养成习惯。

点赞收藏,人生辉煌。

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

HashMap系列文章

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

第二篇 撸啊撸,再次撸HashMap源码,踩坑源码中构造方法!!!每次都有收获

第三篇 MoxiMoxi !!!你看过HashMap中的put方法的源码吗?

第四篇 HashMap源码中的resize扩容方法除了扩容还有一个用途你真的知道吗?

第五篇 留一半清醒、留一半醉!!!HashMap中treeifyBin、treeify源码解析

HashMap集合简介

概述

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

数据结构
  • 在JDK1.8 之前 HashMap 由 数组+链表 数据结构组成的。
  • 在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的继承图解

在这里插入图片描述

继承图解析
  • Cloneable是空接口,表示可以克隆隆。 代表创建并返回HashMap对象的一个副本。
  • Serializable 序列化接口。属于标记性接口。HashMap对象可以被序列化和反序列化。
  • 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。这个不常用。这个构造方法会在接下来的文章中讲解。

谢谢点赞

  • 创作不易, 非常欢迎大家的点赞、评论和关注(^_−)☆
  • 你的点赞、评论以及关注是对我最大的支持和鼓励,而你的支持和鼓励
  • 我继续创作高质量博客的动力 !!!

猜你喜欢

转载自blog.csdn.net/wildwolf_001/article/details/107186226