【Java学习001】Java-HashMap源码详解

HashMap源码解析

1.1底层实现

定义:HashMap基于哈希表的Map接口实现,底层数据以key-value形式存在。
结构特点 ^f51f99

  • HashMap 的实现不是同步的,这意味着它不是线程安全的。它的key、value都可以为null。
  • HashMap中的映射不是有序的。

补充知识:

  1. JDK1.8之前底层采用Entry数组链表进行实现。创建对象的时候就会创建对应的底层数组。
  2. 在JDK1.8 之后 HashMap 由 Node数组+链表 +红黑树进行实现并使用进行。创建对象的时候采用懒加载模式,HashMap对象创建时不会创建底层数组,而是等到第一次调用add方法的时候才创建底层数组。

1.2继承关系

1.2.1Serializable接口

该接口是一个标记型接口,即没有方法或字段,仅用于标识可序列化的语义。不实现此接口的类将不会使任何状态序列化或反序列化。可序列化类的所有子类型都是可序列化的。

[!Note]- 1.8源码注释

If a serializable class does not explicitly declare a serialVersionUID, then the serialization runtime will calculate a default serialVersionUID value for that class based on various aspects of the class, as described in the Java™ Object Serialization Specification. However, it is strongly recommended that all serializable classes explicitly declare serialVersionUID values, since the default serialVersionUID computation is highly sensitive to class details that may vary depending on compiler implementations, and can thus result in unexpected InvalidClassExceptions during deserialization. Therefore, to guarantee a consistent serialVersionUID value across different java compiler implementations, a serializable class must declare an explicit serialVersionUID value. It is also strongly advised that explicit serialVersionUID declarations use the private modifier where possible, since such declarations apply only to the immediately declaring class–serialVersionUID fields are not useful as inherited members. Array classes cannot declare an explicit serialVersionUID, so they always have the default computed value, but the requirement for matching serialVersionUID values is waived for array classes.

1.2.2Cloneable接口

Cloneable接口同样也是一个标记型接口,没有任何的字段或者方法。
实现该接口的意义在于指示Object.clone()方法,调用方法对于该类的实例进行字段的复制是合法的。在不实现Cloneable接口的实例上调用clone()方法会抛出CloneNotSupportedException异常。

[!Note]- 1.8源码注释

A class implements the Cloneable interface to indicate to the Object.clone() method that it is legal for that method to make a field-for-field copy of instances of that class.
Invoking Object’s clone method on an instance that does not implement the Cloneable interface results in the exception CloneNotSupportedException being thrown.
By convention, classes that implement this interface should override Object.clone (which is protected) with a public method. See Object.clone() for details on overriding this method.
Note that this interface does not contain the clone method. Therefore, it is not possible to clone an object merely by virtue of the fact that it implements this interface. Even if the clone method is invoked reflectively, there is no guarantee that it will succeed.

补充知识:

  1. 实现克隆的前提:
    • 实现Cloneable接口。
    • 重写clone方法。(该方法创建的对象与原对象的地址不同)
  2. 浅拷贝的局限性:
    • 基本数据类型可以达到完全复制,引用数据类型则不可以。
    • 引用数据类型进行克隆的时候只是单纯复制了一份引用。

1.3成员变量

------------------ 默认值 ------------------
// 默认初始容量 16  
static final int DEFAULT_INITIAL_CAPACITY = 1 << 4; 

// 容量最大值 
static final int MAXIMUM_CAPACITY = 1 << 30;  

// 默认加载因子0.75
static final float DEFAULT_LOAD_FACTOR = 0.75f; 

// 当桶(bucket)上的结点数大于这个值时会转成红黑树  
static final int TREEIFY_THRESHOLD = 8;  

// 当桶(bucket)上的结点数小于这个值时会转成链表  
static final int UNTREEIFY_THRESHOLD = 6;  

// 链表转为红黑树数组最小长度
static final int MIN_TREEIFY_CAPACITY = 64;

------------------ 对象真正的属性 ------------------
// 底层数组
transient Node<K,V>[] table;  

// 键值对集合
transient Set<Map.Entry<K,V>> entrySet;  

