java 集合学习

集合

Collection

public interface Collection<E> extends Iterable<E> {
    int size();
    boolean contains(Object o);
    Object[] toArray();
    <T> T[] toArray(T[] a);
    boolean add(E e);
    boolean remove(Object o);
    boolean containsAll(Collection<?> c);
    boolean addAll(Collection<? extends E> c);
    boolean removeAll(Collection<?> c);
    default boolean removeIf(Predicate<? super E> filter) {
        Objects.requireNonNull(filter);
        boolean removed = false;
        final Iterator<E> each = iterator();
        while (each.hasNext()) {
            if (filter.test(each.next())) {
                each.remove();
                removed = true;
            }
        }
        return removed;
    }
    boolean retainAll(Collection<?> c);
    void clear();
    boolean equals(Object o);
    int hashCode();

list

ArrayList

  • 初始化
// 默认初始容量
private static final int DEFAULT_CAPACITY = 10; 
// 空的数据组,数组在java中也是一个对象
private static final Object[] EMPTY_ELEMENTDATA = {};
private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {}; 
transient Object[] elementData; // 数据存放
// 数组最大长度,避免报OutOfMemoryError,
private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;
  • 构造
public ArrayList(int initialCapacity) {
    if (initialCapacity > 0) {
        this.elementData = new Object[initialCapacity];
    } else if (initialCapacity == 0) {
        // 当数组容量为0时,给elementData赋值空对象
        this.elementData = EMPTY_ELEMENTDATA; 
    } else {
        throw new IllegalArgumentException("Illegal Capacity: "+
                                           initialCapacity);
    }
}
  • 数组长度调整
    public void ensureCapacity(int minCapacity) {
        int minExpand = (elementData != DEFAULTCAPACITY_EMPTY_ELEMENTDATA)
            // any size if not default element table
            ? 0
            // larger than default for default empty table. It's already
            // supposed to be at default size.
            : DEFAULT_CAPACITY;

        if (minCapacity > minExpand) {
            ensureExplicitCapacity(minCapacity);
        }
    }

	
    private void  ensureCapacityInternal(int minCapacity) {
        if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) { 
            minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity);
        }

        ensureExplicitCapacity(minCapacity);
    }

    private void ensureExplicitCapacity(int minCapacity) {
        modCount++; // 修改数+1,

        // 新长度大于过去的长度,批量插入调用的也是该方法,因此不能保证先前长度 * 1.5 一定能大于需要的长度
        if (minCapacity - elementData.length > 0) 
            grow(minCapacity);
    }

    private void grow(int minCapacity) {
            // overflow-conscious code
        int oldCapacity = elementData.length;
        // 新长度大于过去的长度,批量插入调用的也是该方法,因此不能保证先前长度 * 1.5 一定能大于需要的长度
        int newCapacity = oldCapacity + (oldCapacity >> 1); 
        if (newCapacity - minCapacity < 0)
            newCapacity = minCapacity;
        if (newCapacity - MAX_ARRAY_SIZE > 0)
            newCapacity = hugeCapacity(minCapacity);
        elementData = Arrays.copyOf(elementData, newCapacity);
    }

    private static int hugeCapacity(int minCapacity) {
        if (minCapacity < 0) // overflow
            throw new OutOfMemoryError();
        // 从这里可以看出实际MAX_ARRAY_SIZE更接近于一种规范,实际数组长度最大应该为Integer.MAX_VALUE
        return (minCapacity > MAX_ARRAY_SIZE) ? 
            Integer.MAX_VALUE :
            MAX_ARRAY_SIZE;
    }
  • 新增
public boolean add(E e) {
    ensureCapacityInternal(size + 1);  // 数组长度加1,
    elementData[size++] = e;
    return true;
}

public void add(int index, E element) {
     // 校验index范围是否合法
    rangeCheckForAdd(index);

    ensureCapacityInternal(size + 1);  
    // System.arraycopy 是本地方法效率比for循环高
    System.arraycopy(elementData, index, elementData, index + 1,
                     size - index);
    elementData[index] = element;
    size++;
}
  • 删除
