文章目录
一、前置知识
红黑树定义
红黑树是每个结点都带有颜色属性的二叉查找树,颜色或红色或黑色。在二叉查找树强制一般要求以外,对于任何有效的红黑树我们增加了如下的额外要求:
- 结点是红色或黑色。
- 根结点是黑色。
- 所有叶子都是黑色。(叶子是NIL结点)
- 每个红色结点的两个子结点都是黑色。(从每个叶子到根的所有路径上不能有两个连续的红色结点)
- 从任一结点到其每个叶子的所有路径都包含相同数目的黑色结点。
二、构造方法
HashMap()
public HashMap() {
this.loadFactor = DEFAULT_LOAD_FACTOR; // all other fields defaulted
}
HashMap(int initialCapacity, float loadFactor)
public HashMap(int initialCapacity, float loadFactor) {
if (initialCapacity < 0)
throw new IllegalArgumentException("Illegal initial capacity: " +
initialCapacity);
// 如果指定的容量大于 1 << 30, 则指定为最大值
if (initialCapacity > MAXIMUM_CAPACITY)
initialCapacity = MAXIMUM_CAPACITY;
if (loadFactor <= 0 || Float.isNaN(loadFactor))
throw new IllegalArgumentException("Illegal load factor: " +
loadFactor);
this.loadFactor = loadFactor;
// 将非2的幂次的容量,指定为2的幂次
// hashmap的容量是2的幂,如果传入的参数不是2的幂,则会算出比该参数大的最小的2的幂次的数;例如,传入3,则容量4;传入12,则容量16
this.threshold = tableSizeFor(initialCapacity);
}
tableSizeFor(int cap):计算hashmap初始容量
static final int tableSizeFor(int cap) {
// 减1之后,用最高位的 1 把后面每一位都变成 1,此时再加1时,就是2的幂次
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;
}
三、put 方法源码
1. put()
public V put(K key, V value) {
// 计算 key 的 hash 值
return putVal(hash(key), key, value, false, true);
}
hash(Object key):计算key的hash值
/**
* 计算 key.hashCode() 并将散列的高位散布 (XOR) 到低位。
* 因为该表使用二次方掩码,所以仅在当前掩码以上的位上有所不同的散列集将始终发生冲突。 (在已知的例子中有一组 Float 键在小表中保存连续的整数。)
* 所以我们应用一个转换来向下传播较高位的影响。在位扩展的速度、效用和质量之间存在权衡。
* 因为许多常见的哈希集已经合理分布(因此不会从传播中受益),并且因为我们使用树来处理 bins 中的大量冲突,所以我们只是以最便宜的方式对一些移位的位进行 XOR 以减少系统损失,以及合并最高位的影响,否则由于表边界而永远不会在索引计算中使用这些位。
*/
static final int hash(Object key) {
int h;
// key hashcode 的值(32位),高16位与低16位按位异或
return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
}
2. putVal()
该方法主要考虑以下几种情况:
放值前:
- 初始化时,未指定 HashMap 的容量,设置默认容量为 16
存放值时:
- 数组节点上没有存任何 key,即没有发生 hash 碰撞
- 数组节点的 key 和新 put 的 key 一样
- 数组节点是红黑树
- 数组节点是链表
放完后:
- 检查 hashmap 中所有元素个数,超过 负载因子*容量,则扩容
/**
* 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) {
// 访问局部变量而不是类属性的理解:访问栈空间比访问堆空间更快(逃逸分析、栈上分配)
// tab:数组;p:point,指针,当前访问的节点;n:数组大小;i:k hash值对应的数组下标
Node<K,V>[] tab; Node<K,V> p; int n, i;
if ((tab = table) == null || (n = tab.length) == 0)
// 初始化时,未指定 HashMap 的容量,容量为 0,需先扩容
n = (tab = resize()).length;
if ((p = tab[i = (n - 1) & hash]) == null)
// 数组上的节点中没有存值
tab[i] = newNode(hash, key, value, null);
else {
// e:hashmap 中存在key和新插入key相同的旧节点
// k:旧节点的 key
Node<K,V> e; K k;
if (p.hash == hash &&
((k = p.key) == key || (key != null && key.equals(k))))
// 数组节点上的 key 和新插入的 key 一致
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
// 当第9个插入完成后(此时binCount=7),链表转成红黑树
treeifyBin(tab, hash);
break;
}
if (e.hash == hash &&
((k = e.key) == key || (key != null && key.equals(k))))
// 遍历链表时发现的相同key的情况
break;
p = e;
}
}
if (e != null) { // existing mapping for key
V oldValue = e.value;
if (!onlyIfAbsent || oldValue == null)
// 存在旧的值,如果是 put() 方法,就替换,如果是 putIfAbsent() 方法就不替换
e.value = value;
afterNodeAccess(e);
// 返回旧值
return oldValue;
}
}
++modCount;
if (++size > threshold)
// 如果数组中元素个数大于 容量*负载因子,则扩容
resize();
afterNodeInsertion(evict);
return null;
}
通过 hash 计算数组下标
通过 hash 值计算对应的数组下标:(n - 1) & hash
n 是数组大小,是 2 的幂,对应的二进制就是最高位是 1,其余位全是 0
我对该算法的理解:
- n-1 后,每个位上的值必须都是 1,所以 n 必须是 2 的幂
- 因为该算法只是取 int 的低位,所以在计算 hash 值的时候,将高16位与低16位进行异或运算,减少高位不同而低位相同产生的 hash 碰撞
3. resize():扩容
/**
* 初始化或表大小 * 2。
* 如果为空,则在最大值中保持的初始容量目标。
* 否则,数组大小*2,hashmap 中每个元素必须保持在相同的索引中或者在新表的2的幂的偏移中
*/
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;
}
// 容量*2
else if ((newCap = oldCap << 1) < MAXIMUM_CAPACITY &&
oldCap >= DEFAULT_INITIAL_CAPACITY)
// 扩容后容量<最大容量且旧容量>=16时,扩展门槛*2
newThr = oldThr << 1; // double threshold
}
else if (oldThr > 0) // initial capacity was placed in threshold
newCap = oldThr;
else { // zero initial threshold signifies using defaults
// 设置默认容量大小是 16,扩容门槛(容量*负载因子)是12
newCap = DEFAULT_INITIAL_CAPACITY;
newThr = (int)(DEFAULT_LOAD_FACTOR * DEFAULT_INITIAL_CAPACITY);
}
if (newThr == 0) {
// 扩容门槛为 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;
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
// 数组中是链表的情况
// 高位和低位的理解:扩容后,原先相同hash的key,只会被分到新数组的两个位置,大的位置叫高位,小的位置叫低位
Node<K,V> loHead = null, loTail = null;
Node<K,V> hiHead = null, hiTail = null;
Node<K,V> next;
do {
next = e.next;
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;
}
扩容时计算数组下标
4. treeifyBin(tab, hash):链表转为红黑树
/**
* 替换给定哈希的索引处所有链表节点,除非表太小,在这种情况下会扩容。
*/
final void treeifyBin(Node<K,V>[] tab, int hash) {
int n, index; Node<K,V> e;
if (tab == null || (n = tab.length) < MIN_TREEIFY_CAPACITY)
// 如果数组没有被初始化或者数组长度小于64,扩容
resize();
else if ((e = tab[index = (n - 1) & hash]) != null) {
// hd(head):头结点 tl(tail):尾结点
TreeNode<K,V> hd = null, tl = null;
do {
// 将 Node 结点转换为 TreeNode 结点
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);
}
}
treeify():双向链表转为红黑树
final void treeify(Node<K,V>[] tab) {
TreeNode<K,V> root = null;
for (TreeNode<K,V> x = this, next; x != null; x = next) { // x/this:双向链表头节点
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; //dir:标识大小(负数插入左子树中,正数插入右子树中);ph:父节点hash值;pk:父节点key
K pk = p.key;
if ((ph = p.hash) > h) //要插入的节点hash值比父节点小(往左继续迭代)
dir = -1;
else if (ph < h) //要插入的节点hash值比父节点大(往右继续迭代)
dir = 1;
else if ((kc == null &&
(kc = comparableClassFor(k)) == null) || // hash相同但key不相同,且没有实现Comparable接口
(dir = compareComparables(kc, k, pk)) == 0) // 或者key实现了Comparable接口,调用compareTo()方法返回0
dir = tieBreakOrder(k, pk);
TreeNode<K,V> xp = p;
if ((p = (dir <= 0) ? p.left : p.right) == null) {
x.parent = xp;
if (dir <= 0)
xp.left = x;
else
xp.right = x;
root = balanceInsertion(root, x);
break;
}
}
}
}
moveRootToFront(tab, root); //root节点放入数组中,且root节点是双向链表头节点
}
comparableClassFor():是否实现了 Comparable 接口
是否实现了 Comparable
接口,如果实现了则返回对应的 Class
对象,没有实现则返回 null
static Class<?> comparableClassFor(Object x) {
if (x instanceof Comparable) {
Class<?> c; Type[] ts, as; Type t; ParameterizedType p;
if ((c = x.getClass()) == String.class) // String类型直接返回
return c;
if ((ts = c.getGenericInterfaces()) != null) { //获取所有实现的接口并遍历
for (int i = 0; i < ts.length; ++i) {
if (((t = ts[i]) instanceof ParameterizedType) &&
((p = (ParameterizedType)t).getRawType() ==
Comparable.class) && // 如果实现了Comparable接口就继续往下走
(as = p.getActualTypeArguments()) != null && //获取Comparable<>中的泛型数组
as.length == 1 && as[0] == c) // 泛型长度是1且类型是c,则返回
return c;
}
}
}
return null; // 没有实现Comparable则返回Null
}
ParameterizedType 和使用:
compareComparables():调用Comparable接口的compareTo()方法
static int compareComparables(Class<?> kc, Object k, Object x) {
return (x == null || x.getClass() != kc ? 0 :
((Comparable)k).compareTo(x));
}
tieBreakOrder():当key的比较结果相同时,打破平衡(使比较结果不相等)
static int tieBreakOrder(Object a, Object b) {
int d;
if (a == null || b == null ||
(d = a.getClass().getName().
compareTo(b.getClass().getName())) == 0) // 先类名比较
d = (System.identityHashCode(a) <= System.identityHashCode(b) ? // 类名也相同的情况比较地址hash
-1 : 1);
return d;
}
5. split():分割红黑树
/**
* 将红黑树中的节点拆分为低位和高位的双向链表,如果红黑树里结点数太少,则退化为单向链表。
*/
final void split(java.util.HashMap<K,V> map, Node<K,V>[] tab, int index, int bit) {
// 位于数组上的结点
TreeNode<K,V> b = this;
// Relink into lo and hi lists, preserving order
// 低位双向链表头和尾
TreeNode<K,V> loHead = null, loTail = null;
// 高位双向链表头和尾
TreeNode<K,V> hiHead = null, hiTail = null;
// lc(low count):低位结点数量 hc(high count):高位结点数量
int lc = 0, hc = 0;
for (TreeNode<K,V> e = b, next; e != null; e = next) {
next = (TreeNode<K,V>)e.next;
e.next = null;
if ((e.hash & bit) == 0) {
// 低位
if ((e.prev = loTail) == null)
// 链表为空,则该结点就是头结点
loHead = e;
else
// 结点拼接到链表尾部
loTail.next = e;
loTail = e;
++lc;
}
else {
// 高位
if ((e.prev = hiTail) == null)
hiHead = e;
else
hiTail.next = e;
hiTail = e;
++hc;
}
}
if (loHead != null) {
if (lc <= UNTREEIFY_THRESHOLD)
// 如果低位链表结点数量少于6,则红黑树退化为单向链表
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);
}
}
}
untreeify():双向链表转单向链表
final Node<K,V> untreeify(java.util.HashMap<K,V> map) {
Node<K,V> hd = null, tl = null;
// q 是数组中的红黑树结点
for (Node<K,V> q = this; q != null; q = q.next) {
// 将 TreeNode 对象替换为 Node 对象(红黑树结点转换为单向链表结点)
Node<K,V> p = map.replacementNode(q, null);
if (tl == null)
hd = p;
else
tl.next = p;
tl = p;
}
return hd;
}
6. putTreeVal():往红黑树中插入节点
与 treeify() 方法相比:
-
treeify():
- 对红黑树根节点的判断
-
putTreeVal():
- 多了 key 值相同时的判断
红黑树插入后平衡调整规则:
规定新插入的节点是红色:
- 根节点直接插入,变为黑色
- 如果父节点是黑色,则直接插入
- 如果父节点是红色:
- 叔叔节点是空或黑色,旋转加变色
- 叔叔节点是红色,父节点和叔叔节点变黑色,祖父节点变红色
final TreeNode<K,V> putTreeVal(HashMap<K,V> map, Node<K,V>[] tab,
int h, K k, V v) { //this:TreeNode;h:要插入的key的hash值;k:要插入的key;v:要插入的value
Class<?> kc = null;
boolean searched = false;
TreeNode<K,V> root = (parent != null) ? root() : this; //找到root节点
for (TreeNode<K,V> p = root;;) {
int dir, ph; K pk; //dir:正负标识大小;ph:父节点hash值;pk:父节点key
if ((ph = p.hash) > h) //要插入的节点hash值比父节点小(往左继续迭代)
dir = -1;
else if (ph < h) //要插入的节点hash值比父节点大(往右继续迭代)
dir = 1;
else if ((pk = p.key) == k || (k != null && k.equals(pk))) //key值相同
return p;
else if ((kc == null &&
(kc = comparableClassFor(k)) == null) || // hash相同但key不相同,且没有实现Comparable接口
(dir = compareComparables(kc, k, pk)) == 0) { // 或者key实现了Comparable接口,调用compareTo()方法返回0
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; // 查到了相同的key,就返回
}
dir = tieBreakOrder(k, pk); //打破要插入节点和父节点相等的状态(类名排序,类名相同时identityHashCode排序)
}
TreeNode<K,V> xp = p;
if ((p = (dir <= 0) ? p.left : p.right) == null) { //<=0时走左边,>0时走右边
// 当遍历到叶子节点时
Node<K,V> xpn = xp.next;
TreeNode<K,V> x = map.newTreeNode(h, k, v, xpn); //创建节点,next节点指向xpn(双向链表角度看,新节点插到了父节点后面)
if (dir <= 0)
xp.left = x; //叶子节点赋值
else
xp.right = x;
xp.next = x; //父节点next指针指向新节点
x.parent = x.prev = xp; //新节点的parent和prev指针指向父节点
if (xpn != null)
((TreeNode<K,V>)xpn).prev = x; // 双向链表指针调整
moveRootToFront(tab, balanceInsertion(root, x)); // 平衡红黑树后,并root节点放入数组中,且root节点是双向链表头节点
return null;
}
}
}
find():在红黑树中查找
final TreeNode<K,V> find(int h, Object k, Class<?> kc) { // h:被查找key的hash;k:被查找的key;kc:被查找的key的Class对象
TreeNode<K,V> p = this; // this是要查找树的根节点
do {
int ph, dir; K pk;
TreeNode<K,V> pl = p.left, pr = p.right, q;
if ((ph = p.hash) > h) //往左遍历
p = pl;
else if (ph < h) //往右遍历
p = pr;
else if ((pk = p.key) == k || (k != null && k.equals(pk))) // 找到了对应的Key
return p; //将找到的Key返回
// 下面都是hash冲突的情况
else if (pl == null)
p = pr;
else if (pr == null)
p = pl;
else if ((kc != null ||
(kc = comparableClassFor(k)) != null) &&
(dir = compareComparables(kc, k, pk)) != 0) //key实现了Comparable接口的情况
p = (dir < 0) ? pl : pr;
else if ((q = pr.find(h, k, kc)) != null) //比较不出大小来,先右子树继续遍历
return q;
else //最后左子树遍历
p = pl;
} while (p != null);
return null; // 没有查到返回null
}
balanceInsertion():(红黑树中插入值后)平衡红黑树
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;;) { //xp:父节点;xpp:祖父节点;xppl:祖父节点左孩子结点;xppr:祖父节点右孩子节点
if ((xp = x.parent) == null) { //新插入的节点是根节点的情况
x.red = false;
return x;
}
else if (!xp.red || (xpp = xp.parent) == null) //父节点是黑的,或者父节点是根节点
return root;
//以下都是父节点是红色的情况
if (xp == (xppl = xpp.left)) { //xp 在 xpp 左子树的情况(如下图)
if ((xppr = xpp.right) != null && xppr.red) { // 如图1,叔叔(右)节点是红色的
xppr.red = false; //叔叔节点和父节点变黑
xp.red = false;
xpp.red = true; //祖父节点变红,如图2
x = xpp; //继续迭代
}
else { // 图3
if (x == xp.right) {
root = rotateLeft(root, x = xp); //左旋 图4
xpp = (xp = x.parent) == null ? null : xp.parent;
}
if (xp != null) {
xp.red = false;
if (xpp != null) {
xpp.red = true; // 图5
root = rotateRight(root, xpp); //右旋,如图6
}
}
}
}
else { // 在右子树的情况与上面处理类似
if (xppl != null && xppl.red) {
xppl.red = false;
xp.red = false;
xpp.red = true;
x = xpp;
}
else {
if (x == xp.left) {
root = rotateRight(root, x = xp); // 先右旋
xpp = (xp = x.parent) == null ? null : xp.parent;
}
if (xp != null) {
xp.red = false;
if (xpp != null) {
xpp.red = true;
root = rotateLeft(root, xpp); // 再左旋
}
}
}
}
}
}
rotateLeft():左旋
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) // 图2
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; // 图3
r.left = p; // 图4
p.parent = r;
}
return root;
}
rotateRight():右旋
与左旋操作相反
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;
}
moveRootToFront():将 root 节点放入数组中
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; // 数组中放root节点
// 将root节点从双向链表中删除
TreeNode<K,V> rp = root.prev;
if ((rn = root.next) != null)
((TreeNode<K,V>)rn).prev = rp;
if (rp != null)
rp.next = rn;
// root节点放到first节点前面(双向链表头节点)
if (first != null)
first.prev = root;
root.next = first;
root.prev = null;
}
assert checkInvariants(root);
}
}
总结
- 容量是2的幂次,当存的数达到 容量*0.75 时,扩容
- 数组先存单向链表,链表上的节点个数超过8时,如果数组大小没有达到64,则扩容,否则链表转换成双向链表(仍然存在)再转换成红黑树
- 扩容时,如果红黑树中的节点个数小于等于6,则红黑树退化成单向链表