主要变量和内部结构
public class HashMap<K,V> extends AbstractMap<K,V>
implements Map<K,V>, Cloneable, Serializable {
//===========================成员变量==========================
//传说中的哈希表,一个Node类型的数组
//数组每一个位置是一个哈希桶
transient Node<K,V>[] table;
//整个HashMap中键值对个数
transient int size;
//装载因子
final float loadFactor;
//扩容阈值,当size大于 {容量(数组长度) 乘 装载因子} 时,进行数组扩容
int threshold;
//一个set集合,存放所有的键值对,集合内元素类型为Map.Entry<K,V>
//Entry<K,V>,是Map接口中的一个内部接口
//HashMap中,写了一个静态内部类Node<K,V>来实现Map.Entry<K,V>接口
transient Set<Map.Entry<K,V>> entrySet;
//HashMap结构发生变化的次数
//被动过,就会+1,包括添加元素、扩容、重哈希等操作
transient int modCount;
//===========================静态变量==========================
//=========================规定了几个值========================
//数组默认初始容量:16 ,即哈希桶数量
static final int DEFAULT_INITIAL_CAPACITY = 1 << 4;
//数组最大容量
static final int MAXIMUM_CAPACITY = 1 << 30;
//默认装载因子:0.75
static final float DEFAULT_LOAD_FACTOR = 0.75f;
//树化的阈值:8,表示一个哈希桶内有8个元素时,会转为红黑树
//通过代码分析,到达8时,并不一定会转,8只是一个建议值
static final int TREEIFY_THRESHOLD = 8;
//反树化阈值:6
//单一个哈希桶处于树化状态,且元素个数小于6时,反树化,变为链表。
static final int UNTREEIFY_THRESHOLD = 6;
//树化最小容量
//哈希桶数量达到或超过64时,就可能触发链表到红黑树的转换。
static final int MIN_TREEIFY_CAPACITY = 64;
//序列化版本号
private static final long serialVersionUID = 362498820763181265L;
//===========================内部结构==========================
//======================定义了每个键值对长啥样==================
//静态内部类,描述链状哈希桶中的一个元素,即一个链表结点
static class Node<K,V> implements Map.Entry<K,V> {
final int hash; //哈希值,根据key计算得到
final K key;
V value;
Node<K,V> next;
//...
}
//静态内部类,描述树状哈希桶中的一个元素,即一个红黑树结点
static final class TreeNode<K,V> extends LinkedHashMap.Entry<K,V>{
TreeNode<K,V> parent;
TreeNode<K,V> left;
TreeNode<K,V> right;
TreeNode<K,V> prev;
boolean red;
//...
}
}
构造函数
创建对象时,先不创建数组,只初始化成员变量
//无参构造
public HashMap() {
//装载因子为默认16
this.loadFactor = DEFAULT_LOAD_FACTOR;
}
//指定初始容量(数组长度)和装填因子
public HashMap(int initialCapacity, float loadFactor) {
//合法性检测
if (initialCapacity < 0)
throw new IllegalArgumentException("Illegal initial capacity: " +
initialCapacity);
if (initialCapacity > MAXIMUM_CAPACITY)
initialCapacity = MAXIMUM_CAPACITY;
if (loadFactor <= 0 || Float.isNaN(loadFactor))
throw new IllegalArgumentException("Illegal load factor: " +
loadFactor);
this.loadFactor = loadFactor;
//根据给定初始容量计算一个合适容量---大于等于initialCapacity的最小的2的幂次方
//将这个值保存在threshold中
this.threshold = tableSizeFor(initialCapacity);
//补充:
//由于初始化时并没有直接创建数组,所以容量需要先保存,用于第一次添加元素时,创建数组。
}
//指定初始容量,装填因子默认0.75
public HashMap(int initialCapacity) {
this(initialCapacity, DEFAULT_LOAD_FACTOR);
}
添加元素
计算哈希值,根据哈希值计算属于哪个哈系桶
桶内无元素,直接添加
有元素则依次对比,相同覆盖
//调用定义好的hash函数,计算key的hash值
//再调用putVal存入哈希表
public V put(K key, V value) {
return putVal(hash(key), key, value, false, true);
}
//计算哈希值
static final int hash(Object key) {
int h;
//key是null,返回0
//key非null,调用key的hashCode方法得到哈希值
//再进一步通过 h ^ h >>> 16 得到新的哈希值
return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
//补充:
//为什么要h ^ h >>> 16? 而不直接用hashCode
//在计算元素属于哪一个哈希桶时,有一个取模操作
//取模操作的特性,比如:任何整数对10取模,得到的数只与个位有关,不管高位是什么。
// h ^ h >>> 16,得到一个新的整数值。
//这样可以使得h低16位和高16位都参与到最终的hash值计算中,提高了hash值分布均匀性。
//对应到上面的例子,这样做以后,个位上的数字就不在是“单纯个位”
//而是考虑了高位后,重新计算得到的“新个位”
//比如:
// 00001111 11001100 00001111 10101010
//右移16位后 00000000 00000000 00001111 11001100
//相当于把高16位和低16位异或,这样取模操作时就可以考虑高位和低位
//两个key就算低位一样,只要高位不一样,也大概率分不到同一个哈希桶
//hash分布更均匀
}
final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
boolean evict) {
Node<K,V>[] tab; Node<K,V> p; int n, i; //定义几个辅助变量 tab、p、n、i
if ((tab = table) == null || (n = tab.length) == 0)
//table为null 或 者长度为0
n = (tab = resize()).length; //调用resize创建数组或扩容,并将数组长度(容量)赋值给n
if ((p = tab[i = (n - 1) & hash]) == null) //哈希值转为数组下标,并取出元素(保存在p中)
//p为null,说明桶中没有元素,直接放进去
tab[i] = newNode(hash, key, value, null);
else {
//非空,则依次对比桶中的对象,不存在才放
Node<K,V> e; K k; //定义两个辅助变量e、k
if (p.hash == hash && //要添加的对象的哈希值 和 p(桶中第一个对象)的哈希值相等,并且key相等(引用相等或者equals相等)
((k = p.key) == key || (key != null && key.equals(k))))
e = p; //说明已经存在,将p赋值给e
//由此说明,若哈希值不相等,直接判定为不同
//接下来要继续判断桶中的其它元素
//由于一个桶的结构可能是链表或者红黑树,不同的结构有不同的遍历方式
//所以需要判断当前是什么结构
else if (p instanceof TreeNode)
//是红黑树,则调用红黑树的put逻辑
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) //检查桶内元素个数是否达到树化阈值
treeifyBin(tab, hash); //达到就进行树化
break;
}
if (e.hash == hash &&
((k = e.key) == key || (key != null && key.equals(k))))
break; //找到一个相同的key就可以break
p = e; //指针下移,准备遍历下一个(此时,e = p.next,所以相当于p = p.next)
}
}
if (e != null) { // e不为null,说明找到了相同的key
V oldValue = e.value; //拿到旧的value
//判断是否进行覆盖
//onlyIfAbsent为false,或者oldValue为Null,则覆盖
if (!onlyIfAbsent || oldValue == null) //onlyIfAbsent默认为false,即默认会覆盖
e.value = value; //覆盖旧值,若不想覆盖,可
afterNodeAccess(e); //haspmap中为空实现,主要留给子类扩展。
return oldValue; //将旧值返回
}
}
//至此,说明整个桶中没有相同的key,可以添加
++modCount; //表结构修改次数+1
if (++size > threshold) //元素个数达到阈值,则扩容
resize();
afterNodeInsertion(evict); //haspmap中为空实现,主要留给子类扩展。
return null; //添加成功返回null
}
定位属于哪个哈希桶
i = (n - 1) & hash //i为计算后的数组下边,n为表长,hash为哈希值
例:
n为16, n - 1 为15,二进制形式为1111。
hash 为42,二进制形式为 101010。
执行按位与 (&)
00001111 (15)
& 00101010 (42)
-----------
00001010 (10)
所以,该元素应该放在数组下标为10的位置。
hash 和15(1111)按位&,相当于把hash映射到 0000~1111这个范围,16个数,对应16个数组下标。
& (n - 1) 实现了一个数对16取模的功能。
扩容
计算新容量、阈值,并创建新表
final Node<K,V>[] resize() {
//保存旧表信息
Node<K,V>[] oldTab = table; //保存当前哈希表的 引用
int oldCap = (oldTab == null) ? 0 : oldTab.length; //保存当前哈希表的 容量
int oldThr = threshold; //保存当前哈希表的 阈值
//保存新容量、阈值
int newCap, newThr = 0;
//计算新容量
if (oldCap > 0) {
//旧表非空,走这里
if (oldCap >= MAXIMUM_CAPACITY) { //旧容量已经达到或超过MAXIMUM_CAPACITY
threshold = Integer.MAX_VALUE; //阈值拉满,不再进行扩容
return oldTab; //直接返回旧表
}
else if ((newCap = oldCap << 1) < MAXIMUM_CAPACITY &&
oldCap >= DEFAULT_INITIAL_CAPACITY)
newThr = oldThr << 1; //正常情况下,容量翻倍,阈值翻倍
}
//处理旧表为空的情况(刚创建对象的状态)
else if (oldThr > 0)
//通过有参构造创建对象,oldThr中保存的是根据初始化容量计算出来的“合适的容量”
newCap = oldThr;
else {
//通过无参构造创建对象,oldCap和oldThr都为0,会走到这里
//使用默认值设置新的容量和阈值
newCap = DEFAULT_INITIAL_CAPACITY;
newThr = (int)(DEFAULT_LOAD_FACTOR * DEFAULT_INITIAL_CAPACITY);
}
//计算新的阈值
if (newThr == 0) {
float ft = (float)newCap * loadFactor; //正常情况等于 容量乘加载因子
newThr = (newCap < MAXIMUM_CAPACITY && ft < (float)MAXIMUM_CAPACITY ?
(int)ft : Integer.MAX_VALUE); //异常情况(容量或者阈值大于等于最大容量)直接拉满
}
threshold = newThr;
//新容量和阈值计算好了
//创建新表
@SuppressWarnings({"rawtypes","unchecked"})
Node<K,V>[] newTab = (Node<K,V>[])new Node[newCap];
table = newTab;
//旧表非空,则将旧表元素迁移到新表
if (oldTab != null) {
for (int j = 0; j < oldCap; ++j) {
Node<K,V> e;
if ((e = oldTab[j]) != null) {
oldTab[j] = null;
if (e.next == null)
newTab[e.hash & (newCap - 1)] = e;
else if (e instanceof TreeNode)
((TreeNode<K,V>)e).split(this, newTab, j, oldCap);
else { // preserve order
Node<K,V> loHead = null, loTail = null;
Node<K,V> hiHead = null, hiTail = null;
Node<K,V> next;
do {
next = e.next;
if ((e.hash & oldCap) == 0) {
if (loTail == null)
loHead = e;
else
loTail.next = e;
loTail = e;
}
else {
if (hiTail == null)
hiHead = e;
else
hiTail.next = e;
hiTail = e;
}
} while ((e = next) != null);
if (loTail != null) {
loTail.next = null;
newTab[j] = loHead;
}
if (hiTail != null) {
hiTail.next = null;
newTab[j + oldCap] = hiHead;
}
}
}
}
}
return newTab;
}
是否真的树化?
树化的条件:单个桶内元素个数到阈值8,且表长到达阈值64
final void treeifyBin(Node<K,V>[] tab, int hash) {
int n, index; Node<K,V> e;
if (tab == null || (n = tab.length) < MIN_TREEIFY_CAPACITY)
resize(); //由此说明,表长未达到MIN_TREEIFY_CAPACITY(64)时,继续调用resize()扩容
else if ((e = tab[index = (n - 1) & hash]) != null) {
//满足条件,才转红黑树
TreeNode<K,V> hd = null, tl = null;
do {
TreeNode<K,V> p = replacementTreeNode(e, null);
if (tl == null)
hd = p;
else {
p.prev = tl;
tl.next = p;
}
tl = p;
} while ((e = e.next) != null);
if ((tab[index] = hd) != null)
hd.treeify(tab);
}
}
总结
HashMap内部维护一个Node类型数组,称为哈希表,数组的每个元素称为桶,每个桶可能是链状(链表)或树状(红黑树)
添加元素时,先计算哈希值,根据哈希值定位到哈系桶,将元素放入桶中,有相同的元素(hash值相同且equals为true)则覆盖
当哈希表中元素个数达到扩容阈值(容量*装载因子)时,进行2倍扩容
桶内元素个数到8,且表长到64,才会转为红黑树。