public E remove(int index) {
    rangeCheck(index);

    modCount++;
    E oldValue = elementData(index);

    int numMoved = size - index - 1;
    if (numMoved > 0)
        System.arraycopy(elementData, index+1, elementData, index,
                         numMoved); 
    elementData[--size] = null; // 置null,以便调用jvm gc线程

    return oldValue;
}

// 删除元素,可以看到删除的元素是第一个匹配的,同时插入元素可以为null
public boolean remove(Object o) {
    if (o == null) {
        for (int index = 0; index < size; index++)
            if (elementData[index] == null) {
                fastRemove(index);
                return true;
            }
    } else {
        for (int index = 0; index < size; index++)
            if (o.equals(elementData[index])) {
                fastRemove(index);
                return true;
            }
    }
    return false;
}

   private boolean batchRemove(Collection<?> c, boolean complement) {
        final Object[] elementData = this.elementData;
        int r = 0, w = 0;
        boolean modified = false;
        try {
            for (; r < size; r++)
                if (c.contains(elementData[r]) == complement) // 和remove不同,是替换所有,而不是第一个匹配的
                    elementData[w++] = elementData[r]; // 双指针
        } finally {
            // c.contains可能抛出,空指针异常,或者类型不匹配异常
            if (r != size) {
                // 剩下的当做他们全部满足条件,不过感觉直接把错误丢出去更好
                System.arraycopy(elementData, r,
                                 elementData, w,
                                 size - r);
                w += size - r;
            }
            if (w != size) {
                for (int i = w; i < size; i++)
                    elementData[i] = null;
                modCount += size - w; // modcount 加上 删除的数量
                size = w;
                modified = true;
            }
        }
        return modified;
    }
  • 清空
// 这步如果clear同时,有另外一个线程正在执行修改操作会怎么样
public void clear() {
    modCount++;

    for (int i = 0; i < size; i++)
        elementData[i] = null;

    size = 0;
}
  • ArrayList 对并发做了哪些努力(大部分感觉不做也行)
@Override
public void forEach(Consumer<? super E> action) {
    Objects.requireNonNull(action);
    final int expectedModCount = modCount;
    @SuppressWarnings("unchecked")
    final E[] elementData = (E[]) this.elementData;
    final int size = this.size;
    for (int i=0; modCount == expectedModCount && i < size; i++) {
        action.accept(elementData[i]);
    }
    // modCount的意义,判定在遍历中是否存在元素被修改,这也就是为什么执行foreach的时候不能修改ArrayList元素的原因
    if (modCount != expectedModCount) {
        throw new ConcurrentModificationException();
    }
}

LinkedList

  • 初始化
transient int size = 0;
transient Node<E> first; // 指向第一个节点
transient Node<E> last; // 指向最后一个节点
  • 插入首节点
private void linkFirst(E e) {
    final Node<E> f = first;
    final Node<E> newNode = new Node<>(null, e, f);
    first = newNode;
    if (f == null)
        last = newNode;
    else
        f.prev = newNode;
    size++;
    modCount++;
}
  • 删除首节点
// 把 f 节点的首节点清理掉,实际看下去f传进来的其实就是first,所以不理解为什么要绕一圈,可能是因为避免另外的这时候刚好执行了插入首节点,这样结果就会出乎预料
private E unlinkFirst(Node<E> f) {
    // assert f == first && f != null;
    final E element = f.item;
    final Node<E> next = f.next;
    f.item = null;
    f.next = null; // help GC
    first = next;
    if (next == null)
        last = null;
    else
        next.prev = null;
    size--;
    modCount++;
    return element;
}

Set(Map伪装成Set的数据结构)

HashSet

  • 赋值
private transient HashMap<E,Object> map;
// Dummy value to associate with an Object in the backing Map
private static final Object PRESENT = new Object();
  • 插入