// 键值对容量
transient int size;  

// 结构修改计数器,用于检查并发修改异常
transient int modCount;  

// 扩容阈值
int threshold;  

// 加载因子
final float loadFactor;

1.4关键内部类详解

1.4.1Node

定义:基本的HashMap节点结构,HashMap底层采用的就是Node数组。

1.4.1.1方法和属性
  
static class Node<K,V> implements Map.Entry<K,V> {  
    final int hash;  
    // 键
    final K key;  
    // 值
    V value;  
    // 下一个Node节点
    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; }  

	// 键所属类型的hashCode方法返回值^值得hashCode方法返回值
    public final int hashCode() {  
        return 
        Objects.hashCode(key) ^ Objects.hashCode(value);  
    }  
  
    public final V setValue(V newValue) {  
        V oldValue = value;  
        value = newValue;  
        return oldValue;  
    }  

	/*
	如果是同一个对象直接返回TRUE,
	*/
    public final boolean equals(Object o) {  
        if (o == this)  
            return true;  
  
        return o instanceof Map.Entry<?, ?> e  
                && Objects.equals(key, e.getKey())  
                && Objects.equals(value, e.getValue());  
    }  
}

// Object-equals方法
public static boolean equals(Object a, Object b) {  
    return (a == b) || (a != null && a.equals(b));  
}

// Object-hashCode方法
public static int hashCode(Object o) {  
    return o != null ? o.hashCode() : 0;  
}

1.4.2EntrySet

final class EntrySet extends AbstractSet<Map.Entry<K,V>> {
         // 返回键值对容量
   		 public final int size()                 { return size; }
    	// 调用HashMap的清除方法
    	public final void clear()               { HashMap.this.clear(); }
    	// 返回包装的迭代器对象
        public final Iterator<Map.Entry<K,V>> iterator() {
            return new EntryIterator();
        }
        public final boolean contains(Object o) {
            if (!(o instanceof Map.Entry))
                return false;
            Map.Entry<?,?> e = (Map.Entry<?,?>) o;
            Object key = e.getKey();
            Node<K,V> candidate = getNode(hash(key), key);
            return candidate != null && candidate.equals(e);
        }
        public final boolean remove(Object o) {
            if (o instanceof Map.Entry) {
                Map.Entry<?,?> e = (Map.Entry<?,?>) o;
                Object key = e.getKey();
                Object value = e.getValue();
                return removeNode(hash(key), key, value, true, true) != null;
            }
            return false;
        }
        public final Spliterator<Map.Entry<K,V>> spliterator() {
            return new EntrySpliterator<>(HashMap.this, 0, -1, 0, 0);
        }
        public final void forEach(Consumer<? super Map.Entry<K,V>> action) {
            Node<K,V>[] tab;
            if (action == null)
                throw new NullPointerException();
            if (size > 0 && (tab = table) != null) {
                int mc = modCount;
                for (int i = 0; i < tab.length; ++i) {
                    for (Node<K,V> e = tab[i]; e != null; e = e.next)
                        action.accept(e);
                }
                if (modCount != mc)
                    throw new ConcurrentModificationException();
            }
        }
    }

1.5构造方法

1.5.1无参初始化

public HashMap() {
		// 初始化设置默认加载因子为对象加载因子
    	// 默认初始容量(16)和默认负载因子(0.75)。 
        this.loadFactor = DEFAULT_LOAD_FACTOR; // all other fields defaulted
    }

1.5.2有参初始化

给定容量或/和加载因子
// 给定初始容量
public HashMap(int initialCapacity) {
		// 传递给定的初始大小和默认加载因子0.75
        this(initialCapacity, DEFAULT_LOAD_FACTOR);
    }

