前言
上问中介绍了map的get方法以及hash方法的思想,简单的提及了关于二叉查询树以及红黑树的结构,本文将会介绍hashmap中的put方法。
正文
先看下源码
// 调用hash处理后,调用putval方法
public V put(K key, V value) {
return putVal(hash(key), key, value, false, true);
}
接下来看下调用的putVal
方法
final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
boolean evict) {
Node<K,V>[] tab; Node<K,V> p; int n, i;
// 判断有没有初始化,没有就初始化map,调用resize扩容方法
if ((tab = table) == null || (n = tab.length) == 0)
n = (tab = resize()).length;
// 计算出存储下标的位置,并判断数组下标内是否有数据
if ((p = tab[i = (n - 1) & hash]) == null)
// 下标内为空,就创建数据,将入参放进去
tab[i] = newNode(hash, key, value, null);
else {
Node<K,V> e; K k;
// 判断下标内的头结点是否与入参的hash,key相等,相等就覆盖
if (p.hash == hash &&
((k = p.key) == key || (key != null && key.equals(k))))
e = p;
// 不相等,判断是否树结点,是放入红黑树中
else if (p instanceof TreeNode)
e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);
else {
// 不是树结点,那就是链表,遍历链表
for (int binCount = 0; ; ++binCount) {
// 链表没有遍历到与入参相等的hash与科研,就在链表最后插入
if ((e = p.next) == null) {
p.next = newNode(hash, key, value, null);
// 链表长度大于阈值的话即8,就调用treeifyBin方法尝试转换成红黑树
if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st
treeifyBin(tab, hash);
break;
}
// 遍历找到链表中有节点的与入参相同,就停止遍历
if (e.hash == hash &&
((k = e.key) == key || (key != null && key.equals(k))))
break;
p = e;
}
}
// 对e的value进行处理
if (e != null) { // existing mapping for key
V oldValue = e.value;
// value为null或者onlyIfAbsent为false,onlyIfAbsent意思是的值不存在才插入
if (!onlyIfAbsent || oldValue == null)
e.value = value;
// 回调 linkhashmap的方法,将e节点移交链表尾部
afterNodeAccess(e);
return oldValue;
}
}
// 修改次数+1
++modCount;
// 判断当前的容量是否超过阈值,超过进行扩容
if (++size > threshold)
resize();
// 回调 linkhashmap的方法,删除旧节点
afterNodeInsertion(evict);
return null;
}
resize扩容方法做为hashmap其中的一个核心点本文将不展开,后续将会对其详细剖析。treeifyBin
转换成红黑树也放到后续介绍红黑树的时候具体展开,继续往下,先看下afterNodeAccess
这个方法。
// hashmap 定义的方法由linkhashmap实现,将入参节点做成链表的尾结点
void afterNodeAccess(Node<K,V> e) { // move node to last
LinkedHashMap.Entry<K,V> last;
// accessOrder为ture 以及参数e不是链表尾节点才走入内部
// accessOrder为true代表查询方式排序
// tail为链表的尾节点
if (accessOrder && (last = tail) != e) {
LinkedHashMap.Entry<K,V> p =
(LinkedHashMap.Entry<K,V>)e, b = p.before, a = p.after;
p.after = null;
// e的before即指向上个节点为空,说明e相当于头节点
if (b == null)
// 链表的将a节点变更为头节点
head = a;
else
// 将b的下个节点指向a,以前是指向e
b.after = a;
if (a != null)
// 将a的befor从e变更成b
a.before = b;
else
// last结点指向b,last节点之前存的是tail尾节点
last = b;
// 尾节点为空
if (last == null)
// p赋值为头节点
head = p;
else {
// p做为last的后一个节点
p.before = last;
last.after = p;
}
// 即尾节点指向p
tail = p;
// 修改数+1
++modCount;
}
}
继续,往下看afterNodeInsertion
这个方法,这个方法的是删除最旧的数据
void afterNodeInsertion(boolean evict) { // possibly remove eldest
LinkedHashMap.Entry<K,V> first;
if (evict && (first = head) != null && removeEldestEntry(first)) {
K key = first.key;
removeNode(hash(key), key, null, false, true);
}
}
然而这个方法却不会调用,看下removeEldestEntry(first))
这个方法
protected boolean removeEldestEntry(Map.Entry<K,V> eldest) {
return false;
}
直接返回false,所以是不会走进if里面,如果硬要实现,那只能实现linkhashmap的removeEldestEntry
方法。
不知是否有读者疑惑modCount这个字段是干嘛的,笔者也疑惑过,后查询不少资料才确定modCount这个字段的含义。众所周知,hashmap并非线程安全的map,不安全即存在并发问题。所以hashmap内部迭代的时候就通过modCount的值来判断是否有并发问题。
先看下内部类HashIterator
的源码
abstract class HashIterator {
Node<K,V> next; // next entry to return
Node<K,V> current; // current entry
int expectedModCount; // for fast-fail
int index; // current slot
HashIterator() {
// 初始化的时候将modcount值赋给expectedModCount
expectedModCount = modCount;
Node<K,V>[] t = table;
current = next = null;
index = 0;
if (t != null && size > 0) { // advance to first entry
do {} while (index < t.length && (next = t[index++]) == null);
}
}
public final boolean hasNext() {
return next != null;
}
final Node<K,V> nextNode() {
Node<K,V>[] t;
Node<K,V> e = next;
// 如果后续有修改过modCount,导致两边不相等则抛异常
if (modCount != expectedModCount)
throw new ConcurrentModificationException();
if (e == null)
throw new NoSuchElementException();
if ((next = (current = e).next) == null && (t = table) != null) {
do {} while (index < t.length && (next = t[index++]) == null);
}
return e;
}
public final void remove() {
Node<K,V> p = current;
if (p == null)
throw new IllegalStateException();
if (modCount != expectedModCount)
throw new ConcurrentModificationException();
current = null;
K key = p.key;
removeNode(hash(key), key, null, false, false);
expectedModCount = modCount;
}
}
这段代码思想很简单,就是fast-fail机制,通过判断modCount是否等于expectedModCount,在put,以及remove等方法操作时,会修改modCount,所以在迭代器的nextNode(),remove()
方法里,都先执行判断。
expectedModCount是否与modCount相等,如果有其他线程的操作,导致modCount的值修改,就抛出异常。
后话
本文中介绍了hashmap的put方法的思想,以及介绍了modCount字段的含义,接下来剖析一下hashmap的核心点之一resize扩容方法以及1.8中的重压特性红黑树的数据结构。