public boolean add(E e) {
    return map.put(e, PRESENT)==null;
}

TreeSet

  • 赋值
private transient NavigableMap<E,Object> m;
// Dummy value to associate with an Object in the backing Map
private static final Object PRESENT = new Object();

Queue

ArrayDeque

  • 初始化值
transient Object[] elements; 
transient int head;
transient int tail;

/**
 * The minimum capacity that we'll use for a newly created deque.
 * Must be a power of 2.
 */
private static final int MIN_INITIAL_CAPACITY = 8;
  • 容量修正(代码和 HashMap 判断扩容几乎一样)
  • 举例(为了清晰和偷懒就当 int 只有 8 位) 00001000 -> 00001100 -> 00001111 -> 00010000
private void allocateElements(int numElements) {
    int initialCapacity = MIN_INITIAL_CAPACITY;
    // Find the best power of two to hold elements.
    // Tests "<=" because arrays aren't kept full.
    if (numElements >= initialCapacity) {
        initialCapacity = numElements;
        initialCapacity |= (initialCapacity >>>  1);
        initialCapacity |= (initialCapacity >>>  2);
        initialCapacity |= (initialCapacity >>>  4);
        initialCapacity |= (initialCapacity >>>  8);
        initialCapacity |= (initialCapacity >>> 16);
        initialCapacity++;

        if (initialCapacity < 0)   // Too many elements, must back off
            initialCapacity >>>= 1;// Good luck allocating 2 ^ 30 elements
    }
    elements = new Object[initialCapacity];
}
  • 构造函数
public ArrayDeque() {
    elements = new Object[16];
}
  • 新增
public void addFirst(E e) {
    if (e == null)
        throw new NullPointerException();
    elements[head = (head - 1) & (elements.length - 1)] = e; // 到了这一步就可以发现要求容量是2的幂次的意义了,位运算速度优于取模运算
    if (head == tail)
        doubleCapacity(); // 看到名字就知道了吧,扩容两倍
}
  • 删除
public E pollFirst() {
    int h = head;
    @SuppressWarnings("unchecked")
    E result = (E) elements[h];
    // Element is null if deque empty
    if (result == null)
        return null;
    elements[h] = null;     // Must null out slot
    head = (h + 1) & (elements.length - 1);
    return result;
}

PriorityQueue

  • 字段
private static final int DEFAULT_INITIAL_CAPACITY = 11; // 10 + 1
// 采用堆排序 2 * n + 1
transient Object[] queue; 
private int size = 0;
private final Comparator<? super E> comparator;
transient int modCount = 0; // non-private to simplify nested class access
  • 扩容
private void grow(int minCapacity) {
    int oldCapacity = queue.length;
    // Double size if small; else grow by 50%
    int newCapacity = oldCapacity + ((oldCapacity < 64) ?
                                     (oldCapacity + 2) :
                                     (oldCapacity >> 1)); 
    // overflow-conscious code
    if (newCapacity - MAX_ARRAY_SIZE > 0)
        newCapacity = hugeCapacity(minCapacity);
    queue = Arrays.copyOf(queue, newCapacity);
}
  • 新增
public boolean offer(E e) {
    if (e == null)
        throw new NullPointerException();
    modCount++;
    int i = size; // 应该也是为了并发,如果这时候后一个线程执行了clear操作,以最后执行的一个为准
    if (i >= queue.length)
        grow(i + 1);
    size = i + 1;
    if (i == 0)
        queue[0] = e;
    else
        siftUp(i, e);
    return true;
}
  • 平衡
// 从代码中也可以看出prioriQueue是一个小顶堆
private void siftUp(int k, E x) {
    if (comparator != null) 
        siftUpUsingComparator(k, x);
    else // 没有传入自定义的比较就使用类 E 的compareTo 函数
        siftUpComparable(k, x);
}

