对于HashMap想必大家都不陌生,无论是平时code还是面试都经常和它打交道。今天我们通过源码的层面来分析一下它的实现原理,注意本文基于的是JDK1.8。
问题是从哪边开始聊起呢?我觉得不妨先从一段熟悉的代码开始。
MapString> map = map.put(1, "Jack");
然后我们会迫不及待点开HashMap这个类,发现里面有大量的属性和方法,一脸懵逼。那就直接点开put方法?点了之后发现下面这段代码。
final V putVal(int hash, K key, V value, boolean onlyIfAbsent, boolean evict) {
Node[] tab; Node p; int n, i; if ((tab = table) == null || (n = tab.length) == 0) n = (tab = resize()).length; if ((p = tab[i = (n - 1) & hash]) == null) tab[i] = newNode(hash, key, value, null);
依旧一脸懵逼,完全没有看下去的欲望,怎么办?为什么会这样?原因是我们不了解HashMap的数据结构,什么意思?也就是说,当把key,value存储到HashMap中之后,不知道它们是以何种数据排列的方式去存储的,这样根本就不明白源码写的思路是什么,所以我们先把目光转向到数据结构,更准备地说是HashMap的数据结构。
1 HashMap数据结构
从网上的很多资料我们知道,HashMap1.7的数据结构是数组+链表,HashMap1.8的数据结构是数组+链表+红黑树,下面这张图我画出了HashMap1.8的数据结构。
有些哥们可能会说,我在网上看到的不是这样。这时候你可以发挥空间想象能力逆时针旋转个90度,也就是下面这样的展示形式。
不管如何,反正都能体现出是数组+链表+红黑树的数据结构的方式。虽然数据结构是知道了,但是关键是图解中的每个小格子表示的是什么呢?对于我们了解HashMap的原理和源码有什么作用吗?先别急,一个个来看,先看每个小格子表示什么。
2 每个小格子的含义
我们可以猜想一下,每个小格子表示里面至少包含了key,value,为什么这么说呢?因为hashmap.put(key,value)之后,就会形成上述的数组+链表+红黑树的结构,那这个结构中的每个小格子至少把key和value涵盖进去了,如果不是,那么key,value怎么存储呢?ok,假如这个猜想是对的,那Java中想要同时存储key,value两个值,该怎么表示呢?我觉得可以用XXX类,比如下面的伪代码。
class XXX{
private Integer key; private String value;}
我觉得靠谱,如果真的是这样,那么要想形成上述的数据结构的图解,只需要创建一个个XXX类的对象,然后排列好它们的方式不就ok了吗?没错,关键这个排列要形式数组+链表+红黑树的数据结构。我们暂且给XXX一个名称叫”Node”,于是就是这样了。
class Node{
private Integer key; private String value;}
这时候有些哥们想,上面都是你主观的一个猜想,源码中真的是这样做的吗?我们不妨在HashMap类中搜索一下”Node”,发现有这样一个内部类。
static class Node<K,V> implements Map.Entry<K,V> {
final int hash; final K key; V value; Node next; ...}
ok,至此每个小格子表示的含义猜想和验证已经完成,发现源码中真的也有这样一个Node类,并且里面维护了key和value属性,至于其他属性是什么含义,我们后面再聊。
3 Node的排列方式/数据结构
上面既然已经验证了小格子对应的就是Node类,或者可以称为是Node节点。接下来我们的任务就是将这些节点来排列成数组+链表+红黑树的形式。
3.1 数组
想要将Node节点形式数组,按照以往的经验只需要在类中维护一个Node[]的属性即可,那么源码中是否有这样做呢?
/** * The table, initialized on first use, and resized as * necessary. When allocated, length is always a power of two. * (We also tolerate length zero in some operations to allow * bootstrapping mechanics that are currently not needed.) */ transient Node[] table;
我们会发现源码中维护了这样一个成员变量,Node[] table,这样数组的排列方式就解决了。
3.2 链表
链表无非就是Node节点和Node节点的关系的维护,这个关系可以分为单向链表或者双向链表,在前面的图解中我们发现这个链表是单向的,但是如果要想在源码中验证这个单向链表,只需要在原来的Node类中维护一个Node属性,如下所示。
class Node{
private Integer key; private String value; private Node next; //单向链表属性的维护}
那源码中是否是这样做的呢?通过下面代码中的Node next属性可以发现的确是单向链表的方式
static class Node<K,V> implements Map.Entry<K,V> {
final int hash; final K key; V value; Node next;}
3.3 红黑树
红黑树是一种特殊的二叉树,对于二叉树我们比较熟悉,会有父节点,左子树,右子树等。
这时候我们会想,源码中是否有这样来做呢?搜索”TreeNode”,发现会有这样一段代码。
/** * Entry for Tree bins. Extends LinkedHashMap.Entry (which in turn * extends Node) so can be used as extension of either regular or * linked node. */ static final class TreeNode<K,V> extends LinkedHashMap.Entry<K,V> {
TreeNodeparent; // red-black tree links TreeNode left; Tree