前言
由于Java 1.7
和Java 1.8
的HashMap
的HashMap
中的put()
和get()
方法在实现上差异很大,所以本文将于分别分析这两个版本的put()
和get()
f方法
下面将会分析这部分的源码,如果觉得源码分析内容太啰嗦,可以跳过源码部分,直接看源码下面的总结。
put()方法源码分析
HashMap
的put()
方法是我们最常用的方法,但是put()
方法是怎么工作的呢?
Java 1.7
put()方法
public V put(K key, V value) {
if (key == null)// 处理key为null的情况
return putForNullKey(value);
// 计算key的hash值
int hash = hash(key);
// 计算命中table的索引
int i = indexFor(hash, table.length);
// 遍历命中的链表
for (Entry<K,V> e = table[i]; e != null; e = e.next) {
Object k;
// 存在key和hash值相同则替换value
if (e.hash == hash && ((k = e.key) == key || key.equals(k))) {
V oldValue = e.value;
e.value = value;
e.recordAccess(this);
return oldValue;
}
}
// 记录结构性变化
modCount++;
// 增加新链表
addEntry(hash, key, value, i);
// 上一次节点不存在,返回null
return null;
}
put()
方法实际上是
- 若
key
为null
时,直接调用putForNullKey()
方法。否则进入下一步 - 调用
hash()
方法获取key
的hash
值,进入下一步 - 调用
indexFor()
计算命中的散列表table
的索引 - 遍历链表,如果链表不存在或链表不存在
key
和hash
值相同的节点,则创建新的链表或尾部添加节点,否则替换对应节点的value
putForNullKey()
private V putForNullKey(V value) {
// 遍历链表,但是命中的散列表的索引和key的hash值为0
// 后续逻辑与`put()`类似
for (Entry<K,V> e = table[0]; e != null; e = e.next) {
if (e.key == null) {
V oldValue = e.value;
e.value = value;
e.recordAccess(this);
return oldValue;
}
}
modCount++;
addEntry(0, null, value, 0);
return null;
}
putForNullKey
只是将命中散列表table
的索引和key
的hash
值都设置为0
,其他逻辑与put()
方法后续的逻辑一致。
indexFor()方法
/**
* 计算命中散列表的索引
*/
static int indexFor(int h, int length) {
// 等价于length%h
return h & (length-1);
}
hash()方法
/**
* hash值计算方法
*/
final int hash(Object k) {
int h = 0;
// 使用替代的hash方法
if (useAltHashing) {
if (k instanceof String) {
// 为字符串则使用特定的hash方法
return sun.misc.Hashing.stringHash32((String) k);
}
// 使用特定的hash种子计算hash值
h = hashSeed;
}
h ^= k.hashCode();
// 这部分代码是为了减少哈希碰撞
h ^= (h >>> 20) ^ (h >>> 12);