@SuppressWarnings("unchecked")
private void siftUpComparable(int k, E x) {
    Comparable<? super E> key = (Comparable<? super E>) x; // 所以如果没有传入 compare, 那么 E 必须继承自 Comparable,否则会报类型转换异常
    while (k > 0) {
        int parent = (k - 1) >>> 1; /// 无符号右移?可以用 >>替换吗 
        Object e = queue[parent];
        if (key.compareTo((E) e) >= 0)
            break;
        queue[k] = e;
        k = parent;
    }
    queue[k] = key;
}

@SuppressWarnings("unchecked")
private void siftUpUsingComparator(int k, E x) {
    while (k > 0) {
        int parent = (k - 1) >>> 1;
        Object e = queue[parent];
        if (comparator.compare(x, (E) e) >= 0)
            break;
        queue[k] = e;
        k = parent;
    }
    queue[k] = x;
}

Map

HashMap

  • 字段
// HashMap 默认长度 16
static final int DEFAULT_INITIAL_CAPACITY = 1 << 4; 
// hashmap数组的最大长度,如果新的长度大于最大值,则不扩容,而是在原有的桶继续插入值
static final int MAXIMUM_CAPACITY = 1 << 30;
// 负载因子
static final float DEFAULT_LOAD_FACTOR = 0.75f;
// 树化: 理论上 链表最大长度应该是7,但实际由于MIN_TREEIFY_CAPACITY的存在实际最大可能是9
static final int TREEIFY_THRESHOLD = 8;
// 解树化
static final int UNTREEIFY_THRESHOLD = 6;
// hashmap 树化的最小数组长度,小于该长度,当链表大于8,优先考虑扩容而不是转化为树,同时,由于树化的存在,列表长度是有可能大于8的
static final int MIN_TREEIFY_CAPACITY = 64;
  • 执行过程(增加删除)-> 计算hash -> 执行修改函数->调用平衡方法
  • 节点结构
static class Node<K,V> implements Map.Entry<K,V> {
    final int hash;
    final K key;
    V value;
    Node<K,V> next; // 转化成链表使用,以及遍历整个map时需要用到,因为 TreeNode 也在维护节点 next,所以可以通过 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;
    }
}
  • 计算hash
static final int hash(Object key) {
    int h;
    return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16); // 之所以 ^ (h >>> 16) 当长度较小时,也能充分利用到 hashcode
}
  • 计算初始长度(意义同 ArrayDeque,不过 HashMap 还得加上在扩容的时候能尽可能少的修改节点的桶)
  • 例子:长度 4, 新的长度8,hash = 00011011,00001011
  • hash = 00011011:原来的桶 00001011,现在的桶 00011011 位置向右移动 2 ^ 4 长度
  • hash = 00001011:原来的桶 00001011,现在的桶 00001011 位置不变
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;
}
  • 构造函数