public HashMap(int initialCapacity, float loadFactor) {
    	// 容量小于0会报错
        if (initialCapacity < 0)
            throw new IllegalArgumentException("Illegal initial capacity: " + initialCapacity);
    
    	// 容量过大会纠正为 min(initialCapacity, MAXIMUM_CAPACITY)
    	
        if (initialCapacity > MAXIMUM_CAPACITY)
            initialCapacity = MAXIMUM_CAPACITY;
    
    	// 加载因子<=0或者是无穷抛出异常
        if (loadFactor <= 0 || Float.isNaN(loadFactor))
            throw new IllegalArgumentException("Illegal load factor: " +
                                               loadFactor);
    	
        this.loadFactor = loadFactor;
    	// 给定长度不一定就会被接受,必须是2的整数倍才能被接受,但是他会自动选择最近的2的整数倍
    	// 这里给threshold获取到的值没有问题,在put方法中还会对他进行修改,可以理解为就是暂存
        this.threshold = tableSizeFor(initialCapacity);
    }

// 返回给定cap最近的2的整数倍
static final int tableSizeFor(int cap) {  
	// 底层调的方法
    int n = -1 >>> Integer.numberOfLeadingZeros(cap - 1);  
    // 对计算的n归一化到1-MAXIMUM_CAPACITY
    return (n < 0) ? 1 : (n >= MAXIMUM_CAPACITY) ? MAXIMUM_CAPACITY : n + 1;  
}

public static int numberOfLeadingZeros(int i) {  
    // HD, Count leading 0's  
    if (i <= 0)  
        return i == 0 ? 32 : 0;  
    int n = 31;  
    if (i >= 1 << 16) { n -= 16; i >>>= 16; }  
    if (i >= 1 <<  8) { n -=  8; i >>>=  8; }  
    if (i >= 1 <<  4) { n -=  4; i >>>=  4; }  
    if (i >= 1 <<  2) { n -=  2; i >>>=  2; }  
    return n - (i >>> 1);  
}
使用Map创建
// 使用一个Map进行创建
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) {
    	// 获取map的大小
        int s = m.size();
        if (s > 0) {
           
            if (table == null) { // pre-size
				// 计算ft,用来判断是否进行扩容
				// 加1.0F与(int)ft相当于是对小数做一个向上取整以尽可能的保证更大容量,更大的容量能够减少resize的调用次数。
                float ft = ((float)s / loadFactor) + 1.0F;
                int t = ((ft < (float)MAXIMUM_CAPACITY) ?
                         (int)ft : MAXIMUM_CAPACITY);
                         
                
                if (t > threshold)
                    threshold = tableSizeFor(t);
            }
             // 如果大于扩容阈值则进行扩容
            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);
            }
        }
    }

这里重点讲一下,源码注释中说明 t h r e s h o l d = c a p a c i t y × l o a d f a c t o r threshold=capacity\times loadfactor threshold=capacity×loadfactor ,假设此时传递参数是 s = 15 s=15 s=15,那么 t = f t = 20 t=ft=20 t=ft=20,调用tableSizeFor方法后 t h r e s h o l d = 16 threshold=16 threshold=16,然后再进行扩容判断。扩容方法在下面会详细讲解。

[!Quote] threshold
The next size value at which to resize (capacity * load factor)

1.6常见方法详解

1.6.1添加方法

在这里插入图片描述

// 外部包装的putAPI
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或者是不是null,执行初始化的扩容,这也是HashMap懒加载的初始化位置
        if ((tab = table) == null || (n = tab.length) == 0)
            n = (tab = resize()).length;
    	// 计算的节点(n - 1) & hash,这就是插入索引,如果为空则直接进行插入
        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;
            // 如果此时插入的节点已经是一颗红黑树节点了那就直接调用红黑树API进行节点添加
            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);
                        // 添加完成后的链表长度大于等8会进行红黑树转变
                        if (binCount >= TREEIFY_THRESHOLD - 1) 
                            treeifyBin(tab, hash);
                        break;
                    }
                    if (e.hash == hash &&
                        ((k = e.key) == key || (key != null && key.equals(k))))
                        break;
                    p = e;
                }
            }
            // 执行了替换操作,将旧Node对象的值替换成新的
            if (e != null) { // existing mapping for key
                V oldValue = e.value;
                if (!onlyIfAbsent || oldValue == null)
                    e.value = value;
                afterNodeAccess(e);
                return oldValue;
            }
        }
    	// 结构修改计数器+1
        ++modCount;
    	// 扩容检测
        if (++size > threshold)
            resize();
        afterNodeInsertion(evict);
        return null;
    }

