阅读源码的时候应该关注HashMap的几个特征:
- 一些重要的静态成员变量
// HashMap的最大容量:2^30
static final int MAXIMUM_CAPACITY = 1 << 30;
// HashMap的最小容量:16
static final int DEFAULT_INITIAL_CAPACITY = 1 << 4;
// 默认的负载因子,主要用于计算HashMap的threhold
static final float DEFAULT_LOAD_FACTOR = 0.75f;
// 当节点中至少有八个元素时,节点从链表转化为红黑树的阀值
static final int TREEIFY_THRESHOLD = 8;
// 当节点中数量为六时,红黑树蜕化为链表的阀值
static final int UNTREEIFY_THRESHOLD = 6;
// 红黑树的最小容量
static final int MIN_TREEIFY_CAPACITY = 64;
- HashMap中的一些静态方法
// 将Hash码的高位向右移与低位做异或,这样做的目的是为了保留高位特征,降低hash冲突
static final int hash(Object key) {
int h;
return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
}
// 返回大于cap的最小二次方
static final int tableSizeFor(int cap) {
int n = cap - 1;
n |= n >>> 1;
n |= n >>> 2;
n |= n >>> 4;
n |= n >>> 8;
n |= n >>> 16;
return (n < 0) ? 1 : (n >= MAXIMUM_CAPACITY) ? MAXIMUM_CAPACITY : n + 1;
}
- 初始化HashMap的过程
// 手动指定HashMap的容量和负载因子
public HashMap(int initialCapacity, float loadFactor)
// 手动指定HashMap的容量,使用默认负载因子
public HashMap(int initialCapacity)
// 容量和负载因子使用默认大小
public HashMap()
- 添加元素的过程
/**
* Implements Map.put and related methods.
*
* @param hash key的hash码
* @param key 添加元素的key
* @param value 添加元素的value
* @param onlyIfAbsent 如果为true,不改变已存在的valye
* @param evict 如果为false,说明是刚创建向hashMap中添加元素
* @return previous value, or null if none
*/
final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
boolean evict) {
Node<K,V>[] tab; Node<K,V> p; int n, i;
if ((tab = table) == null || (n = tab.length) == 0)
//HashMap中元素为空 或 HashMap长度为0
n = (tab = resize()).length; // 初始化hashMap
if ((p = tab[i = (n - 1) & hash]) == null)
//HashMap已经初始化完毕,此时tab[(n-1)&hash]] 元素为空
tab[i] = newNode(hash, key, value, null);
//创建新的节点
else { //HashMap 初始化完毕,节点位置已经存在元素(hash冲突)
Node<K,V> e; K k;
if (p.hash == hash &&
((k = p.key) == key || (key != null && key.equals(k)))) //判断hash值 and key值与预期加入位置元素是否一致,如果一致做value替换
e = p;
else if (p instanceof TreeNode)
// hash值 and key 与预期加入元素不一致,考虑链表是否已经转换为了红黑树,进入红黑树的节点插入逻辑
e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);
else {
/**尚未转换为红黑树,说明此时hashMap的(n-1)&hash位置元素长度小于8,做链表的遍历。一般两种情况会到这个逻辑:不同的key有相同的hashcode 相同的hashcode有不同的key,都说明当前指向的节点元素与要加入元素是不同的元素**/
for (int binCount = 0; ; ++binCount) {
if ((e = p.next) == null) {
//如果p的next节点为空 将新的元素插入p的next节点中
p.next = newNode(hash, key, value, null);
if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st 判断当前节点数量是否等于转换为红黑树的阀值
treeifyBin(tab, hash);
// 等于阀值 则转换为红黑树
break;
}
// 此时说明当前节点有next节点,需要把要加入的元素移动到链表尾部 ,判断e的hash与key 是否与待加入元素一致
if (e.hash == hash &&
((k = e.key) == key || (key != null && key.equals(k))))
break; //相同 跳出循环
p = e; // 不相同,继续向后遍历
}
}
if (e != null) { // existing mapping for key
// 这里主要是做key 相同 值替换的逻辑
V oldValue = e.value;
if (!onlyIfAbsent || oldValue == null)
e.value = value;
// LinkedHashMap 要将节点后移
afterNodeAccess(e);
return oldValue;
}
}
++modCount; // 计数器加1 size 加一
if (++size > threshold)
resize(); // hashMap中元素大于临界值 要考虑扩容
afterNodeInsertion(evict);
return null;
}
putVal()的几个作用:
- 为刚初始化的HashMap做resize()
- 保证HashMap中key的唯一性
- 若发生hash碰撞:hash值不同 但映射到同一个位置;key不同,但计算出相同的hash
这两种情况都说明加入的元素key是唯一的。但需要将新的元素加入对应桶的尾部,桶对应的链表长度大于等于8时,会出发treeifyBin的操作,将桶对应的链表转换为红黑树 - 未发生hash碰撞,则说明元素唯一,可直接将key,value加入HashMap中,需要考虑扩容(size>threshold)问题
- 删除元素的过程
- 查找元素的过程