public HashMap(int initialCapacity, float loadFactor) {
    if (initialCapacity < 0)
        throw new IllegalArgumentException("Illegal initial capacity: " +
                                           initialCapacity);
    if (initialCapacity > MAXIMUM_CAPACITY)
        initialCapacity = MAXIMUM_CAPACITY;
    if (loadFactor <= 0 || Float.isNaN(loadFactor))
        throw new IllegalArgumentException("Illegal load factor: " +
                                           loadFactor);
    this.loadFactor = loadFactor;
    this.threshold = tableSizeFor(initialCapacity); 
}
  • 扩容
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;
    if (oldCap > 0) {
        if (oldCap >= MAXIMUM_CAPACITY) { // 如果先前容量已经大于 1 << 30 则不会再进行扩容
            threshold = Integer.MAX_VALUE; // 修改允许放置的元素个数为 Integer.MAX_VALUE
            return oldTab; // 同时由于容量未曾更改,自然也就不需要将内部元素进行重新分配位置
        }
        else if ((newCap = oldCap << 1) < MAXIMUM_CAPACITY && // 每次resize都将长度翻一倍
                 oldCap >= DEFAULT_INITIAL_CAPACITY) // 如果新长度小于允许的最大长度1 << 30 并且 大于默认长度 1 << 4
            newThr = oldThr << 1; // 允许元素个数翻倍
    }
    else if (oldThr > 0) // initial capacity was placed in threshold
        newCap = oldThr;
    else {               // 实际执行初始化hashmap的操作
        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) { // 如果不是初次resize
        for (int j = 0; j < oldCap; ++j) {
            Node<K,V> e;
            if ((e = oldTab[j]) != null) {
                oldTab[j] = null;
                if (e.next == null) // 如果没有与e相同的hash的元素,扩容两倍相当于 j & (e.hash & (newCap >> 1)) 根据位运算很明显扩容后也不可能在新的 bucket 中有其他元素
                    newTab[e.hash & (newCap - 1)] = e; // 这步 100000 - 1 = 111111,如果容量是2的倍数可以最大化利用hash的位,同时resize可以让大约一半的数据不需要重新定位
                else if (e instanceof TreeNode) // 如果e是树形节点,需要遍历树形的每个节点,重新分配bucket
                    ((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) { // 新加入位运算的一位进行 & 运算, 如果结果为0,证明该节点在新的列表中所在的 bucket 不变
                            if (loTail == null)
                                loHead = e;
                            else
                                loTail.next = e;
                            loTail = e; // 从这里也可以看出更新过后的 bucket 在同一个 bucket 中的元素相对顺序不变。
                        }
                        else { // 反之,将该节点移动到新的 bucket 中
                            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;
}
  • 树化
// 当 tab 长度小于序列化树的最小长度时,优先尝试resize(), 而不是将节点变为红黑树结构
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) {
        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);
    }
}
  • 红黑树性质
  1. 每个结点要么是红的要么是黑的。
  2. 根结点是黑的。
  3. 每个叶结点(叶结点即指树尾端NIL指针或NULL结点)都是黑的。
  4. 如果一个结点是红的,那么它的两个儿子都是黑的。
  5. 对于任意结点而言,其到叶结点树尾端NIL指针的每条路径都包含相同数目的黑结点。
  • 红黑树结构
static final class TreeNode<K,V> extends LinkedHashMap.Entry<K,V> {
    TreeNode<K,V> parent;  // red-black tree links
    TreeNode<K,V> left;
    TreeNode<K,V> right;
    TreeNode<K,V> prev;    // needed to unlink next upon deletion
    boolean red;
    TreeNode(int hash, K key, V val, Node<K,V> next) {
        super(hash, key, val, next);
    }
}
  • 红黑树插入
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; // 返回值是数值的长度2 ^ m
    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) { // 这步为什么不插在开头,访问过的对象近期也很有可能会被访问到,不管从插入还是查询效率插在开头都会比较好吧(猜测,如果是插入开头应该会写成这样 e.next = p, table[i] = e,如果这时候执行了删除p的操作,这个桶原有节点就全部丢失了,插在最后虽然也可能会出现这种情况,丢失的一般也就一个新插入的节点)
                if ((e = p.next) == null) {
                    p.next = newNode(hash, key, value, null); // 从这步可以看到列表是没有排过序的,插入到列表尾部
                    if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st // 如果链表的长度大于等于8,则将这个节点变成树
                        treeifyBin(tab, hash); // 如果 table 现有容量小于64,则先执行扩容,而不是树化,以空间换时间
                    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) // 对应putifabsent 方法,如果存在则退出
                e.value = value;
            afterNodeAccess(e);
            return oldValue;
        }
    }
    ++modCount;
    if (++size > threshold)
        resize(); // 长度大于 capital * 负载因子 的实际可允许放置元素个数
    afterNodeInsertion(evict);
    return null;
}

红黑树删除操作