// 树化方法
final void treeifyBin(Node<K,V>[] tab, int hash) {  
    int n, index; Node<K,V> e;  
    // 树化的条件是 Node数组不为null且数组长度要大于64,否则只会进行扩容
    if (tab == null || (n = tab.length) < MIN_TREEIFY_CAPACITY)  
        resize();  
    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);  
    }  
}

从上文可以看到HashMap检测键冲突的方法是p.hash == hash && ((k = p.key) == key || (key != null && key.equals(k))),总结起来符合以下几点就是key相同,存在哈希冲突。

  1. 键的哈希值相等(比较地址)且键相等(比较地址)。
  2. 键的哈希值相同且键不为Null,调用键equals方法两者相等。

此外,在添加Node对象的过程中还会进行树化判断,如果链节点数量大于等于8则进行树化。这里会有一个疑问就是为啥不直接使用树状结构,避免树化过程中的资源开销,且同样8深度,树结构携带的节点数将远远大于链状结构且树状结构查询的时间复杂度是 O ( log ⁡ N ) O(\log N) O(logN)。这里真正的原因是因为一个树状节点的大小约是链节点的两倍大,所以只在一个桶内有足够多的节点时才采用树结构进行存储。

在使用分布良好的哈希函数时,很少使用树状结构。在随机哈希码下,桶中节点数量符合泊松分布:

桶中节点数量概率
00.60653066
10.30326533
20.07581633
30.01263606
40.00157952
50.00015795
60.00001316
70.00000094
80.00000006

此外虽然链表长度达到8就会进行树化,但是这里还有一个隐性条件,那就是Node数组的长度需要达到64,数组长度没有达到64,但是链表长度达到8则只会进行扩容。

1.6.2扩容方法

