HashMap特点
- 不同步,支持null的键和值,put或get操作通常是常数时间。
- Map接口的实现。
- 去掉了Hashtable的
contains(Object value)
方法,保留containsKey和containsValue方法。 - 使用Iterator而不是Enumration。
对HashMap还不太了解的同学,可以先看看这篇文章大致了解框架:HashMap简单入门
内部字段
// 默认初始长度为16
static final int DEFAULT_INITIAL_CAPACITY = 1 << 4;
// 桶数组的最大长度为2^30
static final int MAXIMUM_CAPACITY = 1 << 30;
// 构造器为设置时,默认的负载因子
static final float DEFAULT_LOAD_FACTOR = 0.75f;
// 当桶中的节点数大于8时,桶结构由单链表转化为一个既是红黑树又是双链表的结构
static final int TREEIFY_THRESHOLD = 8;
// 当桶中的节点数小于6时,树转单链表
static final int UNTREEIFY_THRESHOLD = 6;
// 只有桶位数组大小达到64时,才允许桶位树化,否则只是扩容
// 至少为4 * TREEIFY_THRESHOLD以避免resize和树化的冲突
static final int MIN_TREEIFY_CAPACITY = 64;
// 哈希桶数组,数组中保存的是链表或树节点。长度总是2的整数幂。
transient Node<K,V>[] table;
// HashMap将数据转换成set的另一种存储形式,这个变量主要用于迭代功能
transient Set<Map.Entry<K,V>> entrySet;
// Map中的键值对个数
transient int size;
// 结构性修改的次数,和迭代器的使用有关,对应fail-fast
transient int modCount;
// 扩容阈值,当table大小超过阈值时要扩容为2倍
int threshold;
// 负载因子,用来计算当前table长度下的容量扩容阈值:threshold = loadFactor * table.length
final float loadFactor;
内部类
a. 单链表节点
static class Node<K,V> implements Map.Entry<K,V> {
final int hash; // 这个hash值只考虑键
final K key; // final:说明节点的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;
}
public final K getKey() { return key; }
public final V getValue() { return value; }
public final String toString() { return key + "=" + value; }
public final int hashCode() { // 同时考虑键和值
return Objects.hashCode(key) ^ Objects.hashCode(value);
}
public final V setValue(V newValue) {
V oldValue = value;
value = newValue;
return oldValue;
}
public final boolean equals(Object o) {
if (o == this)
return true;
if (o instanceof Map.Entry) {
Map.Entry<?,?> e = (Map.Entry<?,?>)o;
if (Objects.equals(key, e.getKey()) &&
Objects.equals(value, e.getValue()))
return true;
}
return false;
}
}
b. 树节点(难点)
这里的红黑树比较特殊,它的规则是:
- 根节点是黑色的。
- 红节点可以为左孩子,也可以为右孩子,一个节点可以同时有两个红色的左右孩子。(有些红黑树的实现要求红节点必须是左孩子)。
- 红节点的子节点必须是黑色。
- 每个节点到根节点的路径上黑色节点数量相同。
基本属性和构造方法
HashMap中的红黑树,同时也是一个双链表,parent、left、right和red字段构建了红黑树信息,而prev、next(next是基类HashMap.Node
的字段)构建了双链表信息。树的root节点同时也是双链表的头结点,放在桶位数组上。这种双结构,使得红黑树操作比纯粹红黑树要复杂一些。
static final class TreeNode<K,V> extends LinkedHashMap.Entry<K,V> {
TreeNode<K,V> parent; // 父节点
TreeNode<K,V> left; // 左孩子
TreeNode<K,V> right; // 右孩子
TreeNode<K,V> prev; // 前一个节点
boolean red; // true为红链接,false为黑链接
TreeNode(int hash, K key, V val, Node<K,V> next) {
super(hash, key, val, next);
}
...
}
putTreeVal 方法
HashMap的putVal
方法会调用树方法putTreeVal
来插入新的键值对。
该方法首先会查找树中是否有这个键,有的话就返回这个节点的引用,但注意,这里没有马上更新Value。查找的过程中,首先比较hash值,其次再比较Key,应该是考虑到比较int类型的速度更快。没有找到的话,会新建一个树的叶子节点,再调用树方法balanceInsertion
插入新节点到红黑树中。
final TreeNode<K,V> putTreeVal(HashMap<K,V> map, Node<K,V>[] tab,
int h, K k, V v) {
Class<?> kc = null;
boolean searched = false;
// 找到树根
TreeNode<K,V> root = (parent != null) ? root() : this;
for (TreeNode<K,V> p = root;;) { // p为当前和新元素比较的节点
int dir, ph; K pk;
// 根据hash值比较大小
if ((ph = p.hash) > h)
dir = -1;
else if (ph < h)
dir = 1;
// hash值相等时,如果键指向同一地址,查找结束
else if ((pk = p.key) == k || (k != null && k.equals(pk)))
return p;
// hash值相等,比较两个键
else if ((kc == null &&
(kc = comparableClassFor(k)) == null) ||
(dir = compareComparables(kc, k, pk)) == 0) {
// 键不可比较,或者比较结果为0时,在节点的左右子树查找该Key,找到就结束
if (!searched) {
TreeNode<K,V> q, ch;
searched = true;
if (((ch = p.left) != null &&
(q = ch.find(h, k, kc)) != null) ||
((ch = p.right) != null &&
(q = ch.find(h, k, kc)) != null))
return q;
}
// 比较两个对象的内存地址
dir = tieBreakOrder(k, pk);
}
TreeNode<K,V> xp = p;
// 找到叶子节点还没找到时
if ((p = (dir <= 0) ? p.left : p.right) == null) {
Node<K,V> xpn = xp.next;
// 将当前节点插入到p节点之后,注意这里的前后是双链表的前后
TreeNode<K,V> x = map.newTreeNode(h, k, v, xpn);
// 当前节点和p节点建立父子关系,这里是在树结构中
if (dir <= 0)
xp.left = x;
else
xp.right = x;
xp.next = x;
x.parent = x.prev = xp;
if (xpn != null)
((TreeNode<K,V>)xpn).prev = x;
// 将树结构中刚刚插入的元素向上红黑化,然后将树的根节点排到桶的第一位
moveRootToFront(tab, balanceInsertion(root, x));
return null;
}
}
}
treeify 方法
HashMap的treeifyBin
方法会调用树方法treeify
,来将双链表构建为红黑树。这个方法的主体和putTreeVal
方法类似(不同的是,它不会遇到重复Key值),向只含有一个元素的红黑树上不断添加新的节点。
// 在一个双链表中,将此节点之后的节点构建成红黑树结构,返回树根节点
final void treeify(Node<K,V>[] tab) {
TreeNode<K,V> root = null;
for (TreeNode<K,V> x = this, next; x != null; x = next) {
next = (TreeNode<K,V>)x.next;
x.left = x.right = null; // 初始化树节点
// 初始化根节点,根节点为黑链接
if (root == null) {
x.parent = null;
x.red = false;
root = x;
}
// 将每一个节点插入树中
else {
K k = x.key;
int h = x.hash;
Class<?> kc = null;
for (TreeNode<K,V> p = root;;) {
int dir, ph;
K pk = p.key;
// 根据hash值比较
if ((ph = p.hash) > h)
dir = -1;
else if (ph < h)
dir = 1;
// hash值相等时,比较键
else if ((kc == null &&
(kc = comparableClassFor(k)) == null) ||
(dir = compareComparables(kc, k, pk)) == 0)
// 如果节点的键不是Comparable类,
// 或者两个节点键比较的结果为相等,就强行让比较结果不为0
dir = tieBreakOrder(k, pk);
TreeNode<K,V> xp = p;
// 根据比较的结果判断在左子树还是右子树
if ((p = (dir <= 0) ? p.left : p.right) == null) {
// 子树为null,查找结束,将节点作为树叶子节点
x.parent = xp;
if (dir <= 0)
xp.left = x;
else
xp.right = x;
// 从新添加的元素往上更新红黑树结构,见红黑树操作
root = balanceInsertion(root, x);
break;
}
}
}
}
// 确保红黑树的根节点,同时也是桶位的第一个节点,详见其他方法
moveRootToFront(tab, root);
}
红黑树操作:插入新节点
插入操作主要处理一下三种情况的迭代:
第一种,上浮颜色,上浮后迭代插入红色的xpp节点(图中的2)即可。
第二种和第三种通过旋转转化成了稳定结构,不需要再迭代。
// 往红黑树中平衡插入节点x
static <K,V> TreeNode<K,V> balanceInsertion(TreeNode<K,V> root,
TreeNode<K,V> x) {
x.red = true; // 新节点为红色
// 注意这是一个循环体,将节点一直上浮
for (TreeNode<K,V> xp, xpp, xppl, xppr;;) {
// x为根节点
if ((xp = x.parent) == null) {
x.red = false;
return x;
}
// xp为黑节点,或者xp为根节点时
// 根节点也是黑节点
// 黑节点的孩子可以是红,所以更新结束
else if (!xp.red || (xpp = xp.parent) == null)
return root;
// 下面的情况中父节点都为红----------------------------
// xp在xpp的左子树时
if (xp == (xppl = xpp.left)) {
// xp和xppr都为红
// 直接上浮颜色即可,见示意图1,x = xpp继续迭代
if ((xppr = xpp.right) != null && xppr.red) {
xppr.red = false;
xp.red = false;
xpp.red = true;
x = xpp; // 上浮节点为xpp
}
// xp是红,xppr是黑时
// 见示意图2,最终转化为父节点为黑,所以下一轮就结束
else {
// 当前节点为右孩子时,左旋父节点
// 转变为xppl、xppll都为红的模式
if (x == xp.right) {
root = rotateLeft(root, x = xp);
xpp = (xp = x.parent) == null ? null : xp.parent;
}
// 转变为xp的左右孩子都为红的模式
if (xp != null) {
xp.red = false; // xp改为黑色
if (xpp != null) {
xpp.red = true; // xpp改为红色
root = rotateRight(root, xpp); // 右旋xpp
}
}
}
}
// xp在xpp的右子树时
else {
// xp和xppl都为红
// 直接上浮颜色即可,类似示意图1,x = xpp继续迭代
if (xppl != null && xppl.red) {
xppl.red = false;
xp.red = false;
xpp.red = true;
x = xpp; // x上浮到xpp
}
// xp是红,xppl是黑时
// 见示意图3,最终转化为父节点为黑,所以下一轮就结束
else {
// 当前节点为左孩子时,右旋父节点
// 转变为xppr、xpprr都为红的模式
if (x == xp.left) {
root = rotateRight(root, x = xp); // 右旋父节点
xpp = (xp = x.parent) == null ? null : xp.parent;
}
// 转变为xp的左右孩子都为红的模式
if (xp != null) {
xp.red = false;
if (xpp != null) {
xpp.red = true; // xpp改成红色
root = rotateLeft(root, xpp); // 左旋xpp
}
}
}
}
}
}
}
红黑树操作:左旋
// 节点p左旋转,注意这里没有改变颜色
static <K,V> TreeNode<K,V> rotateLeft(TreeNode<K,V> root,
TreeNode<K,V> p) {
TreeNode<K,V> r, pp, rl;
// 当前节点以及右孩子不为空时
if (p != null && (r = p.right) != null) {
if ((rl = p.right = r.left) != null)
rl.parent = p;
if ((pp = r.parent = p.parent) == null)
(root = r).red = false;
else if (pp.left == p)
pp.left = r;
else
pp.right = r;
r.left = p;
p.parent = r;
}
return root;
}
红黑树操作:右旋
// 节点p右旋转,没有改变颜色
static <K,V> TreeNode<K,V> rotateRight(TreeNode<K,V> root,
TreeNode<K,V> p) {
TreeNode<K,V> l, pp, lr;
// 当前节点以及左孩子不为空时
if (p != null && (l = p.left) != null) {
if ((lr = p.left = l.right) != null)
lr.parent = p;
if ((pp = l.parent = p.parent) == null)
(root = l).red = false;
else if (pp.right == p)
pp.right = l;
else
pp.left = l;
l.right = p;
p.parent = l;
}
return root;
}
辅助方法
// 获得树的根节点
final TreeNode<K,V> root() {
for (TreeNode<K,V> r = this, p;;) {
if ((p = r.parent) == null)
return r;
r = p;
}
}
// 确保树的根节点是桶中的双链表的第一个节点
static <K,V> void moveRootToFront(Node<K,V>[] tab, TreeNode<K,V> root) {
int n;
if (root != null && tab != null && (n = tab.length) > 0) {
int index = (n - 1) & root.hash; // 找到桶位
TreeNode<K,V> first = (TreeNode<K,V>)tab[index];
if (root != first) { // 当根节点不是桶中第一个元素时
Node<K,V> rn;
tab[index] = root; // 根节点放在桶的第一位
TreeNode<K,V> rp = root.prev; // 根的前一个节点
// 将根节点从双表中抽出,原来的位置前后链接
if ((rn = root.next) != null)
((TreeNode<K,V>)rn).prev = rp;
if (rp != null)
rp.next = rn;
// 根节点放在双链表的首位
if (first != null)
first.prev = root;
root.next = first;
root.prev = null;
}
assert checkInvariants(root);
}
}
// 从this树节点查找hash值为h,Key为k的节点
final TreeNode<K,V> find(int h, Object k, Class<?> kc) {
TreeNode<K,V> p = this;
do {
int ph, dir; K pk;
TreeNode<K,V> pl = p.left, pr = p.right, q; // 当前节点的左右孩子
if ((ph = p.hash) > h) // hash值小的从左子树迭代查找
p = pl;
else if (ph < h) // hash值大的从右子树迭代查找
p = pr;
// hash值相等,且键地址相同或都为空时,查找成功
else if ((pk = p.key) == k || (k != null && k.equals(pk)))
return p;
// hash值相等,但键不相同,且节点没有左子树,就从右子树查找
else if (pl == null)
p = pr;
// hash值相等,但键不相同,且节点没有右子树,就从左子树查找
else if (pr == null)
p = pl;
// 比较两个Key
else if ((kc != null ||
(kc = comparableClassFor(k)) != null) &&
(dir = compareComparables(kc, k, pk)) != 0)
p = (dir < 0) ? pl : pr;
// Key不可比较或比较结果为0时,先在右子树中查找
else if ((q = pr.find(h, k, kc)) != null)
return q;
// 右子树查找不到时
else
p = pl;
} while (p != null);
return null;
}
// 从根节点查找hash值为h,Key为k的节点
final TreeNode<K,V> getTreeNode(int h, Object k) {
return ((parent != null) ? root() : this).find(h, k, null);
}
// 强行比较两个对象,结果为-1或1
static int tieBreakOrder(Object a, Object b) {
int d;
// a和b都不为空时比较它们的类名
if (a == null || b == null ||
(d = a.getClass().getName().
compareTo(b.getClass().getName())) == 0)
// a为null,或b为null,或类名也相等时,比较它们的内存地址
d = (System.identityHashCode(a) <= System.identityHashCode(b) ?
-1 : 1);
return d;
}
/**
* Returns a list of non-TreeNodes replacing those linked from
* this node.
*/
final Node<K,V> untreeify(HashMap<K,V> map) {
Node<K,V> hd = null, tl = null;
for (Node<K,V> q = this; q != null; q = q.next) {
Node<K,V> p = map.replacementNode(q, null);
if (tl == null)
hd = p;
else
tl.next = p;
tl = p;
}
return hd;
}
构造函数
HashMap(int initialCapacity, float loadFactor)
选择不小于intialCapacity的最小的2的幂作为threshold的值。第一次put键值对时,初始化table数组大小为threshold。
HashMap(int initialCapacity)
采用默认的扩容因子0.75f。
HashMap()
采用默认的扩容因子0.75f,threshold为0。第一次put键值对时,初始化table数组大小为16(DEFAULT_INITIAL_CAPACITY)
用集合数据的构造方法,就是将Map中的每个键值对添加到
HashMap
中。
public HashMap(Map<? extends K, ? extends V> m) {
this.loadFactor = DEFAULT_LOAD_FACTOR;
putMapEntries(m, false);
}
final void putMapEntries(Map<? extends K, ? extends V> m, boolean evict) {
int s = m.size();
if (s > 0) {
// 数组还未初始化时,根据集合中元素数量和负载因子,计算数组大小的阈值
if (table == null) { // pre-size
float ft = ((float)s / loadFactor) + 1.0F;
int t = ((ft < (float)MAXIMUM_CAPACITY) ?
(int)ft : MAXIMUM_CAPACITY);
if (t > threshold)
threshold = tableSizeFor(t);
}
// 数组已经初始化时,如果Map中元素数量超过阈值,就扩容
else if (s > threshold)
resize();
for (Map.Entry<? extends K, ? extends V> e : m.entrySet()) {
K key = e.getKey();
V value = e.getValue();
putVal(hash(key), key, value, false, evict);
}
}
}
添加键值对:put 方法
采用拉链法。插入新的键值对,使用hash&table.length
确定桶位,如果桶位为null,直接存放节点。如果不为空,就调用这个桶位的的put(K, V)方法。
桶中元素较少时(小于8个),桶为单链表,如果插入了一个新的键,链表长度增加1。当链表长度达到8(TREEIFY_THRESHOLD)时,调用树化函数treeifyBin(tab, hash)
。
桶为红黑树结构时,调用第一个树节点的putTreeVal
方法。
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;
// 数组未初始化或者长度为0,都要扩容
if ((tab = table) == null || (n = tab.length) == 0)
n = (tab = resize()).length;
// Key值对应的桶位若为空,直接添加,皆大欢喜就结束了
if ((p = tab[i = (n - 1) & hash]) == null)
tab[i] = newNode(hash, key, value, null);
// 桶中已经有元素时
else {
Node<K,V> e; K k;
// 新元素如果和第一个元素hash值相等,且Key相等时,直接修改Value
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);
// 如果桶是单链表结构,就往单链表中插入Node节点
else {
for (int binCount = 0; ; ++binCount) {
// 如果已经找到单链表的末尾,就将新节点放在末尾,同时判断是否要树化
if ((e = p.next) == null) {
p.next = newNode(hash, key, value, null);
// 如果新的桶中元素总数达到树化阈值,判断是要树化,还是要扩容
if (binCount >= TREEIFY_THRESHOLD - 1)
treeifyBin(tab, hash);
break;
}
// 如果找到hash值相等,且Key相等的节点,就直接修改Value
if (e.hash == hash &&
((k = e.key) == key || (key != null && key.equals(k))))
break;
p = e;
}
}
// 在上述过程如果不是添加新节点,而是查找到了Key值相等的旧节点,就返回就旧节点
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;
}
辅助方法:hash 函数
Key在数组中的桶位,通过(tab.length - 1) & hash
确定。Key为null的时候,hash值为0,桶位索引为0,所以null键都被放到table[0]的位置。
static final int hash(Object key) {
int h;
// 可以防止hash数组太小时,hash值的高位被忽略
return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
}
final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
boolean evict) {
...
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
...
}
辅助方法:tableSizeFor 方法
该方法用来将桶位数组需要的容量扩充到2的自然数幂
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;
}
辅助方法:treeifyBin 函数
HashMap中添加元素时会调用putVal
方法,当桶中单链表结构元素总数超过8时,调用treeifyBin
方法,将桶中的链表转化为红黑树,或者只是扩容桶位数组(会将单链表拆分为两半,达到减小单链表长度的目的)。
final void treeifyBin(Node<K,V>[] tab, int hash) {
int n, index; Node<K,V> e;
// 桶数组为空,或者长度小于MIN_TREEIFY_CAPACITY,不符合树化条件
if (tab == null || (n = tab.length) < MIN_TREEIFY_CAPACITY)
resize(); // 扩容
// 符合树化条件,而且桶位数组对应位置的桶不为null
else if ((e = tab[index = (n - 1) & hash]) != null) {
TreeNode<K,V> hd = null, tl = null; // 红黑树的头尾节点
do {
// 把普通节点转化为树节点
TreeNode<K,V> p = replacementTreeNode(e, null);
if (tl == null)
hd = p; // 首先确定树的头结点
else { // 树节点同时也是双向链表
p.prev = tl;
tl.next = p;
}
tl = p; // 循环的最后确定了树的尾节点
} while ((e = e.next) != null);
// 将桶中的双链表转化成红黑树,详细见树节点内部方法
if ((tab[index] = hd) != null)
hd.treeify(tab);
}
}
辅助方法:扩容 resize
- 初始化或者扩容桶位数组,空数组的话就根据threshold来计算初始化容量
- 分配到新的散列表时,每个桶位中的元素,要么不动,要么后移新数组长度的一半
final Node<K,V>[] resize() {
Node<K,V>[] oldTab = table; // 保存旧的数组
int oldCap = (oldTab == null) ? 0 : oldTab.length; // 保存数组长度
int oldThr = threshold; // 保存旧的阈值
int newCap, newThr = 0;
// 旧数组不为null且长度不为0时
if (oldCap > 0) {
// 旧长度达到最大长度限制时,阈值设为最大整型,return
if (oldCap >= MAXIMUM_CAPACITY) {
threshold = Integer.MAX_VALUE;
return oldTab;
}
// 否则,扩容一倍
else if ((newCap = oldCap << 1) < MAXIMUM_CAPACITY &&
oldCap >= DEFAULT_INITIAL_CAPACITY)
// 扩容后没超过长度限制&&旧长度>=默认初始长度时,阈值翻倍
newThr = oldThr << 1;
}
// 旧数组为null,或长度为0时
else if (oldThr > 0) // 旧阈值大于0,就将旧阈值作为新的数组大小
newCap = oldThr;
else { // 旧阈值为0,旧容量也为0,就采用默认值(使用无参构造器后,插入一个元素就会执行这里)
newCap = DEFAULT_INITIAL_CAPACITY;
newThr = (int)(DEFAULT_LOAD_FACTOR * DEFAULT_INITIAL_CAPACITY);
}
// 新阈值为0,新阈值 = 新的长度 * 负载因子
// 但如果新的长度或者计算出来的新阈值超过最大长度限制,就采用最大整型
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;
// 旧HashMap中的所有元素分配到新的HashMap
if (oldTab != null) {
for (int j = 0; j < oldCap; ++j) {
Node<K,V> e;
// 遍历处理每一个桶位中的桶
if ((e = oldTab[j]) != null) {
oldTab[j] = null;
// 只有一个元素的桶,直接重新hash
if (e.next == null)
newTab[e.hash & (newCap - 1)] = e;
// 桶中不止一个元素时
else if (e instanceof TreeNode) // 红黑树就用split方法分离节点
((TreeNode<K,V>)e).split(this, newTab, j, oldCap);
else { // 单链表就拆分成两个链表
Node<K,V> loHead = null, loTail = null; // 原索引处的一个链表
Node<K,V> hiHead = null, hiTail = null; // 原索引+oldCap的一个链表
Node<K,V> next;
do {
next = e.next;
// 根据hash值的某一位为0还是1将单链表拆分
if ((e.hash & oldCap) == 0) {
if (loTail == null)
loHead = e;
else
loTail.next = e;
loTail = e;
}
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;
}
上一篇:Android新手必读的RecyclerView使用总结
下一篇:Jdk1.8集合框架之LinkedHashMap源码解析