final Node<K,V> removeNode(int hash, Object key, Object value,
                           boolean matchValue, boolean movable) { // 删除节点并返回被删除节点的值,不存在则返回null
    Node<K,V>[] tab; Node<K,V> p; int n, index;
    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) {
            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);
            }
        }
        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) // 判断是否为buchet的根节点
                tab[index] = node.next;
            else
                p.next = node.next;
            ++modCount;
            --size;
            afterNodeRemoval(node);
            return node;
        }
    }
    return null;
}
  • 移除树节点
        final void removeTreeNode(HashMap<K,V> map, Node<K,V>[] tab,
                                  boolean movable) {
            int n;
            if (tab == null || (n = tab.length) == 0)
                return;
            int index = (n - 1) & hash;
            TreeNode<K,V> first = (TreeNode<K,V>)tab[index], root = first, rl;
            TreeNode<K,V> succ = (TreeNode<K,V>)next, pred = prev;
            if (pred == null) // 判断是否是第一个有值桶的根节点
                tab[index] = first = succ;
            else
                pred.next = succ;
            if (succ != null) // 当前被删除节点是最后一个节点
                succ.prev = pred;
            if (first == null) // first = succ 也就是被删除的是该桶中唯一的一个节点
                return;
            if (root.parent != null) // 找寻根节点,为什么不用first?
                root = root.root();
            if (root == null || root.right == null ||
                (rl = root.left) == null || rl.left == null) { // 这步如果rl.left == null 的情况下,红黑树最多应该可以有 rr 有 1 + 1,rl最多1 + 2 + 4 = 7, 减掉一个被删除的,所以最多有9个节点,虽然节点大于6但就论平均状态应该是 (3 + 9) / 2 = 6,解树化的效率在接受范围内
                tab[index] = first.untreeify(map);  // too small // 都解树化了自然不需要改变parent,left,right的值了
                return;
            }
            TreeNode<K,V> p = this, pl = left, pr = right, replacement;
            if (pl != null && pr != null) { // 左右节点都不为空,同时需要把两个节点都接到parent上
                TreeNode<K,V> s = pr, sl;
                while ((sl = s.left) != null) // find successor // 此时s节点为红色节点或者左右节点都为空
                    s = sl;
                boolean c = s.red; s.red = p.red; p.red = c; // swap colors
                TreeNode<K,V> sr = s.right;
                TreeNode<K,V> pp = p.parent;
                if (s == pr) { // p was s's direct parent
                    p.parent = s; // 把pr节点移动到p的位置,虽然不是搜索树,但p节点本身就是需要删除的,那么删除掉整棵树依旧满足搜索树性质
                    s.right = p;
                }
                else {
                    TreeNode<K,V> sp = s.parent; //
                    if ((p.parent = sp) != null) { // sp
                        if (s == sp.left)
                            sp.left = p;
                        else
                            sp.right = p;
                    }
                    if ((s.right = pr) != null)
                        pr.parent = s;
                }
                p.left = null;
                if ((p.right = sr) != null) // 上述操作就是把s节点与p节点位置互换,颜色互换,s.left==null,理论上s就是sr子树中最小的一个节点,所以即使把s和p互换,一般情况下,依旧满足搜索树性质,需要调整的就是如何满足红黑树性质
                    sr.parent = p;
                if ((s.left = pl) != null)
                    pl.parent = s;
                if ((s.parent = pp) == null)
                    root = s;
                else if (p == pp.left)
                    pp.left = s;
                else
                    pp.right = s;
                if (sr != null)
                    replacement = sr;
                else
                    replacement = p;
            }
            else if (pl != null) // 左节点或者右节点为null,这操作直接接在下面就行
                replacement = pl;
            else if (pr != null)
                replacement = pr;
            else // 被删除的是叶子节点
                replacement = p;
            if (replacement != p) {
                TreeNode<K,V> pp = replacement.parent = p.parent;
                if (pp == null)
                    root = replacement;
                else if (p == pp.left)
                    pp.left = replacement;
                else
                    pp.right = replacement;
                p.left = p.right = p.parent = null;
            }

            TreeNode<K,V> r = p.red ? root : balanceDeletion(root, replacement); // p和

            if (replacement == p) {  // detach
                TreeNode<K,V> pp = p.parent;
                p.parent = null;
                if (pp != null) {
                    if (p == pp.left)
                        pp.left = null;
                    else if (p == pp.right)
                        pp.right = null;
                }
            }
            if (movable)
                moveRootToFront(tab, r);
        }

