简介
本文为我对 HashMap 实现原理的笔记整理以及一些个人理解,如若发现有错误的地方,欢迎留言指正
在不同的 Java 版本中 HashMap 的实现也略有不同,本文示例使用的 Java 版本为:“1.8.0_181”
什么是 Hash(散列函数)
Hash 音译为「哈希」,它是把任意长度的输入通过散列算法变换成固定长度的输出,这个输出称为散列值。
这种转换是一种压缩映射,也就是说散列值的空间远小于输入的空间,不同的输入可能会散列成相同的输出,所以 不可能从散列值来确定唯一的输入值。
Map: key 和 value 的映射
在了解了什么是 Hash 后,我们再来看下什么是 Map
Map 是一种最基础的数据结构,它描述的是一个 key 和 一个 value 一 一对应的数据结构。
每种高级语言都有对 Map 的定义和实现。
Map 接口的定义
Map.java
public interface Map<K,V> {
···
// 增加一个键值对
V put(K key, V value);
// 传入一个 key,并返回对应的 value
// 注意此处传入的 key 的类型不同于 put 方法,不是泛型而是一个 Object
V get(Object key);
// 删除一个键值对
V remove(Object key);
···
}
问题思考
为什么 Map 中 put 方法中传入的 key 的类型是泛型,而在 get 或 remove 方法中却是 Object 类型呢?
HashMap的实现:数组+链表+(红黑树)
HashMap 是 Map 最常用的实现类,它是通过 key 的 hash 值来决定 value 在数组中的位置。但是上面我们也提到哈希值是有可能会重复的,也就是说不同的 key 得到的哈希值是一样的,这种情况我们称为 哈希冲突。
哈希冲突 意味着不同的 value 要放在数组的同一个位置,那么 HashMap 是如何处理这种情况的呢?
在 Java1.7 中 HashMap 主要以数组+链表的形式实现的,而到了 Java1.8 则引入了黑红树算法来优化 HashMap 的查询速度。
当发生 哈希冲突 时,HashMap 会将相同的 value 以链表的形式存放到对应的数组位置中。
如上图所示,table 的初始长度为 16,每个节点都由四个元素组成:hash、key、value 和 next,其中 next 记录着下一个节点。
举个例子:我们根据 key1 的哈希值得出它在 table 的下标位置为 0,若当前 table[0] 还未有元素则创建一个 Node1,这个 Node1 记录着 key1 的哈希值、key1、key1 对应的 value 值以及 next(默认为 null),然后将这个 Node1 放入 table[0] 的位置中
之后我们再根据 key2 的哈希值得到它在 table 的下标位置也为 0,此时 table[0] 已经存在了一个 Node1, 此时 HashMap 同样会先创建一个 Node2,接着把这个 Node2 放到 Node1 的 next 中
HashMap 源码分析
AbstractMap.java
public abstract class AbstractMap<K,V> implements Map<K,V> {
...
}
HashMap.java
public class HashMap<K,V> extends AbstractMap<K,V>
implements Map<K,V>, Cloneable, Serializable {
...
}
HashMap 继承了 AbstractMap 抽象类,而 AbstractMap 抽象类又实现了 Map 接口
阀值、负载系数、容量
在深入 HashMap 代码之前,我们先来认识 HashMap 的几个变量
HashMap.java
public class HashMap<K,V> extends AbstractMap<K,V>
implements Map<K,V>, Cloneable, Serializable {
```
// 初始容量 = 2的4次方 = 16
static final int DEFAULT_INITIAL_CAPACITY = 1 << 4;
// 最大容量 = 2的30次方
static final int MAXIMUM_CAPACITY = 1 << 30;
// 默认负载系数
static final float DEFAULT_LOAD_FACTOR = 0.75f;
// 链表树形化的阈值
static final int TREEIFY_THRESHOLD = 8;
```
}
DEFAULT_INITIAL_CAPACITY 定义了 HashMap 的初始容量为 16
MAXIMUM_CAPACITY 则表示 HashMap 的最大容量为 230
比较有意思的是 DEFAULT_INITIAL_CAPACITY 和 MAXIMUM_CAPACITY 都是用位运算来定义的
HashMap.java
public class HashMap<K,V> extends AbstractMap<K,V>
implements Map<K,V>, Cloneable, Serializable {
```
// 默认负载系数
static final float DEFAULT_LOAD_FACTOR = 0.75f;
// 链表树形化的阈值
static final int TREEIFY_THRESHOLD = 8;
```
// 扩容阀值
int threshold;
// 负载系数
final float loadFactor;
```