以下源码版本为java8,与java7版本的HashMap源码有所差异,请区分。
HashMap对象put(K key, V value)源码
public V put(K key, V value) {
return putVal(hash(key), key, value, false, true);
}
/**
* Implements Map.put and related methods
*
* @param hash hash for key
* @param key the key
* @param value the value to put
* @param onlyIfAbsent if true, don't change existing value
* @param evict if false, the table is in creation mode.
* @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)
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执行过程:
1、首先检查hashmap是否为空,为空的话执行resize,相当于初始化一个map。
2、hashmap非空时,计算tab数组下标[(n - 1) & hash],判断数组对象是否为空,为空时新建一个node节点。
3、数组对象非空,tab[i]非空,首先判断该节点的key与即将put的key值是否相同,相同的话先讲tab[i]对应的node存储起来。
4、继续判断tab[i]是否为红黑树对象,若tab节点为红黑树,则执行一次树对象put操作。
5、接下来处理tab[i]节点为链表对象,通过一个计数器binCount统计链表长度。如果tab[i]对象p的next为null,则链表到头了,这个时候新建一个node<key,value>节点为p.next。
6、如果链表长度计数器binCount>7(8-1),换句话说,链表长度大于8时,则进行红黑色转换。如果不满足转换条件,链表种插入新节点完毕,无需其他操作。
7、遍历链表过程中,发现key值相同的节点时,直接break,执行最后面的value覆盖即可。
8、针对存在相同key的节点,执行value覆盖,并返回旧值。
9、针对新增node节点的情况,若tab大小超过阈值(容量*负载因子),执行resize扩容操作,返回null。
https://www.cnblogs.com/jzb-blog/p/6637823.html
resize执行过程:
由于java版本差异,jdk7与jdk8扩容机制还是存在差异,先看看jdk7版本的扩容实现,比较好理解:
java7版本的扩容方式即新建一个数组,遍历老数组元素,计算每个元素的下标[(length-1)&hash],然后按照下标填充元素。
//传入新的容量
void resize(int newCapacity) {
//引用扩容前的Entry数组
Entry[] oldTable = table;
int oldCapacity = oldTable.length;
//扩容前的数组大小如果已经达到最大(2^30)了
if (oldCapacity == MAXIMUM_CAPACITY) {
//修改阈值为int的最大值(2^31-1),这样以后就不会扩容了
threshold = Integer.MAX_VALUE;
return;
}
//初始化一个新的Entry数组
Entry[] newTable = new Entry[newCapacity];
//!!将数据转移到新的Entry数组里
transfer(newTable);
//HashMap的table属性引用新的Entry数组
table = newTable;
//修改阈值
threshold = (int) (newCapacity * loadFactor);
}
/**
这里就是使用一个容量更大的数组来代替已有的容量小的数组,transfer()方法将原有Entry数组的元素拷贝到新的Entry数组里。
**/
void transfer(Entry[] newTable) {
//src引用了旧的Entry数组
Entry[] src = table;
int newCapacity = newTable.length;
//遍历旧的Entry数组
for (int j = 0; j < src.length; j++) {
//取得旧Entry数组的每个元素
Entry<K, V> e = src[j];
if (e != null) {
//释放旧Entry数组的对象引用(for循环后,旧的Entry数组不再引用任何对象)
src[j] = null;
do {
Entry<K, V> next = e.next;
//!!重新计算每个元素在数组中的位置
int i = indexFor(e.hash, newCapacity);
e.next = newTable[i]; //标记[1]
newTable[i] = e; //将元素放在数组上
e = next; //访问下一个Entry链上的元素
} while (e != null);
}
}
}
static int indexFor(int h, int length) {
return h & (length - 1);
}
java8后的版本扩容机制引入了红黑树,扩容原理类似,但实现细节变更了:
参考文章:https://blog.csdn.net/z69183787/article/details/64920074?locationNum=15&fps=1
/**
* Initializes or doubles table size. If null, allocates in
* accord with initial capacity target held in field threshold.
* Otherwise, because we are using power-of-two expansion, the
* elements from each bin must either stay at same index, or move
* with a power of two offset in the new table.
*
* @return the table
*/
final Node<K,V>[] resize() {
Node<K,V>[] oldTab = table;
int oldCap = (oldTab == null) ? 0 : oldTab.length;
int oldThr = threshold;
int newCap, newThr = 0;
if (oldCap > 0) {
// 老容量超过最大容量,则不进行扩容,任其碰撞
if (oldCap >= MAXIMUM_CAPACITY) {
threshold = Integer.MAX_VALUE;
return oldTab;
}
// 扩容两倍
else if ((newCap = oldCap << 1) < MAXIMUM_CAPACITY &&
oldCap >= DEFAULT_INITIAL_CAPACITY)
newThr = oldThr << 1; // double threshold
}
else if (oldThr > 0)
// initial capacity was placed in threshold
newCap = oldThr;
else {
// zero initial threshold signifies using defaults
// 空map初始化
newCap = DEFAULT_INITIAL_CAPACITY;
newThr = (int)(DEFAULT_LOAD_FACTOR * DEFAULT_INITIAL_CAPACITY);
}
if (newThr == 0) {
float ft = (float)newCap * loadFactor;
newThr = (newCap < MAXIMUM_CAPACITY && ft < (float)MAXIMUM_CAPACITY ?
(int)ft : Integer.MAX_VALUE);
}
threshold = newThr;
@SuppressWarnings({"rawtypes","unchecked"})
// 新建扩容数组
Node<K,V>[] newTab = (Node<K,V>[])new Node[newCap];
table = newTab;
if (oldTab != null) {
// 遍历老数组
for (int j = 0; j < oldCap; ++j) {
Node<K,V> e;
if ((e = oldTab[j]) != null) {
oldTab[j] = null;
// 老数组节点为孤点时,即子节点为null,通过e.hash & (newCap - 1)获取数组下标,将节点填充到该数组对象中
if (e.next == null)
newTab[e.hash & (newCap - 1)] = e;
else if (e instanceof TreeNode)
// 节点为红黑色时,内部分裂机制还不理解
((TreeNode<K,V>)e).split(this, newTab, j, oldCap);
else { // preserve order
Node<K,V> loHead = null, loTail = null;
Node<K,V> hiHead = null, hiTail = null;
Node<K,V> next;
do {
next = e.next;
// 元素位置没有发生变化
// 原hash与老容量进行与运算,loHead、loTail位置不变时的头尾节点
if ((e.hash & oldCap) == 0) {
if (loTail == null)
loHead = e;
else
loTail.next = e;
loTail = e;
}
// 元素位置发生变化
// hiHead、hiTail位置变化后新的头节点和尾节点
else {
if (hiTail == null)
hiHead = e;
else
hiTail.next = e;
hiTail = e;
}
} while ((e = next) != null);
// 位置不变时,(e.hash & oldCap) == 0,数组当前下标指向loHead
if (loTail != null) {
loTail.next = null;
newTab[j] = loHead;
}
// 位置变化时,数组下标变为[j + oldCap],指向头节点
if (hiTail != null) {
hiTail.next = null;
newTab[j + oldCap] = hiHead;
}
}
}
}
}
return newTab;
}
ConcurrentHashMap对象put(K key, V value)源码
参考文章:https://www.cnblogs.com/snowater/p/8087166.html
public V put(K key, V value) {
// 核心是调用putVal方法
return putVal(key, value, false);
}
public V putIfAbsent(K key, V value) {
// 如果key存在就不更新value
return putVal(key, value, true);
}
/** Implementation for put and putIfAbsent */
final V putVal(K key, V value, boolean onlyIfAbsent) {
// key或value 为null都是不允许的,因为Forwarding Node就是key和value都为null,是用作标志位的。
if (key == null || value == null) throw new NullPointerException();
// 根据key计算hash值,有了hash就可以计算下标了
int hash = spread(key.hashCode());
int binCount = 0;
// 可能需要初始化或扩容,因此一次未必能完成插入操作,所以添加上for循环
for (Node<K,V>[] tab = table;;) {
Node<K,V> f; int n, i, fh;
// 表还没有初始化,先初始化,lazily initialized
if (tab == null || (n = tab.length) == 0)
tab = initTable();
// 根据hash计算应该插入的index,该位置上还没有元素,则直接插入
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
}
// static final int MOVED = -1; // hash for forwarding nodes
// 说明f为ForwardingNode,只有扩容的时候才会有ForwardingNode出现在tab中,因此可以断定该tab正在进行扩容
else if ((fh = f.hash) == MOVED)
// 协助扩容
tab = helpTransfer(tab, f);
else {
V oldVal = null;
// 节点上锁,hash值相同的节点组成的链表头结点
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) {
// 遍历链表,如果一直没找到,则新建一个Node放到链表结尾
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) {
// 保存旧的value用于返回
oldVal = p.val;
if (!onlyIfAbsent)
p.val = value; // 替换旧的值
}
}
}
}
if (binCount != 0) {
if (binCount >= TREEIFY_THRESHOLD)
// 链表长度超过阈值(默认为8),则需要将链表转为一棵红黑树
treeifyBin(tab, i);
if (oldVal != null)
// 如果只是替换,并未带来节点的增加则直接返回旧的value即可
return oldVal;
break;
}
}
}
// 元素总数加1,并且判断是否需要扩容
addCount(1L, binCount);
return null;
}
put操作总结:
1、初始化判断,将节点直接插入tab[i];
2、节点为链表的话,遍历链表定位相同key的位置,有相同的就替换,没有相同的就将新node插入到链表末尾。
3、节点为红黑树的话,遍历树节点,指向相应的更新、新增节点操作。
get操作总结:
1、通过tab[(n - 1) & hash])获取数组下标;
2、遍历链表、红黑树。