流程:

图:

交换s和this颜色

avatar

  • 红黑树插入平衡操作(实际是作为 TreeNode 的静态类方法存在)
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;
}

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;
}

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;;) {
        if ((xp = x.parent) == null) { // 插入节点就是根节点不用做平衡 (情况1)
            x.red = false;
            return x;
        }
        else if (!xp.red || (xpp = xp.parent) == null) // 插入节点的父节点是黑色节点,或者 xp 的父节点为空即 x 为第二层的节点(情况二)
            return root;
        if (xp == (xppl = xpp.left)) {  // xp 为 xp父节点的左节点
            if ((xppr = xpp.right) != null && xppr.red) { // 如果 xpp 的右节点不为空, 且右节点为红色
                xppr.red = false;  // 将颜色变成根节点为红色,左右节点为黑色,这时以 xpp 为根节点的红黑树已经满足了到所有子节点的黑色节点个数相等的效果
                xp.red = false; // 但由于 xpp 这颗子树实际操作后比同层的其他子树多了一个黑色节点,所以之后需要把xpp看做一个整体重复以上步骤
                xpp.red = true;
                x = xpp;
            }
            else { // 右边节点为空 (由于 xp 颜色是 红色,如果 xppr 是黑色节点,左右节点黑色节点个数不相等)
                if (x == xp.right) {
                    root = rotateLeft(root, x = xp);
                    xpp = (xp = x.parent) == null ? null : xp.parent;
                }
                if (xp != null) {
                    xp.red = false;
                    if (xpp != null) {
                        xpp.red = true;
                        root = rotateRight(root, xpp); 
                    }
                }// x当前位置还是叶子节点级别,重来一次,避免这时候如果有插入等事件的发生
            }
        }
        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);
                    }
                }
            }
        }
    }
}

avatar

  • 红黑树标准结构(下面图,数字只是序号不是key值,同时map树化最少个数为7,画三个节点只是为了省事)

avatar

  • 情况三

avatar

  • 情况四(走到这步的时候,正常情况3下面是不可能存在节点,想一下如果3下面还有节点,那必定是黑色节点,这就违背了红黑树的性质5)
  • 左旋
    avatar
  • 右旋
    avatar

TreeMap

  • 常数:
private final Comparator<? super K> comparator; // 传入的比较函数,没有则将按照传入类的compareTo方法进行比较

private transient Entry<K,V> root; // 数据结构参看TreeNode,除了没有next指针

/**
 * size 默认从空开始,意义不大
 */
private transient int size = 0;

/**
 * 所有集合类型都有的modCount,用于实现fast-fail
 */
private transient int modCount = 0;
  • 构造函数
public TreeMap(Comparator<? super K> comparator) {
    this.comparator = comparator;
}
  • 获取键值
final Entry<K,V> getEntry(Object key) {
    // Offload comparator-based version for sake of performance
    if (comparator != null)
        return getEntryUsingComparator(key);
    if (key == null)
        throw new NullPointerException();
    @SuppressWarnings("unchecked")
        Comparable<? super K> k = (Comparable<? super K>) key;
    Entry<K,V> p = root;
    while (p != null) {
        int cmp = k.compareTo(p.key);
        if (cmp < 0)
            p = p.left;
        else if (cmp > 0)
            p = p.right;
        else
            return p;
    }
    return null;
}
  • 找到大于等于这个键的最小整数
