HashMap源码解析
HashMap内部存储结构:数组+链表+红黑树
属性:
static final int DEFAULT_INITIAL_CAPACITY = 1 << 4;//默认容量:16
static final int MAXIMUM_CAPACITY = 1 << 30;//最大容量,2的30次方。
static final float DEFAULT_LOAD_FACTOR = 0.75f;//加载因子,默认0.75,扩容会用到。
static final int TREEIFY_THRESHOLD = 8;//当某个桶节点数量大于等于8时,会转换为红黑树。
static final int UNTREEIFY_THRESHOLD = 6;//当某个桶节点数量小于等于6时,会转换为链表,前提是它当前是红黑树结构。
/**
* HashMap链表树化的最小数组容量,其值至少为4*TREEIFY_THRESHOLD
* 在将链表转化为红黑树之前,会先判断数组的长度,如果数组长度小于64,
* 那么会选择扩容数组,而不是转化为红黑树
*/
static final int MIN_TREEIFY_CAPACITY = 64;
transient Node<K,V>[] table;//存储元素的数组,transient关键字表示该属性不能被序列化
transient Set<Map.Entry<K,V>> entrySet;//将数据转换成set的另一种存储形式,这个变量主要用于迭代功能。
transient int size;//数组中存储K,V对的数量
int threshold;//临界值,也就是元素数量达到临界值时,会进行扩容。
final float loadFactor; //也是加载因子,只不过这个是变量。
MAXIMUM_CAPACITY为什么是2的30次方?
为什么table的最大容量为2的30次方呢,让我们来看看2的30的二进制为0100……0000,由于第一位为符号位,如果将该值再往左移一位就占用了符号位,成了负值,这是不可行的,这时可能就有人疑问了,为什么不取0x7fff ffff,即0111……1111,因为HashMap的扩容机制是 原大小*2,而且HaspMap的初始容量都是2正整数幂,所以最后扩容最大也就是2^30次方,再 *2 的话就占用了符号位,这就是MAXIMUM_CAPACITY为什么取2的30次方的原因。
DEFAULT_LOAD_FACTOR为什么选用0.75?
这是空间利用和减少哈希冲突之间的一个折中选择。
如果加载因子为0.5,每扩容一次基本都会浪费一半的空间甚至更多,并且会随着一次次的扩容,空间浪费的现象会大大加剧。
如果加载因子为1,会在扩容前大大加剧哈希冲突的频率,从而使得hashmap整体的效率下降。
所以选用0.75就是在 空间利用 和 哈希冲突 之间的一个折中选择。
TREEIFY_THRESHOLD,UNTREEIFY_THRESHOLD 为什么为是8 和6?
在HashMap中有一段Implementation notes
,其中有一段大概含义是当bin变得很大的时候,就会被转换成TreeNodes中的bin,其结构和TreeMap相似,也就是红黑树:TreeNodes占用空间是普通Nodes的两倍,所以只有当bin包含足够多的节点时才会转成TreeNodes
这样就解析了为什么不是一开始就将其转换为TreeNodes,而是需要一定节点数才转为TreeNodes,其实就是空间和时间的权衡:
/** 作者给的hash冲突链表长度分别为以下值得概率
* 0: 0.60653066
* 1: 0.30326533
* 2: 0.07581633
* 3: 0.01263606
* 4: 0.00157952
* 5: 0.00015795
* 6: 0.00001316
* 7: 0.00000094
* 8: 0.00000006
*/
这段内容还说到:理想情况下随机hashCode算法下所有bin中节点的分布频率会遵循泊松分布,我们可以看到,一个bin中链表长度达到8个元素的概率为0.00000006,几乎是不可能事件。所以,之所以选择8,是根据概率统计决定的。
至于为什么转回来是6,因为如果hash碰撞次数在8附近徘徊,会一直发生链表和红黑树的互相转化,为了预防这种情况的发生。
构造方法:
/**
无参构造方法,加载默认加载因子0.75
**/
public HashMap() {
//初始化加载因子为默认值(0.75)
this.loadFactor = DEFAULT_LOAD_FACTOR; // all other fields defaulted
}
/**
* @param initialCapacity 数组容量
设置数组默认初始大小,同时也加载默认的加载因子0.75
**/
public HashMap(int initialCapacity) {
this(initialCapacity, DEFAULT_LOAD_FACTOR); //调用另一个双参数构造方法
}
/**
* @param initialCapacity 数组容量
* @param loadFactor 装载因子
*/
public HashMap(int initialCapacity, float loadFactor) {
//检查initialCapacity和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的非负整数幂的数
this.threshold = tableSizeFor(initialCapacity);
}
/**
使用另一个Map初始化的构造函数
**/
public HashMap(Map<? extends K, ? extends V> m) {
this.loadFactor = DEFAULT_LOAD_FACTOR;
putMapEntries(m, false);
}
put
public V put(K key, V value) {
return putVal(hash(key), key, value, false, true);
}
final V putVal(int hash, K key, V value, boolean onlyIfAbsent, boolean evict) {
Node<K,V>[] tab; //代表hashmap内的数组
Node<K,V> p; //
int n, i; //n:数组长度 ,i:计算出存储的数组下标值
if ((tab = table) == null || (n = tab.length) == 0)
n = (tab = resize()).length; //初始化数组,并将数组长度存入n
if ((p = tab[i = (n - 1) & hash]) == null) //p引用在数组i位置的对象,如果为空,则直接填入
tab[i] = newNode(hash, key, value, null);
else {
Node<K,V> e; //用来数组内对象的key属性与key值相同的对象
K k;
if (p.hash == hash && ((k = p.key) == key || (key != null && key.equals(k))))
e = p; //e获取到数组i位置的对象
else if (p instanceof TreeNode) //判断是否为红黑树
e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);
else {
for (int binCount = 0; ; ++binCount) { //遍历链表
if ((e = p.next) == null) { //将该位置的对象赋值给e,如果链接下一个对象为null,则直接链接到新创建的Node对象
p.next = newNode(hash, key, value, null);
if (binCount >= TREEIFY_THRESHOLD - 1) // binCount大于等于7,此时新加了一个对象,此时链表大小为8,符合转换成红黑树的标准
treeifyBin(tab, hash);
break;
}
if (e.hash == hash && ((k = e.key) == key || (key != null && key.equals(k))))//e的key属性和链表内某个对象相同,则跳出遍历
break;
p = e;
}
}
if (e != null) { // e获取到了对象
V oldValue = e.value; //oldValue用来存储旧的value值。
if (!onlyIfAbsent || oldValue == null)
e.value = value; //新值将其覆盖
afterNodeAccess(e);
return oldValue; //返回旧值
}
}
++modCount; //map结构变化记录加1
if (++size > threshold) //存放Node数量+1,如果大于扩容阈值,则进行扩容
resize();
afterNodeInsertion(evict);
return null; //因为新填入的,则旧值为null
}
tableSizeFor
static final int tableSizeFor(int cap) { //传入自定义大小,假设 cap = 9,二进制为 0000 1001
int n = cap - 1; //n = 8 0000 1000
/**
经过下面的一系列 位移 和 与 操作,将n 最高位1 后面的全部位变为1
*/
n |= n >>> 1; //0000 1000 | 0000 0100 = 0000 1100 操作2位
n |= n >>> 2; //0000 1100 | 0000 0011 = 0000 1111 操作4位
n |= n >>> 4; //0000 1100 | 0000 0011 = 0000 1111 操作8位
n |= n >>> 8; // 操作16位
n |= n >>> 16; // 操作32位
/**
如果n<0,返回最小大小: 1
否则
如果 n>=最大容量,返回 最大容量
否则 返回 n+1 , 即返回 大于等于 cap最接近的 2正整数次幂
*/
return (n < 0) ? 1 : (n >= MAXIMUM_CAPACITY) ? MAXIMUM_CAPACITY : n + 1;
}
hash
/**
这段代码是为了对key的hashCode进行扰动计算,防止不同hashCode的高位不同但低位相同导致的hash冲突。
简单点说,就是为了把高位的特征和低位的特征组合起来,降低哈希冲突的概率
也就是说,尽量做到任何一位的变化都能对最终得到的结果产生影响。
*/
static final int hash(Object key) {
int h;
return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
}
resize
final Node<K,V>[] resize() {
Node<K,V>[] oldTab = table; //指向当前的哈希数组
int oldCap = (oldTab == null) ? 0 : oldTab.length; //oldCap : 当前的数组大小
int oldThr = threshold; //oldThr : 目前的阀值
int newCap, newThr = 0; //newCap : 新的容量大小 newThr :新的阀值
if (oldCap > 0) { //已经被初始化,判断是否扩容
if (oldCap >= MAXIMUM_CAPACITY) { //超过最大容量,不进行扩容
threshold = Integer.MAX_VALUE;
return oldTab; //将阀值设置成Integer.MAX_VALUE,直接返回旧表
}
else //记号:A newCap = oldCap*2<最大容量限制,并且oldCap>= 默认容量(16)
if ((newCap = oldCap << 1) < MAXIMUM_CAPACITY && oldCap >= DEFAULT_INITIAL_CAPACITY)
newThr = oldThr << 1; // double threshold
}
else //未被初始化,因为长度为0
if (oldThr > 0)//记号:B 当阀值>0,此时阀值就是容量大小,因为构造方法内没有进行 容量*加载因子 的运算
newCap = oldThr; // 新容量=阀值
else { // 阀值=0,使用的是 无参 构造方法
newCap = DEFAULT_INITIAL_CAPACITY;
newThr = (int)(DEFAULT_LOAD_FACTOR * DEFAULT_INITIAL_CAPACITY);
}
//对记号AB的补充,因为没有进行newThr的操作和赋值
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]; //创建一个newCap大小的新表
table = newTab; //table指向新创建的新表
if (oldTab != null) {
for (int j = 0; j < oldCap; ++j) { //遍历数组
Node<K,V> e;
if ((e = oldTab[j]) != null) { //e指向数组该下标位置的对象,如果不为null
oldTab[j] = null;
if (e.next == null) //如果没有下一个节点
newTab[e.hash & (newCap - 1)] = e; //直接在新数组的 e.hash & (newCap - 1) 位置赋值
else if (e instanceof TreeNode) //判断是否为红黑树结构
((TreeNode<K,V>)e).split(this, newTab, j, oldCap);
else {
/**
这块是处理链表的情况,
需要将此链表拆成两个链表,放到新的数组中,并且保留原来的先后顺序
loHead、loTail 对应一条链表(在新数组下标位置不变,即:j)
hiHead、hiTail 对应另一条链表(在新数组下标为:j + oldCap)
Head指向第一个节点,Tail是遍历时用来连接的中间“指针”
*/
Node<K,V> loHead = null, loTail = null;
Node<K,V> hiHead = null, hiTail = null;
Node<K,V> next;
do {
next = e.next;
/**
(e.hash & oldCap)结果只能为oldCap或者0
因为oldCap为2的倍数,在二进制中只有一位为1
所以结果为 0 的Node对象不需要移动位置
而结果为 oldCap 的Node对象需要往前移动oldCap个位置,即:j+oldCap
*/
if ((e.hash & oldCap) == 0) {
if (loTail == null) //当中间节点为null,代表头结点没有初始化
loHead = e;
else
loTail.next = e; //当头结点已经初始化,将自己与e链接起来
loTail = e; //继续往前走,指向当前的e
}
else {
if (hiTail == null)
hiHead = e;
else
hiTail.next = e;
hiTail = e;
}
} while ((e = next) != null);//当e == null时,代表链表到头了,此时退出循环
if (loTail != null) { //如果不为null,将此条链表填入新数组的 j 位置
loTail.next = null;
newTab[j] = loHead;
}
if (hiTail != null) { //如果不为null,将此条链表填入新数组的 j + oldCap 位置
hiTail.next = null;
newTab[j + oldCap] = hiHead;
}
}
}
}
}
return newTab; //返回新表
}
get
public V get(Object key) {
Node<K,V> e;
return (e = getNode(hash(key), key)) == null ? null : e.value;
}
final Node<K,V> getNode(int hash, Object key) {
Node<K,V>[] tab; Node<K,V> first, e; int n; K k;
if ((tab = table) != null && (n = tab.length) > 0 &&
(first = tab[(n - 1) & hash]) != null) { //判断数组是否初始化 和 计算出的下标位置是否为null
if (first.hash == hash &&
((k = first.key) == key || (key != null && key.equals(k)))) //该数组位置的对象就是要找的
return first; //直接返回该对象
if ((e = first.next) != null) { //e指向 first.next,判断是否为null
if (first instanceof TreeNode) //判断是否为红黑树
return ((TreeNode<K,V>)first).getTreeNode(hash, key);
do { //到达这个循环说明,这是个链表
if (e.hash == hash &&
((k = e.key) == key || (key != null && key.equals(k))))
return e;
} while ((e = e.next) != null);
}
}
return null;
}
remove
public V remove(Object key) {
Node<K,V> e;
/**
调用removeNode,如果返回值为null,则返回null
否则返回 removeNode返回值的属性value,即:返回删除对象的value
*/
return (e = removeNode(hash(key), key, null, false, true)) == null ?
null : e.value;
}
/**
* @param hash key的hash值
* @param key key
* @param value 如果匹配值,则忽略要匹配的值
* @param matchValue 如果为真,只有当值相等时才删除
* @param movable 如果为假,删除时不移动其他节点
* @return Node对象 如果没有,则为空
*/
final Node<K,V> removeNode(int hash, Object key, Object value,
boolean matchValue, boolean movable) {
Node<K,V>[] tab; Node<K,V> p; int n, index;
if ((tab = table) != null && (n = tab.length) > 0 &&
(p = tab[index = (n - 1) & hash]) != null) { //判断数组是否初始化 和 计算出的下标位置是否为null
Node<K,V> node = null, e; K k; V v;
if (p.hash == hash &&
((k = p.key) == key || (key != null && key.equals(k)))) //该数组位置的对象就是要删除的
node = p; //找到了,记录在node
else //该数组下标位置下的对象不是要找的
if ((e = p.next) != null) { //查看是否还有后续节点
if (p instanceof TreeNode) //判断是否Wie红黑树
node = ((TreeNode<K,V>)p).getTreeNode(hash, key);
else { //是链表
do {
if (e.hash == hash &&
((k = e.key) == key ||
(key != null && key.equals(k)))) {
node = e; //找到了,记录在node
break;
}
p = e;
} while ((e = e.next) != null);
}
}
//matchValue = false
if (node != null && (!matchValue || (v = node.value) == value ||
(value != null && value.equals(v)))) {
if (node instanceof TreeNode) //判断node节点是否为红黑树
((TreeNode<K,V>)node).removeTreeNode(this, tab, movable);
else if (node == p) //这里判断如果为真代表,node就是数组内节点
tab[index] = node.next;
else //否则就是链表节点,此时p为node的前一个节点
p.next = node.next; //跳过node节点
++modCount; //map结构变化记录加1
--size; //存储的数量-1
afterNodeRemoval(node);
return node; //返回node对象
}
}
return null;//判断数组未初始化 或者 计算出的下标位置为null , 返回 null
}