HashMap在java集合中是一个非常重要的类,经常会在大厂面试中用于测试面试者对HashMap源码设计的理解,对java基础技术研究的热情以及学习能力、思维的缜密等等。每次看到HashMap相关的技术文章,总是看了记,忘了看,理解和掌握不是很深刻,有种雾里看花的感觉,索性自己看到底是怎么实现的,但是时间一长还是会模糊,干脆自己动手记录下来,一来加深研究,二来增加印象,方便回头复习。

本文就HashMap初始化、添加元素、扩容时机和机制等常见问题进行分探索。(本篇用到的Java版本是Java8)

SRE实战 互联网时代守护先锋,助力企业售后服务体系运筹帷幄!一键直达领取阿里云限量特价优惠。

先看下几个常量定义:

/**
* The default initial capacity - MUST be a power of two.
*/
static final int DEFAULT_INITIAL_CAPACITY = 1 << 4; // aka 16

这个常量是在新建HashMap时候,没有指定初始容量的时候,会默认指定初始容量为16

/**
* The maximum capacity, used if a higher value is implicitly specified
* by either of the constructors with arguments.
* MUST be a power of two <= 1<<30.
*/
static final int MAXIMUM_CAPACITY = 1 << 30;

HashMap的最大容量即2^30

/**
* The load factor used when none specified in constructor.
*/
static final float DEFAULT_LOAD_FACTOR = 0.75f;

默认的负载因子(当前容量/数组总长度)

/**
* The bin count threshold for using a tree rather than list for a
* bin. Bins are converted to trees when adding an element to a
* bin with at least this many nodes. The value must be greater
* than 2 and should be at least 8 to mesh with assumptions in
* tree removal about conversion back to plain bins upon
* shrinkage.
*/
static final int TREEIFY_THRESHOLD = 8;

HashMap中数组某个下标里存放超过8个Node,就开始对这些元素树化,不再线性存储,而是采取红黑树进行存储

/**
* The bin count threshold for untreeifying a (split) bin during a
* resize operation. Should be less than TREEIFY_THRESHOLD, and at
* most 6 to mesh with shrinkage detection under removal.
*/
static final int UNTREEIFY_THRESHOLD = 6;

当HashMap中数组某个下标里存放超过少于6个Node,则对它进行反树化,也就是从红黑树模式回归到原来的线性存储模式
/**
* The smallest table capacity for which bins may be treeified.
* (Otherwise the table is resized if too many nodes in a bin.)
* Should be at least 4 * TREEIFY_THRESHOLD to avoid conflicts
* between resizing and treeification thresholds.
*/
static final int MIN_TREEIFY_CAPACITY = 64;

在对某个下标下的元素进行树化,还有一个前提,就是此时数组长度必须达到64
下面这个内部类就是HashMap中存储元素的文件结构,换句话说,HashMap存储的就是一个个Node类型的类,类中存储着它的hash值、key、value和下一个Node的引用。
static class Node<K,V> implements Map.Entry<K,V> {
final int hash;
final K key;
V value;
Node<K,V> next;

Node(int hash, K key, V value, Node<K,V> next) {
this.hash = hash;
this.key = key;
this.value = value;
this.next = next;
}
}
来看下这个hash()方法
static final int hash(Object key) {
int h;
return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
}
这个方法是对key取hash值。若key为null则hash值返回0,所以HashMap的可以为null,当key不为空的时候,并不是直接调用Object.hashCode()方法,而是用它和它自身的高16位进行抑或,这样会使得出来的hash值更为分散,存储在HashMap的时候,也存储的更均匀。

扫码关注我们
微信号:SRE实战
拒绝背锅 运筹帷幄