final Entry<K,V> getCeilingEntry(K key) {
    Entry<K,V> p = root;
    while (p != null) {
        int cmp = compare(key, p.key); // getEntryUsingComparator 显得就有点多余了
        if (cmp < 0) {
            if (p.left != null)
                p = p.left;
            else
                return p;
        } else if (cmp > 0) { // key > p.key
            if (p.right != null) { 
                p = p.right;
            } else {
                Entry<K,V> parent = p.parent; // 查找 rp 节点
                Entry<K,V> ch = p;
                while (parent != null && ch == parent.right) { // 找到最后一个左拐点,没找到左拐点返回null
                    ch = parent;
                    parent = parent.parent;
                }
                return parent;
            }
        } else
            return p;
    }
    return null;
}
  • 插入(逻辑很简单,注意下根节点为空判断)
public V put(K key, V value) {
    Entry<K,V> t = root;
    if (t == null) {
        compare(key, key); // type (and possibly null) check

        root = new Entry<>(key, value, null);
        size = 1;
        modCount++;
        return null;
    }
    int cmp;
    Entry<K,V> parent;
    // split comparator and comparable paths
    Comparator<? super K> cpr = comparator;
    if (cpr != null) {
        do {
            parent = t;
            cmp = cpr.compare(key, t.key);
            if (cmp < 0)
                t = t.left;
            else if (cmp > 0)
                t = t.right;
            else
                return t.setValue(value);
        } while (t != null);
    }
    else {
        if (key == null)
            throw new NullPointerException();
        @SuppressWarnings("unchecked")
            Comparable<? super K> k = (Comparable<? super K>) key;
        do {
            parent = t;
            cmp = k.compareTo(t.key);
            if (cmp < 0)
                t = t.left;
            else if (cmp > 0)
                t = t.right;
            else
                return t.setValue(value);
        } while (t != null);
    }
    Entry<K,V> e = new Entry<>(key, value, parent);
    if (cmp < 0)
        parent.left = e;
    else
        parent.right = e;
    fixAfterInsertion(e);
    size++;
    modCount++;
    return null;
}
  • 插入平衡

    代码同 hashmap 比起来基本一模一样,但不再存储中间状态的变量,而是选择实时获取,个人感觉可读性更强,也可能是由于先看了一遍hashmap

private void fixAfterInsertion(Entry<K,V> x) {
    x.color = RED; // 插入节点默认为红色

    while (x != null && x != root && x.parent.color == RED) { // 当前节点不为空,不是根节点,父节点颜色不等于红色
        if (parentOf(x) == leftOf(parentOf(parentOf(x)))) { // xp 是 xpp 的左节点
            Entry<K,V> y = rightOf(parentOf(parentOf(x))); // xppr 是 xpp 的右节点
            if (colorOf(y) == RED) { // xppr 颜色是红色
                setColor(parentOf(x), BLACK); // xp.color = BLACK;
                setColor(y, BLACK); // xppr.color = BLACK;
                setColor(parentOf(parentOf(x)), RED); // xpp.color = RED;
                x = parentOf(parentOf(x)); // X= XPP;
            } else {
                if (x == rightOf(parentOf(x))) {
                    x = parentOf(x);
                    rotateLeft(x); // 左旋,图看上面
                }
                setColor(parentOf(x), BLACK);
                setColor(parentOf(parentOf(x)), RED); // xpp 颜色置为红, xp置为黑
                rotateRight(parentOf(parentOf(x)));
            }
        } else { // 同上
            Entry<K,V> y = leftOf(parentOf(parentOf(x)));
            if (colorOf(y) == RED) {
                setColor(parentOf(x), BLACK);
                setColor(y, BLACK);
                setColor(parentOf(parentOf(x)), RED);
                x = parentOf(parentOf(x));
            } else {
                if (x == leftOf(parentOf(x))) {
                    x = parentOf(x);
                    rotateRight(x);
                }
                setColor(parentOf(x), BLACK);
                setColor(parentOf(parentOf(x)), RED);
                rotateLeft(parentOf(parentOf(x)));
            }
        }
    }
    root.color = BLACK; // 根节点设置为根节点
}

引用

posted @ 2021-04-21 18:13  异客LLL  阅读(39)  评论(0)    收藏  举报