final Node<K,V>[] resize() {
	// 获取旧的Node数组
    Node<K,V>[] oldTab = table;  
    // 获取就数组的容量
    int oldCap = (oldTab == null) ? 0 : oldTab.length;  
    // 获取旧的扩容阈值
    int oldThr = threshold;  
    // 初始化新的边界值和容量
    int newCap, newThr = 0;  
    // HashMap 已经初始化完毕了并且有元素
    if (oldCap > 0) {  
	    // 如果容量比最大容量要大,那么threshold设置为整数的最大值
        if (oldCap >= MAXIMUM_CAPACITY) {  
            threshold = Integer.MAX_VALUE;  
            // 返回旧的Node数组
            return oldTab;  
        }  
          // 扩容2倍,如果这个值在16和2^30之间则将新的边界值也扩大两倍
        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 {             
	    // 无参构造的情况下使用threshold公式,构造容量16,扩容阈值12的对象  
        newCap = DEFAULT_INITIAL_CAPACITY;  
        newThr = (int)(DEFAULT_LOAD_FACTOR * DEFAULT_INITIAL_CAPACITY);  
    }  
    // 如果新的扩容阈值0,容量非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数组并将指向该对象
    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;  
            // 只处理0-oldcap桶内不为null的数据
            if ((e = oldTab[j]) != null) {  
	            // 旧的Node数组j位置元素赋值null,方便gc
                oldTab[j] = null;  
                //e.next == 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;  
                        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;  
}

// 哈希方法
static final int hash(Object key) {  
    int h;  
    return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);  
}

上文说的方便gc是指断开了指向Node对象的引用,方便gc回收该Node对象。

HashMap的哈希计算方法是键的哈希值亦或其右移16位的结果。正常情况下插入的索引位置计算公式是 H a s h ( k e y ) % N Hash(key)\%N Hash(key)%N,但是计算机执行取余效率太低,所以采用位运算进行代替 ( n − 1 ) & h a s h ( k e y ) (n - 1) \& hash(key) (n1)&hash(key),使用这个公式有一个条件那就是n必须是2的幂次方。下面举两个例子大家就知道了。

示例1:
扩容前
1111 1010 0101 1011 1000 0001 1011 0101 | hash(key)
0000 0000 0000 0000 0000 0000 0000 1111 | 15
---------------------------------------------------
0000 0000 0000 0000 0000 0000 0000 0101 | index = 5

扩容后
1111 1010 0101 1011 1000 0001 1011 0101 | hash(key)
0000 0000 0000 0000 0000 0000 0001 1111 | 31
---------------------------------------------------
0000 0000 0000 0000 0000 0000 0001 0101 | index = 21 = 16+5

--------------------------------------------------------------
示例2:
扩容前
1111 1010 0101 1011 1000 0001 1010 1111 | hash(key)
0000 0000 0000 0000 0000 0000 0000 1111 | 15
---------------------------------------------------
0000 0000 0000 0000 0000 0000 0000 1111 | index = 15


1111 1010 0101 1011 1000 0001 1010 1111 | hash(key)
0000 0000 0000 0000 0000 0000 0001 1111 | 32
---------------------------------------------------
0000 0000 0000 0000 0000 0000 0000 1111 | index = 15 

从上面两个示例可以看出,扩容前后的Node对象的位置主要取决多出来的比特位对应hash(key)上是1还是0,如果是1则 i n d e x n e w = i n d e x o l d + o l d c a p index_{new}=index_{old}+oldcap indexnew=indexold+oldcap,否则 i n d e x n e w = i n d e x o l d index_{new}=index_{old} indexnew=indexold,由于新增比特位是0还是1可以人为是随机的,在扩容过程中每个桶上的节点数一定小于扩容前的数量,避免了严重的哈希冲突。

那么什么时候进行扩容呢?上文已经介绍到了一个参数threshold,这个参数用来控制什么时候进行扩容。这个参数的计算公式为 c a p × l o a d f a c t o r cap\times loadfactor cap×loadfactor,但是只有在调用resize方法后才会使用该公式。初始化时就会有如下两种情况:

  1. 空参初始化, t h r e s h o l d = 0 threshold=0 threshold=0
  2. 在给定容量的情况, t h r e s h o l d = t a b l e S i z e F o r ( i n i t i a l C a p a c i t y ) threshold=tableSizeFor(initialCapacity) threshold=tableSizeFor(initialCapacity),返回跟给定容量最近的 2 N 2^N 2N

1.6.3获取方法

// 包装的get方法
public V get(Object key) {  
    Node<K,V> e;  
    return (e = getNode(hash(key), key)) == null ? null : e.value;  
}

final Node<K,V> getNode(int hash, Object key) {  
	// 定义所需参数
    Node<K,V>[] tab; Node<K,V> first, e; int n; K k;  
    // 初始化了,长度不为0,哈希计算的索引不为空
    if ((tab = table) != null && (n = tab.length) > 0 &&  
        (first = tab[(n - 1) & hash]) != null) {  
        // 如果桶内第一个元素就是直接返回
        if (first.hash == hash && // always check first node  
            ((k = first.key) == key || (key != null && key.equals(k))))  
            return first;  
        // 如果不是第一个,且有链式结构则表明要不是链表要不就是红黑树
        if ((e = first.next) != null) {  
	        // 判断是不是红黑树节点,如果是调用红黑树API
            if (first instanceof TreeNode)  
                return ((TreeNode<K,V>)first).getTreeNode(hash, key);  
            // 使用do-while进行链表的顺序判断
            do {  
                if (e.hash == hash &&  
                    ((k = e.key) == key || (key != null && key.equals(k))))  
                    return e;  
            } while ((e = e.next) != null);  
        }  
    } 
     
    // 不满足上述条件则返回null
    return null;  
}

从上文可以看到,不论是添加还是获取采用的方法都是先判断桶内第一个节点(假定无链式结构),再判断是否红黑树结构,最后按照链表结构进行处理。

1.6.4Clear方法

public void clear() {
    	
        Node<K,V>[] tab;
     	// 更改计数器+1
        modCount++;
    	// Node数组非null且数组长度大于0才会执行清除
        if ((tab = table) != null && size > 0) {
            // 将元素数量设置为0
            size = 0;
            // 遍历原本存储数组将之置为null,方便gc
            for (int i = 0; i < tab.length; ++i)
                tab[i] = null;
        }
    }

1.6.5删除方法

// 删除包装方法
public V remove(Object key) {  
    Node<K,V> e;  
    return (e = removeNode(hash(key), key, null, false, true)) == null ?  
        null : e.value;  
}

final Node<K,V> removeNode(int hash, Object key, Object value,
                               boolean matchValue, boolean movable) {
        Node<K,V>[] tab; Node<K,V> p; int n, index;
    	// HashMap初始化了,Node数组长度>0,key所在索引位置有值
        if ((tab = table) != null && (n = tab.length) > 0 &&
            (p = tab[index = (n - 1) & hash]) != null) {
            // 定义所需变量
            Node<K,V> node = null, e; K k; V v;
            // 哈希值相同的时候判断
            if (p.hash == hash &&
                ((k = p.key) == key || (key != null && key.equals(k))))
                node = p;
            // 链式结构处理
            else if ((e = p.next) != null) {
                // 如果是红黑树调用红黑树的API
                if (p instanceof TreeNode)
                    node = ((TreeNode<K,V>)p).getTreeNode(hash, key);
                else {
                    // 链表结构进行处理
                    do {
                        if (e.hash == hash &&
                            ((k = e.key) == key ||
                             (key != null && key.equals(k)))) {
                            node = e;
                            break;
                        }
                        p = e;
                    } while ((e = e.next) != null);
                }
            }
            // 比较找到的key的value和要删除的是否匹配
            if (node != null && (!matchValue || (v = node.value) == value ||
                                 (value != null && value.equals(v)))) {
                // 按着红黑树的方式进行删除
                if (node instanceof TreeNode)
                    ((TreeNode<K,V>)node).removeTreeNode(this, tab, movable);
                // 
                else if (node == p)
                    tab[index] = node.next;
                // 按着链表删除
                else
                    p.next = node.next;
                ++modCount;
                --size;
                afterNodeRemoval(node);
                return node;
            }
        }
        return null;
    }

1.7红黑树

红黑树是2-3树思想的一种简单实现,主要是对2-3树的一种编码,使用标准二叉树和一些额外信息来表示2-3树(这些信息用来表示该节点的类型是2-节点还是3-节点。如下图所示,E节点和J节点就可以看成一个3-节点,C节点比E小,H节点在E和J中间,L节点在J节点右边。
在这里插入图片描述
通过给二叉树添加一个信息进而模拟2-3树。一个红黑树具有黑色和红色两种节点之间的连接方式:

  • 红连接:该节点和其父节点构成一个3-节点。
  • 黑连接:该节点为普通的2-节点。

红黑树有几点规范要求:

  1. 红连接都是左连接
  2. 没有任何节点同时和两条红连接相连。
  3. 树是黑色平衡的,任意空连接到根节点上的黑连接长度相等。

但是实际使用过程中总会出现违背规则的情况,面对颜色的不平衡,红黑树有一套独特的自适应调整方法,这个将在下面的章节详细论述。

颜色调整

左旋

当某个节点的左子节点为黑色,右子节点为红色则需要左旋 。
在这里插入图片描述

以上图为例,左旋调整的步骤如下:

  1. E节点的右子节点指向S节点的左子节点。e.right = s.left
  2. S节点的左子节点指向E。s.left = e;
  3. E节点的颜色赋值给S节点。s.color = e.color
  4. E节点的颜色变红。e.color = RED

右旋

某个节点的左子节点是红色,左子节点的左子节点依旧是红色则需要右旋。(不会解决颜色问题,仍旧不符合红黑树的定义,需要配合颜色翻转)
在这里插入图片描述

以上图为例,右旋调整的步骤如下:

  1. S节点的左子节点指向E节点的右子节点。s.left = e.right
  2. E节点的右子节点指向S节点。e.right = s
  3. S节点的颜色赋值给E节点。e.color = s.color
  4. S节点的颜色变红。s.color = RED

颜色翻转

当一个节点的左右节点都是红色的时候,左右节点的颜色都变为黑色,指向它的颜色变为红色。但是需要注意根节点的颜色永远是黑色。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

小猪猪家的大猪猪

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值