在讲解具体的源码之前,先来对其jdk的介绍做一个翻译,下面是jdk源码对其介绍:
HashMap作为Map接口的实现,提供了所有的可选择的映射的操作,大致上HaspMap和HashTable相等(不是线程安全和允许空值除外),HashMap对map的key的顺序没做出任何保证,特别是不会保证顺序恒久不变。
假定hash方法可以将元素适当的散列在各个桶之间,那么hashmap的get()和 put()方法可以提供常数时间的表现,Hashmap的迭代集合的时间和其容量以及键值对的数量是成正比的,所以,如果迭代性能比较重要,那么不要讲初始容量设置的太高(或者加载因子太低)是非常重要的。
默认的加载因子(0.75)在空间和时间上之间做了一个很好的权衡,空间上在增加的越多,就会增加查找代价(这一点在hashmap的许多操作方法都有体现,例如get put),
在设置初始容量的时候,我们所期望的节点的数量和加载因子应该被考虑在内,所以为了最小化扩容的次数,如果初始化的容量比最大节点数量除以加载因子的值要大,那么扩容将不再发生
如果HashMap中存储大量的键值对,使用一个足够大的容量去创建去存储键值对要比扩容更高效,
但是有一点需要注意,HashMap不是线程安全的,如果多个线程同时访问HashMap,而且至少有一个线程在结构上修改HashMap,在外部必须要被同步,所谓的在结构上修改是指增加、删除一个或者多个键值对
并不是指修改一个已经存在的键的值
HashMap结构图:
在jdk1.7中,Hashmap的原理是基于哈希表的,哈希表是数组和链表的结合,也可以说是一个链表的数组,数组的每一个元素都是一个链表,通过数组下标,我们可以快速定位到某个元素所在的桶,然后再从链表头部通过比较key和value,定位到元素,链表是为了解决哈希碰撞的问题,这里可以看出,如果链表过长,则会降低检索效率。当元素个数达到扩容阈值(数组容量*加载因子)的时候,HashMap会进行扩容操作,将原先数组长度扩大为两倍的长度,对现有元素进行重新哈希
- 2 成员变量
//默认的初始容量,大小必须是2的幂次方,其实数组的长度
static final int DEFAULT_INITIAL_CAPACITY = 16;
//最大容量,如果一个更大的值通过构造函数设置的话,那么就是用这个最大容量
//数组的最大长度
static final int MAXIMUM_CAPACITY = 1 << 30;
//默认的加载因子
static final float DEFAULT_LOAD_FACTOR = 0.75f;
//用来存储链表的数组,必要时会发生扩容,大小必须是2的倍数
transient Entry<K,V>[] table;
//键值对的数量,hashmap的大小
transient int size;
//扩容的阈值(当前容量 * 加载因子),大于等于该值的时候会发生扩容
int threshold;
//加载因子
final float loadFactor;
//hashmap被修改的次数
transient int modCount;
//在键为string的时候,且使用可替代的hash算法的情况下,hashmap默认的扩容阈值,可替代的hash算法减少了由于薄弱的hash算法产生的hash冲突,这个值可以通过dk.map.althashing.threshold来覆盖,
当jdk.map.althashing.threshold为1的时候,一定会使用可替代hash,-1从不会使用
static final int ALTERNATIVE_HASHING_THRESHOLD_DEFAULT = Integer.MAX_VALUE;
/**
* holds values which can't be initialized until after VM is booted.
* Hashmap定义的内部类,定义了一些在虚拟机启动之前不能被初始化的值
*/
private static class Holder {
// Unsafe mechanics
/**
* Unsafe utilities
*/
static final sun.misc.Unsafe UNSAFE;
/**
* Offset of "final" hashSeed field we must set in readObject() method.
*/
static final long HASHSEED_OFFSET;
/**
* Table capacity above which to switch to use alternative hashing.
*/
static final int ALTERNATIVE_HASHING_THRESHOLD;
static {
String altThreshold = java.security.AccessController.doPrivileged(
new sun.security.action.GetPropertyAction(
"jdk.map.althashing.threshold"));
int threshold;
try {
threshold = (null != altThreshold)
? Integer.parseInt(altThreshold)
: ALTERNATIVE_HASHING_THRESHOLD_DEFAULT;
// disable alternative hashing if -1
if (threshold == -1) {
threshold = Integer.MAX_VALUE;
}
if (threshold < 0) {
throw new IllegalArgumentException("value must be positive integer.");
}
} catch(IllegalArgumentException failed) {
throw new Error("Illegal value for 'jdk.map.althashing.threshold'", failed);
}
ALTERNATIVE_HASHING_THRESHOLD = threshold;
try {
UNSAFE = sun.misc.Unsafe.getUnsafe();
HASHSEED_OFFSET = UNSAFE.objectFieldOffset(
HashMap.class.getDeclaredField("hashSeed"));
} catch (NoSuchFieldException | SecurityException e) {
throw new Error("Failed to record hashSeed offset", e);
}
}
}
//Entry类,实现了Map.Entry接口
static class Entry<K,V> implements Map.Entry<K,V> {
final K key;//节点的key
V value;//节点的value
Entry<K,V> next;//节点的后继节点
int hash;//hash值
/**
* 构造函数,创建一个节点
*/
Entry(int h, K k, V v, Entry<K,V> n) {
value = v;
next = n;
key = k;
hash = h;
}
//获取key
public final K getKey() {
return key;
}
//获取value
public final V getValue() {
return value;
}
//设置value,返回旧的value
public final V setValue(V newValue) {
V oldValue = value;
value = newValue;
return oldValue;
}
//判断两个节点是否相等,由此可见,两个节点相等的条件是:
public final boolean equals(Object o) {
if (!(o instanceof Map.Entry))
return false;
Map.Entry e = (Map.Entry)o;
Object k1 = getKey();
Object k2 = e.getKey();
if (k1 == k2 || (k1 != null && k1.equals(k2))) {
Object v1 = getValue();
Object v2 = e.getValue();
if (v1 == v2 || (v1 != null && v1.equals(v2)))
return true;
}
return false;
}
public final int hashCode() {
return (key==null ? 0 : key.hashCode()) ^
(value==null ? 0 : value.hashCode());
}
public final String toString() {
return getKey() + "=" + getValue();
}
/**
* This method is invoked whenever the value in an entry is
* overwritten by an invocation of put(k,v) for a key k that's already
* in the HashMap.
*/
void recordAccess(HashMap<K,V> m) {
}
/**
* This method is invoked whenever the entry is
* removed from the table.
*/
void recordRemoval(HashMap<K,V> m) {
}
}
- 3 添加元素
put方法源码解析:
/**
* Associates the specified value with the specified key in this map.
* If the map previously contained a mapping for the key, the old
* value is replaced.
*
* @param key key with which the specified value is to be associated
* @param value value to be associated with the specified key
* @return the previous value associated with <tt>key</tt>, or
* <tt>null</tt> if there was no mapping for <tt>key</tt>.
* (A <tt>null</tt> return can also indicate that the map
* previously associated <tt>null</tt> with <tt>key</tt>.)
*/
public V put(K key, V value) {
if (key == null)
//如果添加的key为null,那么执行putForNullKey方法。
return putForNullKey(value);
//获取hash值
int hash = hash(key);
//根据hash值和数组长度计算该元素在桶中的位置
int i = indexFor(hash, table.length);
//通过下标快速定位到元素要添加的桶的位置,遍历该下标位置的链表,判断条件是如果该处节点不为空,如果hash值相等且key相等,则将vlaue替换为新的value,返回旧的value.否则就是要添加的该key不在hashmap中存在
for (Entry<K,V> e = table[i]; e != null; e = e.next) {
Object k;
if (e.hash == hash && ((k = e.key) == key || key.equals(k))) {
V oldValue = e.value;
e.value = value;
e.recordAccess(this);
return oldValue;
}
}
//如果添加的key不在map中存在,那么意味着要调用addEntry方法创建一个新的节点,所以modCount要+1,对hashmap的修改次数加1
modCount++;
//该方法会首先判断是否需要进行扩容操作,扩容操作的条件:
1 元素数量大于扩容阈值
2 下标处元素不为空
同时满足这两个条件则进行扩容操作
该方法会创建一个新的节点,并将其指针指向位于下标的元素,将要添加的元素设置为下标处元素,也就是说将添加的新元素设置为链表的头结点
addEntry(hash, key, value, i);
return null;
}
putFornNullKey方法源码解析
private V putForNullKey(V value) {
//直接遍历数组下标为0的节点,如果该下标处节点不为空,遍历这个链表,如果存在key为null的节点,那么替换旧的value,且返回旧的value
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;
}
}
//如果0下标处无链表,或者有链表但是不存在key为null的节点,那么就要创建添加节点
modCount++;
addEntry(0, null, value, 0);
return null;
}
hash方法,根据这个方法计算key的哈希值
/**
* Retrieve object hash code and applies a supplemental hash function to the
* result hash, which defends against poor quality hash functions. This is
* critical because HashMap uses power-of-two length hash tables, that
* otherwise encounter collisions for hashCodes that do not differ
* in lower bits. Note: Null keys always map to hash 0, thus index 0.
*/
final int hash(Object k) {
int h = 0;
//是否使用替代的哈希函数,如果useAltHashing为true,则使用替代的哈希函数
if (useAltHashing) {
//如果key是string类型的,则使用备选的哈希函数,否则返回hashseed和key的哈希码异或后计算得到的哈希值
if (k instanceof String) {
return sun.misc.Hashing.stringHash32((String) k);
}
h = hashSeed;
}
h ^= k.hashCode();
// This function ensures that hashCodes that differ only by
// constant multiples at each bit position have a bounded
// number of collisions (approximately 8 at default load factor).
h ^= (h >>> 20) ^ (h >>> 12);
return h ^ (h >>> 7) ^ (h >>> 4);
}
//下面看下resize()扩容方法
void resize(int newCapacity) {
Entry[] oldTable = table;//获取当前Entry数组
int oldCapacity = oldTable.length;//当前数组长度
if (oldCapacity == MAXIMUM_CAPACITY) {//如果当前数组的长度已经达到最大容量,则不进行扩容,且将扩容阈值设置为最大值,避免下次再次调用扩容方法
threshold = Integer.MAX_VALUE;
return;
}
//定义一个新的数组
Entry[] newTable = new Entry[newCapacity];
boolean oldAltHashing = useAltHashing;
useAltHashing |= sun.misc.VM.isBooted() &&
(newCapacity >= Holder.ALTERNATIVE_HASHING_THRESHOLD);
boolean rehash = oldAltHashing ^ useAltHashing;
//transfer方法将当前数组上的节点转移到新的数组中
transfer(newTable, rehash);
//转移完成后,将table变量设置为新的数组
table = newTable;
//重新设置扩容阈值,这个阈值是新的容量*加载因子和最大值+1之间的最大值
threshold = (int)Math.min(newCapacity * loadFactor, MAXIMUM_CAPACITY + 1);
}
Transfer方法:
作用: 将所有的节点都转移到新的数组上去
/**
* Transfers all entries from current table to newTable.
*/
void transfer(Entry[] newTable, boolean rehash) {
//获取新数组的length
int newCapacity = newTable.length;
//遍历数组中的各个链表元素
for (Entry<K,V> e : table) {
//while循环判断条件是:当该链表不为空
while(null != e) {
//获取当前节点的下个节点
Entry<K,V> next = e.next;
//如果需要重新hash,则重新计算hash值
if (rehash) {
e.hash = null == e.key ? 0 : hash(e.key);
}
//根据hash值和新数组的大小计算数组下标
int i = indexFor(e.hash, newCapacity);
//当前节点指向该数组下标的节点元素
e.next = newTable[i];
//然后将当前下标的元素设置为当前节点,e为下个节点
newTable[i] = e;
e = next;
}
}
}
根据transfer方法来看,在进行扩容后,原先的一条链表在新的hashmap中顺序发生了倒置
//putForNullKey()方法
当添加空的key的时候,首先遍历Entry<K,V>数组的第一个元素,如果这个链表中有key为null的节点,那么就将其value设置为null,返回旧的value,如果不存在,则电泳addEntry()方法添加新的节点,
private V putForNullKey(V value) {
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;
}
一 添加元素的过程
1 key为null的情况
如果元素的key为null,则调用putForNullKey方法进行处理:
1.1 如果该下标处元素不为null,不会对key计算哈希值进而计算key的下标,而是直接遍历数组下标为0的位置的链表,在遍历过程中,如果有key为null的元素,则将该处元素的value修改设置为要添加的value,返回旧的value.
1.2 如果该下标处元素为null或者未找到key为null的元素,如果未达到扩容条件,则使用所给key和value构造一个新的元素,并将该该下标处的元素设置为这个新元素,再将这个新元素的next指针指向原来该下标处的旧元素.
备注:
如果在添加元素的过程中,可能发生扩容操作,该操作会在后面进行介绍.
2 key不为null的情况
如果添加的key值不为null , 则会先计算key的哈希值 , 再根据hash和数组长度计算key的下标 , 如果该下标处元素不为null ,
说明存在哈希冲突 , 此时需要遍历链表判断当前key是否已经在HashMap中存在 , 然后遍历该下标处所存链表 , 如果元素的hash值和要添加的元素的hash值相等且key值相等 , 则覆盖旧元素的value , 设置为新的value , 返回旧的元素
如果该链表中不存在该key 或者 如果该下标处元素为null , 说明还未添加元素(不存在哈希冲突) ,这两种情况下 则根据key和value 构造 新的节点元素 , 将size属性加1 ,如果未达到扩容条件(也就是size小于数组长度乘以) , 则直接 添加到该下标处 ,并将next指针指向原来该下标处的旧元素或者null(该下标处元素为null).
由此可见 , 对于添加HashMap不存在的key , 如果不发生扩容操作 , HashMap会将该元素放在链表的头部.
二 扩容
1 扩容的作用
扩容发生在添加元素的过程中 , 这里所说的添加 , 并不等同于put , 因为如果要添加的key值已经存在于HashMap中,那么此时在调用put方法的时候,会将旧的value值替换更新为 新的value , 当更新成功后 , 会直接返回 , 此时并不会改变属性值size ,当然也就不会触发扩容发生的条件.下面看下扩容操作发生的条件:
扩容操作的方法是在addEntry方法中 , 扩容条件为 :
1 当前HashMap中元素的个数 大于扩容阈值(扩容阈值=数组长度乘以加载因子)
2 根据新值的key计算出的下标处的元素不为空,也就是在计算出的下标处添加元素的时候出现了哈希冲突
看下面这段代码 , 当元素数量大于扩容阈值 且 该下标处元素不为空(发生了哈希冲突) , 则调用扩容方法 ,扩容后 ,
HashMap容量变为原来2倍 , 然后重新计算key的哈希值 , 进而根据哈希值计算下标 , 然后调用createEntry方法 创建新的元素
添加到链表头部。
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);
}
2 如何完成扩容
void resize(int newCapacity) {
//旧数组的引用
Entry[] oldTable = table;
//旧数组的长度
int oldCapacity = oldTable.length;
//如果容量已经达到了上限,则将扩容阈值设置为最大值,立即返回
if (oldCapacity == MAXIMUM_CAPACITY) {
threshold = Integer.MAX_VALUE;
return;
}
//根据新的容量(原先容量的2倍)创建一个新的数组,
Entry[] newTable = new Entry[newCapacity];
boolean oldAltHashing = useAltHashing;
useAltHashing |= sun.misc.VM.isBooted() &&
(newCapacity >= Holder.ALTERNATIVE_HASHING_THRESHOLD);
boolean rehash = oldAltHashing ^ useAltHashing;
//调用transfer方法完成旧数组到新数组的转换
transfer(newTable, rehash);
table = newTable;
threshold = (int)Math.min(newCapacity * loadFactor, MAXIMUM_CAPACITY + 1);
}
如何将旧的数组转换为新的数组,从下面代码可以看出,需要逐个遍历数组元素,也就是每个桶.
void transfer(Entry[] newTable, boolean rehash) {
int newCapacity = newTable.length;
//遍历整个数组元素
for (Entry<K,V> e : table) {
//如果该处元素不为空,从链表头部开始遍历,
while(null != e) {
Entry<K,V> next = e.next;
//是否需要重新计算哈希值
if (rehash) {
e.hash = null == e.key ? 0 : hash(e.key);
}
//为该元素重新计算下标
int i = indexFor(e.hash, newCapacity);
//由下面代码可以发现,扩容后,HashMap会将桶对应链表进行了反转
e.next = newTable[i];
newTable[i] = e;
e = next;
}
}
}
扩容的主要实现在transfer方法中 , 需要遍历整个数组的全部桶 , 进而遍历桶的所有链表元素 , 为该元素重新计算下标 , 采用了链表的头插入方式 , 如果在发生了哈希冲突的情况下 , 先放在新数组中Entry链上元素最终会被放在链表的尾部
三 get方法解析
public V get(Object key) {
//key为null的话,调用getForNullKey方法
if (key == null)
return getForNullKey();
Entry<K,V> entry = getEntry(key);
return null == entry ? null : entry.getValue();
}
/**
private V getForNullKey() {
//对于key为null的情况,直接遍历位于0 下标的Entry链表,如果某个元素的key为null,返回value,找不到就返回null
for (Entry<K,V> e = table[0]; e != null; e = e.next) {
if (e.key == null)
return e.value;
}
return null;
}
final Entry<K,V> getEntry(Object key) {
//计算key 的hash值 , 通过hash值计算其所在的Entry链表所在的数组下标位置,定位到其所在的Entry链表后,遍历该链表,依次和每个元素进行对比,如何判断两个key是否是同一个key呢?
第一: hash值相等
第二: key相等
如果遍历不到,说明之前为put过该元素,返回null
int hash = (key == null) ? 0 : hash(key);
for (Entry<K,V> e = table[indexFor(hash, table.length)];
e != null;
e = e.next) {
Object k;
if (e.hash == hash &&
((k = e.key) == key || (key != null && key.equals(k))))
return e;
}
return null;
}
JDK1.8
- 成员变量
//默认的容量,默认为16
static final int DEFAULT_INITIAL_CAPACITY = 1 << 4; // aka 16
//HashMap最大容量
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如何添加元素
HashMap添加元素的时候,调用put方法,put方法内部又调用了putVal方法,具体流程如下:
1 判断HahsMap的数组的是否已经初始化,
2 根据数组长度和给定的key的hash值计算元素下标,如果该下标处元素为null.说明未添加过元素,就可以直接用key和value构造一个Node,然后赋值给该下标处
3 如果根据数组长度和给定的key的hash值计算的下标处不为空,说明已经有元素被添加,即发生了哈希冲突。此时需要判断下面几种情况:
如果下标处元素不为空,那么该下标处的数据结构有两种情况:
第一种情况:链表
第二种情况:红黑树
HashMap进行如下几种判断:
判断1:如果位于该下标处的节点的key值和给定的key相等且hash值相等 , 那么获取该处元素的引用
判断2:判断1不满足条件,那么就判断是否是红黑树.如果是红黑树,那么调用操作红黑树的相关方法
判断3:如果判断1和判断2条件都不满足,则则进行链表的遍历操作,如果链表中有key相等的节点,则替换其value,返回旧的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;
//如果HashMap内部的数组为空或者其长度为0,说明还未初始化,进行扩容
if ((tab = table) == null || (n = tab.length) == 0)
n = (tab = resize()).length;
//根据key的hash值和数组长度计算得到下标,如果下标处元素为空,说明还没有元素被添加到该下标处.直接根据给定的key和value构造Node节点,赋值
if ((p = tab[i = (n - 1) & hash]) == null)
tab[i] = newNode(hash, key, value, null);
//如果不为空,说明发生了hash冲突
else {
Node<K,V> e; K k;
//如果该下标处的元素的hash值和给定key的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);
//如果下标处的元素的key值和给定的key不一致,且该下标处为链表
else {
for (int binCount = 0; ; ++binCount) {
//如果该链表中未添加过该key,则将给定的key和value添加到链表尾部
if ((e = p.next) == null) {
p.next = newNode(hash, key, value, null);
//如果大于阈值,将链表转为红黑树,添加结束
if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st
treeifyBin(tab, hash);
break;
}
//如果链表中存在key和hash相等的元素,
if (e.hash == hash &&
((k = e.key) == key || (key != null && key.equals(k))))
break;
p = e;
}
}
//设置新的value,返回旧的value
if (e != null) { // existing mapping for key
V oldValue = e.value;
if (!onlyIfAbsent || oldValue == null)
e.value = value;
afterNodeAccess(e);
return oldValue;
}
}
//modCount次数+1
++modCount;
//如果元素数量大于阈值,进行扩容操作
if (++size > threshold)
resize();
afterNodeInsertion(evict);
return null;
}
- HashMap如何扩容
-
final Node<K,V>[] resize() { //获取当前数组的引用 Node<K,V>[] oldTab = table; //获取当前Hashmap的容量,即数组的长度 int oldCap = (oldTab == null) ? 0 : oldTab.length; //获取扩容的阈值 int oldThr = threshold; //定义新的容量和扩容阈值 int newCap, newThr = 0; //如果旧的数组容量大于0,即Hashmap不为空,说明数组已经添加过元素,发生扩容的原因是数组容量达到了扩容阈值,则会进行如下判断: if (oldCap > 0) { //1: 如果数组容量已经大于最大值,则将扩容阈值设置为最大值,且返回旧的容量,也就是不扩容 if (oldCap >= MAXIMUM_CAPACITY) { threshold = Integer.MAX_VALUE; return oldTab; } //2: 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 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) { //扩容之前,要释放掉之前的Node节点的引用 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); //如果是链表 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; // 满足条件(e.hash & oldCap) == 0,说明该元素在旧数组中的下标和新数组中下标是一致的,下标都小于旧数组长度 if ((e.hash & oldCap) == 0) { if (loTail == null) loHead = e; else loTail.next = e; loTail = e; } //如果不满足条件(e.hash & oldCap)==0,说明该处链表的元素下标会发生变化,新的下标 = 旧的下标 + 旧数组容量 else { if (hiTail == null) hiHead = e; else hiTail.next = e; hiTail = e; } } while ((e = next) != null); if (loTail != null) { loTail.next = null; newTab[j] = loHead; } if (hiTail != null) { hiTail.next = null; newTab[j + oldCap] = hiHead; } } } } } return newTab; }
从上面可以看出