嗯,这是想了很久之后准备写的一篇关于HashMap源码解析的文章,如有不足之处欢迎下方评论!
一、涉及到的常量
/**
* 默认容量
*/
static final int DEFAULT_INITIAL_CAPACITY = 1 << 4;
/**
* 最大容量
*/
static final int MAXIMUM_CAPACITY = 1 << 30;
/**
* 加载因子
*/
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提供了四种构造方法,简单过一遍
/**
* 无参构造
*/
public HashMap() {
this.loadFactor = DEFAULT_LOAD_FACTOR;
}
/**
* 建议使用!!!
*
* @param initialCapacity 容量
*/
public HashMap(int initialCapacity) {
this(initialCapacity, DEFAULT_LOAD_FACTOR);
}
/**
* @param initialCapacity 容量
* @param loadFactor 加载因子
*/
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))
//如果加载因子小于零或者是NaN,抛异常!
throw new IllegalArgumentException("Illegal load factor: " +
loadFactor);
//走到这里,没问题,赋值!
this.loadFactor = loadFactor;
this.threshold = tableSizeFor(initialCapacity);
}
/**
* @param m 泛型
*/
public HashMap(Map<? extends K, ? extends V> m) {
this.loadFactor = DEFAULT_LOAD_FACTOR;
putMapEntries(m, false);
}
三、put方法
1.概述
putVal方法可以分为下面的几个步骤:
- 1.如果哈希表为空,调用resize()创建一个哈希表。
- 2.如果指定参数hash在哈希表中没有对应的头节点,没有发生hash碰撞,直接将键值对插入到哈希表中即可。不再执行3。
- 3.发生hash碰撞
- (1)判断数组中的头节点是否匹配,如果匹配,则记录该头节点后续处理。不再执行(2)、(3)。
- (2)判断数组中结构为红黑树,如果是,则调用红黑树对应的方法插入键值对。不再执行(3)。
- (3)遍历链表,如果没有找到key映射的节点,在链表尾部插入节点,插入后,如果链表的长度大TREEIFY_THRESHOLD这个临界值,则使用treeifyBin方法把链表转为红黑树,退出循环;如果找到了,则记录该节点后续处理,退出循环。
- 4.如果(1)/(3)中记录的节点不为null
- (1)记录该节点的vlaue。
- (2)如果参数onlyIfAbsent为false,或者oldValue为null,则替换该节点的value,否则不替换。
- (3)返回记录下来节点的value。
- 5.判断是否需要扩容。
2.源码解析
当你调用put方法时先运行到这里
public V put(K key, V value) {
return putVal(hash(key), key, value, false, true);
}
下面是重头戏,不要慌,一句一句看!
/**
* Implements Map.put and related methods
*
* @param hash 指定参数key的哈希值
* @param key 指定参数key
* @param value 指定参数value
* @param onlyIfAbsent 如果为true,即使指定参数key在map中已经存在,也不会替换value
* @param evict 如果为false,数组table在创建模式中
* @return 如果value被替换,则返回旧的value,否则返回null。当然,可能key对应的value就是null。
*/
final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
boolean evict) {
HashMap.Node<K, V>[] tab;
HashMap.Node<K, V> p;
int n, i;
//如果哈希表为空,调用resize()创建一个哈希表,并用变量n记录哈希表长度
//哈希表:也就是数组,下面统称为哈希表
if ((tab = table) == null || (n = tab.length) == 0)
//第一次调用put,调用resize()对哈希表初始化
//对n赋值为哈希表的长度
n = (tab = resize()).length;
//i = (n - 1) & hash 通过哈希表的长度以及hash计算新节点在哈希表中的位置(确定数组下标)
//哈希表中该位置为空,直接存入
if ((p = tab[i = (n - 1) & hash]) == null)
tab[i] = newNode(hash, key, value, null);
else {
//哈希表中该位置不为空
HashMap.Node<K, V> e;
K k;
//如果链表第头节点或树的根的key与要插入的数元素key和hash值完全相同,覆盖旧值
if (p.hash == hash &&
((k = p.key) == key || (key != null && key.equals(k))))
//赋值给e
e = p;
//如果该位置是红黑树结构,按照红黑树结构插入,调用putTreeVal
else if (p instanceof HashMap.TreeNode)
//赋值给e
e = ((HashMap.TreeNode<K, V>) p).putTreeVal(this, tab, hash, key, value);
//p与链表头节点既不相同,p也不是treenode的实例,发生hash冲突,遍历链表
else {
//这是一个死循环
for (int binCount = 0; ; ++binCount) {
//遇到p.next == null
if ((e = p.next) == null) {
//将新节点插入到p.next
p.next = newNode(hash, key, value, null);
//如果冲突的节点数已经达到8个,判断是否需要将链表结构转换为红黑树结构
//由于binCount是从0开始计数,所以在做树化判断时binCount的值等于链表长度 - 1(注意此时的链表长度没有算新插入的节点)
if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st
// treeifyBin首先判断当前hashMap的长度,如果不足64,只进行resize,扩容table,
// 如果达到64,那么将冲突的链表结构为红黑树结构
treeifyBin(tab, hash);
break;
}
//如果链表中有一个节点key和新插入的key重复,跳出循环(后续接决定是否替换)
if (e.hash == hash &&
((k = e.key) == key || (key != null && key.equals(k))))
break;
//将p中的next赋值给p,即将链表中的下一个node赋值给p, 继续循环遍历链表中的元素
p = e;
}
}
//不要迷糊!此时的e是发生hash冲突的旧值
if (e != null) { // existing mapping for key
V oldValue = e.value;
//这里来判断是否替换
if (!onlyIfAbsent || oldValue == null)
e.value = value;
// 替换后后回调
afterNodeAccess(e);
// 返回旧值
return oldValue;
}
}
//记录修改次数
++modCount;
//如果元素数量大于临界值,则进行扩容
if (++size > threshold)
resize();
//插入后回调
afterNodeInsertion(evict);
return null;
}
452

被折叠的 条评论
为什么被折叠?



