HashMap 是在JDK1.2中引入的一种 K/V对 形式的集合类. HashMap 通过 数组和单链表 组合的结构形式来存储数据,数组在这作为一个外部结构,数组中的每个节点被称做 Bucket(桶) ,而 桶是由在单链表构成 , JDK1.8 之后 为了解决长链表下,查询和插入效率低下的情况,又引入了红黑树的作为桶的实现方式 , HashMap 定义的 Node 内部类生成的,是普通的链表节点类.
HashMap 是线程不安全的,多线程情况下可能会出现环路(后面会讲) ,多线程状态下还是使用 ConcurrentHashMap 比较合适. HashMap 的参数不多,除去当做默认属性的静态常量和底层数组对象,就只有以下五个 transient Node<K,V>[] table; transient int size transient int modCount; int threshold; final float loadFactor; 复制代码
table 就是整个 HashMap 的底层数组, table 的初始化并不在构造函数中完成,而是在 resize() 方法中完成.
table 的初始化可能有点绕,构造函数中最多指定了阈值 threshold 和负载因子 loadFactor 并没有容量相关,但是在 resize() 方法中 会根据旧容量和旧阈值判断新容量是等于默认容量,旧阈值或者两倍旧容量 ,最后根据新容量创建新数组 loadFactor 就是所谓的负载因子,默认为0.75,是控制扩容时机的关键属性,因为扩容发生在当前元素个数超过阈值时,而阈值等于当前容量乘以负载因子. modCount 为修改计数,是 fast-fail 机制的关键参数.在对 Map 中的元素做新增/删除操作时会自增,但修改不会(putVal()方法中覆盖原值) HashMap 的新增过程重点主要还是定位, 如何确定元素在数组中的位置 , HashMap 采用的就是 Hash算法
HashMap 会根据 Key 的hash值,按照表达式 (n - 1) & hash 计算出桶的下标 Node ,作为链表的第一个元素,直接存放在数组中.(以前还听说过什么链表首节点为空的情况,是假的.) TreeNode
table 是否初始化,新增后会判断该桶大小是否超过的8,超过则转化为红黑树,再判断整个数组是否需要扩容. Hash 同时也叫散列,可以把任意长度的输入通过算法,换算成固定长度的输出,不同元素通过 Hash 算法获得的下标一致可以被称之为 冲突或者碰撞 , Hash 算法的要求就是使元素尽量少的发生碰撞,从而均匀的散布在 数组中 .而发生碰撞时,像 HashMap 这种以一个列表下挂的方式可以被称为 拉链法 . get() 方法,通过 key 值查找的情况,如果自己遍历的另说.
(n - 1) & hash 计算出桶的下标(可以说是相当重要了),若得到的桶为空,直接返回null key.equals(k) 判断是否相等 JDK1.8 之后引入的红黑树作为桶的另一种实现方法.当链表长度大于 8 时,桶的实现会转化为 红黑树 . HashMap 的性能很大一部分取决于 Hash 算法. 通过插入和查找我们可以知道,在数组大小不变的情况下,**链表越长或者说树的高度越高性能都会降低,**所以此时很有必要通过扩容数组的方式,重新排列桶中元素,降低链表长度,减少树的高度.
首先,触发扩容的情况是 size > threshold 即元素个数大于阈值.整个扩容过程可以简单的拆分为以下几步:
1 << 30 也就是1的30次方. resize() 方法中重新散布元素的方法还是很有意思的除去单元素链表和红黑树(桶的容量在1~7之间)
lo 和 hi (源码是loHead和hiHead,我猜时low和high,怎么这么缩写随意), lo 表示0到旧容量部分, hi 表示余下算是新加入的部分,并以此创建两个链表 e.hash & oldCap 判断元素是否分布在 lo 部分,是就挂到 lo 链表下面,否就挂到 hi 链表下面. lo 链表挂到和旧数组相同位置的桶,而 hi 则挂到下标为 原下标 + 旧数组容量 的桶.
e.hash & (oldCap - 1) + oldCap == e.hash & (oldCap << 1) -1 可以看出 resize() 方法会调整全部的元素散列情况,因此过于频繁的 resize 会降低 HashMap 的性能, 因此如果一开始可以大概知道所需要存放的元素个数时,尽量直接指定容量大小.
JDK1.7 之前的 resize() 方法在并发条件下可能会发生闭环问题,但在 JDK1.8 之后不会在出现,但并不代表 HashMap 可以在并发条件下使用了,小部分情况还是会出现数据丢失等问题.
介绍 JDK1.8 之前的闭环问题详情的文章