与List、Set、Queue不同,Map是以<K,V>
结构进行存储,其中Map接口是一个顶级接收,它定义了操作一些Map的基本方法,下面的继承图展示了一些常用的Map继承结构
从源码的角度,来理解这些Map,分析它们之间具体的实现区别
一、HashMap
1.1 基本原理
HashMap的存储在JDK1.7和JDK1.8有一些明显的区别,在JDK1.7中,HashMap用数组+链表的形式进行存储,而在JDK1.8及以上的版本中,HashMap采用数组+链表+红黑树的形式进行存储。
HashMap添加、获取、移除元素的操作,都基于HashMap实现的hash算法,该算法根据KEY计算出一个int类型的hash值,然后根据hash值与数组的最大下标进行按位与运算(并不是用%取模),最后就会得到元素所在数组中的位置,如果当前数组位置没有元素,则直接把元素方法数组中即可;如果当前位置已经有元素,则会在当前位置形成一个链表结构。
而在JDK1.7和JDK1.8以上版本,当hash冲突时,形成链表的方式也有所不同。
JDK1.7主要采用头插法,即新插入的元素放入到数组位置,原有的元素则连接在该元素的后面;
JDK1.8主要采用尾插法,即新加入的元素,直接连接在链表的尾部
static final int hash(Object key) {
int h;
return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
}
通过上面的分析,可以看出,HashMap的元素组成应该包含了hash值、KEY、VALUE以及下一个节点的对象
HashMap中通过静态内部类Node来定义了一个元素节点的结构,该类实现了Map接口里面定义的一个Entry接口
static class Node<K,V> implements Map.Entry<K,V> {
final int hash;
final K key;
V value;
Node<K,V> next;
Node(int hash, K key, V value, Node<K,V> next) {
this.hash = hash;
this.key = key;
this.value = value;
this.next = next;
}
}
HashMap使用table来记录数组上的元素,使用entrySet来记录所有元素
transient Node<K,V>[] table;
transient Set<Map.Entry<K,V>> entrySet;
1.2 构造方法
JDK1.8中HashMap提供了三个构造方法,这三个构造方法中,都会给loadFactor赋值,loadFactor是一个HashMap的一个重要属性,主要用于数组的扩容,如果没有指定loadFactor,就会使用默认的0.75
static final float DEFAULT_LOAD_FACTOR = 0.75f;
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))
throw new IllegalArgumentException("Illegal load factor: " +
loadFactor);
this.loadFactor = loadFactor;
this.threshold = tableSizeFor(initialCapacity);
}
public HashMap(int initialCapacity) {
this(initialCapacity, DEFAULT_LOAD_FACTOR);
}
public HashMap() {
this.loadFactor = DEFAULT_LOAD_FACTOR; // all other fields defaulted
}
在JDK1.8中,如果构造方法中指定了数组的容量,则会去调用tableSizeFor()方法,该方法会对传入的容量进行移位和与运算,最后的得到一个2n大小的数字,这个2n可以等于指定的容量,或者等于一个比cap大但最接近cap的值,比如指定初始容量为25,通过一系列运算得到的容量大小为32。如果指定的容量小于等于16,那么初始容量最小就是16而不是8,即便指定了初始容量为4,经过运算得到的初始容量也是16。
HashMap定了默认的容量DEFAULT_INITIAL_CAPACITY就是16
static final int DEFAULT_INITIAL_CAPACITY = 1 << 4;
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;
}
1.3 PUT方法
1.3.1 JDK1.7的实现
JDK1.7中通过Entry类来存储元素信息,与JDK1.8中的Node是一样的,都是实现了Map.Entry接口
通过createEntry()方法可以看出,先把原来数组中的元素拿出来,然后创建一个新的元素,而老的元素作为新元素的链表上的下一个节点,然后把新的Entiry放置在数组上,这就是典型的头插法
public V put(K key, V value) {
if (table == EMPTY_TABLE) {
inflateTable(threshold);
}
if (key == null)
return putForNullKey(value);
int hash = hash(key);
int i = indexFor(hash, table.length);
modCount++;
addEntry(hash, key, value, i);
return null;
}
void addEntry(int hash, K key, V value, int bucketIndex) {
if ((size >= threshold) && (null != table[bucketIndex])) {
resize(2 * table.length);
hash = (null != key) ? hash(key) : 0;
bucketIndex = indexFor(hash, table.length);
}
createEntry(hash, key, value, bucketIndex);
}
void createEntry(int hash, K key, V value, int bucketIndex) {
Entry<K,V> e = table[bucketIndex];
table[bucketIndex] = new Entry<>(hash, key, value, e);
size++;
}
注:这种头插法的方式,在多线程的情况下,如果两个线程都在进行扩容,都进入到了transfer()方法中时,可能会出现死循环
1.3.2 JDK1.8实现
JDK1.8的putVal()方法要比JDK1.7的put()方法复杂很多,如果没有HashMap还没有初始化,则调用resize()来进行初始化,resize()方法最重要的作用的是用来扩容,HashMap的threshold属性记录了扩容的阈值,它是通过容量和加载因子的乘积得到的。
如果数组中当前位置的没有元素,则直接调用newNode创建一个新的元素,填充数组中对应的位置
如果当前元素的KEY已经存在数组中,则把当前元素的值覆盖掉,然后返回原来的值
如果当前数组位置上的节点是一个TreeNode节点(红黑树节点),则调用putTreeVal()方法,将生成的新的元素节点添加到这个数组节点的红黑树上
如果这是一个普通的Node节点,就遍历数组节点下的链表,查找与当前KEY一样的节点,如果没有找到,就生成一个新的元素节点,添加在链表的最后面,然后会去判断,当前链表上的节点数是否大于等于8个,HashMap中定义了TREEIFY_THRESHOLD = 8,当到达这个阈值时,需要就调用treeifyBin()方法,将这个链表转换成一颗红黑树,关于红黑树的结构,后面写一篇专门的文章进行介绍
最后判断判断HashMap中元素的个数,是否达到了数组扩容的阈值
static final int TREEIFY_THRESHOLD = 8;
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; Node<K,V> p; int n, i;
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;
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) {
if ((e = p.next) == null) {
p.next = newNode(hash, key, value, null);
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;
}
}
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;
}
put()方法中,如果数组元素下链表的长度大于等于8,就会把链表转换成为红黑树,那么什么时候把红黑树转换成链表呢?并不是在remove元素的时候,而是在数组扩容的时候
在resize()方法中,如果数组节点是一个TreeNode,会去调用split()进行rehash,这是时候如果红黑树的节点数小于等于6,就会把红黑树在转换成链表结构
static final int UNTREEIFY_THRESHOLD = 6;
final Node<K,V>[] resize() {
Node<K,V>[] oldTab = table;
……
if (oldTab != null) {
for (int j = 0; j < oldCap; ++j) {
Node<K,V> e;
if ((e = oldTab[j]) != null) {
oldTab[j] = null;
if (e.next == null)
newTab[e.hash & (newCap - 1)] = e;
else if (e instanceof TreeNode)
((TreeNode<K,V>)e).split(this, newTab, j, oldCap);
……
}
}
}
return newTab;
}
final void split(HashMap<K,V> map, Node<K,V>[] tab, int index, int bit) {
……
if (loHead != null) {
if (lc <= UNTREEIFY_THRESHOLD)
tab[index] = loHead.untreeify(map);
else {
tab[index] = loHead;
if (hiHead != null) // (else is already treeified)
loHead.treeify(tab);
}
}
if (hiHead != null) {
if (hc <= UNTREEIFY_THRESHOLD)
tab[index + bit] = hiHead.untreeify(map);
else {
tab[index + bit] = hiHead;
if (loHead != null)
hiHead.treeify(tab);
}
}
……
}
二、LinkedHashMap
2.1 存储结构
LinkedHashMap继承了HashMap,它继承了HashMap的所有方法,并没有进行重写,唯一不同的点在于它的存储结构发生了变化
LinkedHashMap的内部类Entry继承了HashMap的Node类,同时又新增了两个before和after属性,这两个属性分别指向前后两个Entry节点,这与Node节点里面的next属性不同,next属性维持的是链表和红黑树的结构,而before和after存储的是节点插入的顺序
static class Entry<K,V> extends HashMap.Node<K,V> {
Entry<K,V> before, after;
Entry(int hash, K key, V value, Node<K,V> next) {
super(hash, key, value, next);
}
}
LinkedHashMap中使用head和tail来记录最早插入和最后插入的元素节点
/**
* The head (eldest) of the doubly linked list.
*/
transient LinkedHashMap.Entry<K,V> head;
/**
* The tail (youngest) of the doubly linked list.
*/
transient LinkedHashMap.Entry<K,V> tail;
2.2 保序实现
在HashMap的putVal()方法中,最后会去调用afterNodeInsertion()方法,HashMap中并没有实现该方法,而在LinkedHashMap中,对方法进行了实现
同类型的还有afterNodeAccess()和afterNodeRemoval()方法
afterNodeAccess()方法会在元素节点的值发生变化时调用,比如replace()方法,或者put()方法的KEY相同时,都会调用该方法
而afterNodeRemoval()方法会在removeNode()方法中进行调用
void afterNodeAccess(Node<K,V> p) { }
void afterNodeInsertion(boolean evict) { }
void afterNodeRemoval(Node<K,V> p) { }
final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
boolean evict) {
……
++modCount;
if (++size > threshold)
resize();
afterNodeInsertion(evict);
return null;
}
LinkedHashMap中重写了newNode()方法,当调用put()方式时,会调用重写的newNode()方法,在该方法中,会生成一个LinkedHashMap.Entry的对象,然后调用linkNodeLast()方法把新插入的Entry节点,放置在链表的最后面,这样在遍历的时候,就可以通过插入的顺序来遍历了。
Node<K,V> newNode(int hash, K key, V value, Node<K,V> e) {
LinkedHashMap.Entry<K,V> p =
new LinkedHashMap.Entry<K,V>(hash, key, value, e);
linkNodeLast(p);
return p;
}
private void linkNodeLast(LinkedHashMap.Entry<K,V> p) {
LinkedHashMap.Entry<K,V> last = tail;
tail = p;
if (last == null)
head = p;
else {
p.before = last;
last.after = p;
}
}
LinkedHashMap的afterNodeAccess()方法,在replace()和put()方法KEY相同时都会调用该方法,我们看下该方法具体实现了什么
在该方法中,如果accessOrder属性为true,会把操作的元素从顺序链表的现有位置移动到最新的位置。
void afterNodeAccess(Node<K,V> e) { // move node to last
LinkedHashMap.Entry<K,V> last;
if (accessOrder && (last = tail) != e) {
LinkedHashMap.Entry<K,V> p =
(LinkedHashMap.Entry<K,V>)e, b = p.before, a = p.after;
p.after = null;
if (b == null)
head = a;
else
b.after = a;
if (a != null)
a.before = b;
else
last = b;
if (last == null)
head = p;
else {
p.before = last;
last.after = p;
}
tail = p;
++modCount;
}
}
而accessOrder属性,只有在下面的构造方法可以指定为true,而其他的构造方法,默认都是false
public LinkedHashMap(int initialCapacity,
float loadFactor,
boolean accessOrder) {
super(initialCapacity, loadFactor);
this.accessOrder = accessOrder;
}
三、TreeMap
TreeMap实现了SortedMap接口,可以进行排序,而排序自然需要一个比较强,在创建TreeMap实例的时候,可以自定义比较器的实现
public TreeMap(Comparator<? super K> comparator) {
this.comparator = comparator;
}
3.1 存储结构
TreeMap的存储结构,采用红黑树来实现,其内部类Entry定义了元素的KEY和VALUE,以及它的父节点、左节点、右节点以及节点颜色
public class TreeMap<K,V>
extends AbstractMap<K,V>
implements NavigableMap<K,V>, Cloneable, java.io.Serializable{
private final Comparator<? super K> comparator;
private transient Entry<K,V> root;
}
static final class Entry<K,V> implements Map.Entry<K,V> {
K key;
V value;
Entry<K,V> left;
Entry<K,V> right;
Entry<K,V> parent;
boolean color = BLACK;
Entry(K key, V value, Entry<K,V> parent) {
this.key = key;
this.value = value;
this.parent = parent;
}
}
3.2 PUT方法
在put()方法中,会根据是否指定了comparator比较器分情况进行比较
如果指定了比较器,就去调用比较器的compare()方法进行比较,如果当前KEY比节点的KEY小,去获取左节点继续比较,如果找到了相同KEY的,就直接覆盖原有的值返回
如果没有指定比较器,就会把KEY转换成Comparable类型,所以,在创建TreeMap实例的时候,如果没有指定比较器,就需要KEY必须实现Comparable接口的compare()方法,不然就会报ClassCastException异常
如果没有找到与当前KEY匹配的,就新创建一个Entry实例,然后根据前面与最后一个节点比较的结果,将其添加到左或右节点上,然后调用fixAfterInsertion()方法,来对红黑树进行平衡
public V put(K key, V value) {
Entry<K,V> t = root;
if (t == null) {
compare(key, key); // type (and possibly null) check
root = new Entry<>(key, value, null);
size = 1;
modCount++;
return null;
}
int cmp;
Entry<K,V> parent;
// split comparator and comparable paths
Comparator<? super K> cpr = comparator;
if (cpr != null) {
do {
parent = t;
cmp = cpr.compare(key, t.key);
if (cmp < 0)
t = t.left;
else if (cmp > 0)
t = t.right;
else
return t.setValue(value);
} while (t != null);
}
else {
if (key == null)
throw new NullPointerException();
@SuppressWarnings("unchecked")
Comparable<? super K> k = (Comparable<? super K>) key;
do {
parent = t;
cmp = k.compareTo(t.key);
if (cmp < 0)
t = t.left;
else if (cmp > 0)
t = t.right;
else
return t.setValue(value);
} while (t != null);
}
Entry<K,V> e = new Entry<>(key, value, parent);
if (cmp < 0)
parent.left = e;
else
parent.right = e;
fixAfterInsertion(e);
size++;
modCount++;
return null;
}
四、HashTable
HashTable虽然没有继承HashMap,它的实现方式与JDK1.7的HashMap几乎一致,存储采用数组+链表的形式,并不会像JDK1.8的HashMap那样有链表和红黑树的转换,所以实现上会简单很多
但是与HashMap相比,HashTable是一个线程安全的Map集合,它所有对外提供的方法都加了synchronized关键字进行同步
在HashTable的addEntry()方法中,如果需要进行扩容,会进行rehash,并且HashTable中计算数组下标的方式才是按照数组长度进行取模
最后采用头插法将元素插入到数组中
public synchronized V put(K key, V value) {
// Make sure the value is not null
if (value == null) {
throw new NullPointerException();
}
// Makes sure the key is not already in the hashtable.
Entry<?,?> tab[] = table;
int hash = key.hashCode();
int index = (hash & 0x7FFFFFFF) % tab.length;
@SuppressWarnings("unchecked")
Entry<K,V> entry = (Entry<K,V>)tab[index];
for(; entry != null ; entry = entry.next) {
if ((entry.hash == hash) && entry.key.equals(key)) {
V old = entry.value;
entry.value = value;
return old;
}
}
addEntry(hash, key, value, index);
return null;
}
private void addEntry(int hash, K key, V value, int index) {
modCount++;
Entry<?,?> tab[] = table;
if (count >= threshold) {
// Rehash the table if the threshold is exceeded
rehash();
tab = table;
hash = key.hashCode();
index = (hash & 0x7FFFFFFF) % tab.length;
}
// Creates the new entry. 头插法
Entry<K,V> e = (Entry<K,V>) tab[index];
tab[index] = new Entry<>(hash, key, value, e);
count++;
}
五、ConcurrentHashMap
ConcurrentHashMap也是一个线程安全的Map集合,但它并不是像HashTable那样进行同步控制。在JDK1.7和JDK1.8中它们的实现有着本质的区别,JDK1.7采用分段锁来进行同步,而JDK1.8使用CAS+synchronized来进行同步,我们从源码来分析这两种实现
5.1 分段锁(JDK1.7)
5.1.1 存储结构
在JDK1.7中,每个元素对应的存储结构如下:
static final class HashEntry<K,V> {
final int hash;
final K key;
volatile V value;
volatile HashEntry<K,V> next;
HashEntry(int hash, K key, V value, HashEntry<K,V> next) {
this.hash = hash;
this.key = key;
this.value = value;
this.next = next;
}
}
而在JDK1.7的ConcurrentHashMap中有一个非常重要的类Segment,这个类继承自ReentrantLock,具有加索的共功能。而这个Segment里面的属性结构与普通的HashMap很相似,也可以把Segment就看成HashMap的结构
static final class Segment<K,V> extends ReentrantLock implements Serializable {
transient volatile HashEntry<K,V>[] table;
transient int count;
transient int modCount;
transient int threshold;
final float loadFactor;
Segment(float lf, int threshold, HashEntry<K,V>[] tab) {
this.loadFactor = lf;
this.threshold = threshold;
this.table = tab;
}
……
}
在在ConcurrentHashMap中,有一个Segment数组,这个数组记录了所有元素对应的分组
final Segment<K,V>[] segments;
5.1.2 检索分段
在ConcurrentHashMap的put()方法中,会根据计算得到的hash值,然后计算KEY对应的分段,这里面用到了Unsafe类的本地方法,如果没有现成的分段,就调用ensureSegment()方法来生成一个分段
public V put(K key, V value) {
Segment<K,V> s;
if (value == null)
throw new NullPointerException();
int hash = hash(key);
int j = (hash >>> segmentShift) & segmentMask;
if ((s = (Segment<K,V>)UNSAFE.getObject // nonvolatile; recheck
(segments, (j << SSHIFT) + SBASE)) == null) // in ensureSegment
s = ensureSegment(j);
return s.put(key, hash, value, false);
}
在ensureSegment()方法中,会把第一个分段的基本信息,HashEntry数组的容量、加载因子都拿出来,作为基础参数,生成一个新的Segment,然后利用CAS将新的Segment填充到数组中
private Segment<K,V> ensureSegment(int k) {
final Segment<K,V>[] ss = this.segments;
long u = (k << SSHIFT) + SBASE; // raw offset
Segment<K,V> seg;
if ((seg = (Segment<K,V>)UNSAFE.getObjectVolatile(ss, u)) == null) {
Segment<K,V> proto = ss[0]; // use segment 0 as prototype
int cap = proto.table.length;
float lf = proto.loadFactor;
int threshold = (int)(cap * lf);
HashEntry<K,V>[] tab = (HashEntry<K,V>[])new HashEntry[cap];
if ((seg = (Segment<K,V>)UNSAFE.getObjectVolatile(ss, u))
== null) { // recheck
Segment<K,V> s = new Segment<K,V>(lf, threshold, tab);
while ((seg = (Segment<K,V>)UNSAFE.getObjectVolatile(ss, u))
== null) {
if (UNSAFE.compareAndSwapObject(ss, u, null, seg = s))
break;
}
}
}
return seg;
}
5.1.3 put方法实现
ConcurrentHashMap的put()方法中,获取到对应的Segment后,会去调用Segment类的put()方法
首先会调用tryLock()进行尝试加索,如果尝试失败,在调用scanAndLockForPut(),在scanAndLockForPut方法中也是先调用tryLock()进行尝试加锁,如果失败次数超过MAX_SCAN_RETRIES值后,直接调用lock()方法进行加锁
下面的代码就是正常的遍历查找,找不到就创建一个新的HashEntry,然后加入到当前分段的HashEntry数组中,这个数组也是才是通过数组+链表实现的,数组下标冲突时,采用头插法将元素添加到链表中
static final int MAX_SCAN_RETRIES =
Runtime.getRuntime().availableProcessors() > 1 ? 64 : 1;
final V put(K key, int hash, V value, boolean onlyIfAbsent) {
HashEntry<K,V> node = tryLock() ? null :
scanAndLockForPut(key, hash, value);
V oldValue;
try {
HashEntry<K,V>[] tab = table;
int index = (tab.length - 1) & hash;
HashEntry<K,V> first = entryAt(tab, index);
for (HashEntry<K,V> e = first;;) {
if (e != null) {
K k;
if ((k = e.key) == key ||
(e.hash == hash && key.equals(k))) {
oldValue = e.value;
if (!onlyIfAbsent) {
e.value = value;
++modCount;
}
break;
}
e = e.next;
}
else {
if (node != null)
node.setNext(first);
else
node = new HashEntry<K,V>(hash, key, value, first);
int c = count + 1;
if (c > threshold && tab.length < MAXIMUM_CAPACITY)
rehash(node);
else
setEntryAt(tab, index, node);
++modCount;
count = c;
oldValue = null;
break;
}
}
} finally {
unlock();
}
return oldValue;
}
5.2 CAS+synchronized(JDK1.8)
5.2.1 存储结构
JDK1.8中的ConcurrentHashMap与HashMap一样,采用数组+链表+红黑树的形式进行存储
transient volatile Node<K,V>[] table;
static class Node<K,V> implements Map.Entry<K,V> {
final int hash;
final K key;
volatile V val;
volatile Node<K,V> next;
Node(int hash, K key, V val, Node<K,V> next) {
this.hash = hash;
this.key = key;
this.val = val;
this.next = next;
}
}
static final class TreeNode<K,V> extends Node<K,V> {
TreeNode<K,V> parent; // red-black tree links
TreeNode<K,V> left;
TreeNode<K,V> right;
TreeNode<K,V> prev; // needed to unlink next upon deletion
boolean red;
TreeNode(int hash, K key, V val, Node<K,V> next,
TreeNode<K,V> parent) {
super(hash, key, val, next);
this.parent = parent;
}
}
5.2.2 put方法实现
同样根据hash值先计算出对应的数组的下标,如果当前数组的位置没有元素,则直接通过casTabAt()方法添加元素,如果此时有别的现成抢先一步把数组当前位置填充了值,casTabAt()方法就会返回false,那么下一次循环时,就会使用synchronized关键字对数组中进行同步,保证只有一个线程可以操作
final V putVal(K key, V value, boolean onlyIfAbsent) {
if (key == null || value == null) throw new NullPointerException();
int hash = spread(key.hashCode());
int binCount = 0;
for (Node<K,V>[] tab = table;;) {
Node<K,V> f; int n, i, fh;
if (tab == null || (n = tab.length) == 0)
tab = initTable();
else if ((f = tabAt(tab, i = (n - 1) & hash)) == null) {
if (casTabAt(tab, i, null,
new Node<K,V>(hash, key, value, null)))
break; // no lock when adding to empty bin
}
else if ((fh = f.hash) == MOVED)
tab = helpTransfer(tab, f);
else {
V oldVal = null;
synchronized (f) {
if (tabAt(tab, i) == f) {
if (fh >= 0) {
binCount = 1;
for (Node<K,V> e = f;; ++binCount) {
K ek;
if (e.hash == hash &&
((ek = e.key) == key ||
(ek != null && key.equals(ek)))) {
oldVal = e.val;
if (!onlyIfAbsent)
e.val = value;
break;
}
Node<K,V> pred = e;
if ((e = e.next) == null) {
pred.next = new Node<K,V>(hash, key,
value, null);
break;
}
}
}
else if (f instanceof TreeBin) {
Node<K,V> p;
binCount = 2;
if ((p = ((TreeBin<K,V>)f).putTreeVal(hash, key,
value)) != null) {
oldVal = p.val;
if (!onlyIfAbsent)
p.val = value;
}
}
}
}
if (binCount != 0) {
if (binCount >= TREEIFY_THRESHOLD)
treeifyBin(tab, i);
if (oldVal != null)
return oldVal;
break;
}
}
}
addCount(1L, binCount);
return null;
}
添加完元素之后,需要判断链表是否需要转成红黑树,在转红黑树的时候,同样先取出数组上的元素,然后通过synchronized对其进行同步
if (binCount != 0) {
if (binCount >= TREEIFY_THRESHOLD)
treeifyBin(tab, i);
if (oldVal != null)
return oldVal;
break;
}
private final void treeifyBin(Node<K,V>[] tab, int index) {
Node<K,V> b; int n, sc;
if (tab != null) {
if ((n = tab.length) < MIN_TREEIFY_CAPACITY)
tryPresize(n << 1);
else if ((b = tabAt(tab, index)) != null && b.hash >= 0) {
synchronized (b) {
if (tabAt(tab, index) == b) {
TreeNode<K,V> hd = null, tl = null;
for (Node<K,V> e = b; e != null; e = e.next) {
TreeNode<K,V> p =
new TreeNode<K,V>(e.hash, e.key, e.val,
null, null);
if ((p.prev = tl) == null)
hd = p;
else
tl.next = p;
tl = p;
}
setTabAt(tab, index, new TreeBin<K,V>(hd));
}
}
}
}
}