HashMap实现原理
HashMap
底层是用以Node
组成的链表为元素的数组table
来存储键值对,每个Node
就是一个键值对对象。table
称呼为哈希表。
而table
被称呼为哈希表,是因为无论是存储还是读取键值对的时候,都会对key
进行hash运算来进行哈希表的命中,然后操作命中的索引对应的Node
链表。
HashMap中的关键成员
/**
* 默认的初始容量,必须为2的次幂
*/
static final int DEFAULT_INITIAL_CAPACITY = 1 << 4; // 总所周知是16
/**
* 最大容量
*/
static final int MAXIMUM_CAPACITY = 1 << 30;
/**
* 默认的负载因子
*/
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;
/**
* 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;
/**
* 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;
/**
* HashMap中存储数据的数组,也称为散列表。
* 建议保持长度为2的次幂
*/
transient Node<K,V>[] table;
/**
* 缓存entrySet()方法的值
*/
transient Set<Map.Entry<K,V>> entrySet;
/**
* Map中键值对的个数
*/
transient int size;
/**
* HashMap数据结构被改变的次数,一般是指哈希表的长度改变、Node链表增加或者减少节点
* 这个参数是用于快速失败机制
*/
transient int modCount;
/**
* 下一次触发调整大小(resize()方法)的阈值,一般为容量乘以负载因子
*/
int threshold;
/**
* 哈希表的负载因子
*
* @serial
*/
final float loadFactor;
put()方法如何工作
当HashMap
的put()
被调用时,实际上是调用其内部的方法putVal()
方法,代码及分析如下:
/**
* Map.put()方法的实际实现
*
* @param hash key的hash值
* @param key 键值对中的key
* @param value 键值对中的value
* @param onlyIfAbsent 如果为true,则键值对中的值已经存在则不修改这个值
* @param evict 如果为false,则是处于创建模式
* @return 上一次的value,如果上一次的value不存在,则为null
*/
final V putVal(int hash, K key, V value, boolean onlyIfAbsent, boolean evict) {
//tab用于暂存哈希表table。p为哈希表中对应索引的链表的头节点的指针。n存储tab的长度。i则为命中的哈希表的索引
Node<K,V>[] tab; Node<K,V> p; int n, i;
//给tab和n赋值
//当tab为null或者tab的长度n为0时,触发resize()来初始化table
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);
else {//哈希表对应索引的链表已存在
Node<K,V> e; K k;
// 判断头节点的hash值和key是否与入参的hash值和key一致。需要注意,null的hash值为0
if (p.hash == hash &&
((k = p.key) == key || (key != null && key.equals(k))))
// 对应的键值对已经存在,记录下来
e = p;
else if (p instanceof TreeNode)//判断对应的链表是否转化为红黑树
//若是,则直接调用红黑树的putTreeVal()方法
e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);
else {//链表的头节点与新的键值对不重复,即没有发生哈希碰撞
for (int binCount = 0; ; ++binCount) {//遍历链表
if ((e = p.next) == null) {//遍历到尾节点
//尾插法添加一个新的节点
p.next = newNode(hash, key, value, null);
//链表长度大于阈值
if (binCount >= TREEIFY_THRESHOLD - 1) // 从-1开始,所以为阈值-1
// 将链表转化为红黑树
treeifyBin(tab, hash);
// 中断循环
break;
}
// 判断当前遍历的节点的hash值和key是否与入参的hash值和key一致,即key是否已经存在
if (e.hash == hash &&
((k = e.key) == key || (key != null && key.equals(k))))
// key已经存在,中断循环
break;
// 记录当前遍历的节点
p = e;
}
}
if (e != null) { // Map中存在重复的key
V oldValue = e.value;//记录下旧值
if (!onlyIfAbsent || oldValue == null)//判断值存在是否可以进行修改以及旧值是否为null
e.value = value;//修改该节点的值
afterNodeAccess(e);// 链表节点的回调方法,此处为空方法
return oldValue;//返回旧值
}
}
// HashMap发生结构变化,变化次数累加
++modCount;
// 键值对个数自增,同时判断是否达到扩容的阈值
if (++size > threshold)
resize();
// 链表节点的回调方法,此处为空方法
afterNodeInsertion(evict);
// 此处返回null是因为链表新增了节点,所以上一次的值必然为null
return null;
}
Hash值的计算
无论是调用put()
还是get()
方法,HashMap都会先调用其类方法hash()
,而这个方法内部其实很简单,如下所示:
static final int hash(Object key) {
int h;
//当key为null时,hash值为0。否则就先获取key原来的hashCode,然后进行hashCode^(hashCode>>>16)运算
return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
}