TreeMap 集合源码分析

TreeMap 集合源码分析


在这里插入图片描述

建议

  • TreeMap是一个可用来排序的Map集合。 底层是使用红黑树来存储数据的,节点为 Entry<K,V>,K 用来排序,V 为实际要存储的值。你可以传入自定义比较器来对K进行排序,也可使用TreeMap的自然排序。默认 左子节点更小,右子节点更大。
  • 在学习TreeMap 源码之前一定要对红黑树有所了解才可以,不然很难看懂里面关于红黑树的一些操作。如果你的数据结构不太好,一下学习红黑树又觉得太困难,建议你先学 二叉树 -> 二叉搜索树 -> 平衡二叉搜索树(AVL树)-> B树 -> 红黑树,知识都是层层递进的。

一、字段分析

//用于比较K的比较器,从而对 K 进行排序
private final Comparator<? super K> comparator;
//红黑树的跟节点,默认为null
private transient Entry<K,V> root = null;
//实际存储数据的节点个数
private transient int size = 0;
//版本号(也有称呼修改计数器),每次操作集合是更新该值,用于使用迭代器迭代过程中对集合是否发生修改做判断,
//因为不允许迭代过程中对集合进行操作,否在抛出 并发修改异常 ConcurrentModificationException。
private transient int modCount = 0;

//用来操作 节点entry 的集合,也可用来获取 entry 的迭代器
private transient EntrySet entrySet;
//用来操作 节点key 的集合,功能非常的丰富,也可用来获取 key的迭代器。
private transient KeySet<K> navigableKeySet;
//NavigableMap 是 Java 集合框架中的一个接口,它扩展了 SortedMap 接口,提供了更丰富的导航和搜索功能。说人话就是提供了很多
//花里胡哨的根据key获取entry的方法,功能非常的丰富。从未用过...
private transient NavigableMap<K,V> descendingMap;
//红黑树节点颜色 红色
private static final boolean RED   = false;
//红黑树节点颜色 黑色
private static final boolean BLACK = true;
//---------------------------------
//继承自父类AbstractMap,用来操作 节点 value 集合,和获取value的迭代器
transient Collection<V> values;

二、构造方法分析

//无参构造器
public TreeMap() {
        comparator = null;
    }
//传入自定义的比较器构造器
public TreeMap(Comparator<? super K> comparator) {
        this.comparator = comparator;
    }
    
//通过传入的 容器map 进行构建
public TreeMap(Map<? extends K, ? extends V> m) {
        comparator = null;
        putAll(m);
    }
//将 map 中的所有元素添加到 TreeMap中
public void putAll(Map<? extends K, ? extends V> map) {
		//需要添加的元素数量
        int mapSize = map.size();
        //如果 当前 treeMap 没有元素 且 要添加的元素数量 > 0 且 参数map是有序的map
        //1:为什么要 treeMap 为空呢?
        //这个和 buildFromSorted 的构建有关系,buildFromSorted 必须从 treeMap 是空的状态构建才能是平衡的,具体可看下面
        //buildFromSorted 的详细介绍。
        if (size==0 && mapSize!=0 && map instanceof SortedMap) {
            Comparator<?> c = ((SortedMap<?,?>)map).comparator();
            if (c == comparator || (c != null && c.equals(comparator))) {
                ++modCount;
                try {
                	//从有序的容器中构建map中 红黑树
                    buildFromSorted(mapSize, map.entrySet().iterator(),
                                    null, null);
                } catch (java.io.IOException cannotHappen) {
                } catch (ClassNotFoundException cannotHappen) {
                }
                return;
            }
        }
        //否则会循环遍历 map 一个一个节点进行添加到 treeMap 中
        super.putAll(map);
    }

//父类 AbstractMap 的方法
public void putAll(Map<? extends K, ? extends V> m) {
		//将 m 的元素遍历添加到 TreeMap 中
        for (Map.Entry<? extends K, ? extends V> e : m.entrySet())
        	//调用 在调用子类treeMap 方法添加元素,模版模式用的确实666
            put(e.getKey(), e.getValue());
    }
    
//通过传入有序的map 进行构建
public TreeMap(SortedMap<K, ? extends V> m) {
        comparator = m.comparator();
        try {
            buildFromSorted(m.size(), m.entrySet().iterator(), null, null);
        } catch (java.io.IOException cannotHappen) {
        } catch (ClassNotFoundException cannotHappen) {
        }
    }

//buildFromSorted 重载方法,该方法为构建 treemap 的入口。
private void buildFromSorted(int size, Iterator<?> it,
                                 java.io.ObjectInputStream str,
                                 V defaultVal)
        throws  java.io.IOException, ClassNotFoundException {
        this.size = size;
        root = buildFromSorted(0, 0, size-1, computeRedLevel(size),
                               it, str, defaultVal);
    }
// se 个节点能够见多少层的红黑树
private static int computeRedLevel(int sz) {
        int level = 0;
        for (int m = sz - 1; m >= 0; m = m / 2 - 1)
            level++;
        return level;
    }

//buildFromSorted 的重载方法,真正开始构建红黑树
//先介绍下参数的意义
//level:整个红黑树的构建是一层一层的构建的,level 表示需要构建多少层
//lo 和 hi: 表示可用的数据范围,初始开始时肯定是传入的map中所有的数据都可用,所以一开始是 [0,size - 1],lo为0,hi为size - 1
//it: 用来可获取可用元素的迭代器,是entrySet的迭代器,获取到的是 entry节点,包含 k 和 v
//str:同样是用来获取可用元素的,但是优先从 it中获取,it不能获取了才能从 str 中获取,和 it 作用一样,只是str是流
//default:默认值,只要是我们从 it 获 str 中获取的元素,都用 default 为 value 构建节点
//参数介绍完了,在从整个方法的功能上说明下他是具体怎么做的:
//整个方法的思想和归并排序的思想是一样的(先排序左边一部分(构建左子树),在排序右边一部分(构建右子树),在合并(左子树和右子树和父节点合并)),归并排序你可能不了解,其实和二叉树的后序遍历思想也是一样的,先遍历左子树,再遍历右子树,在到父节点。这个方法也是采用递归,想要构建红黑树,先将左子树构建成红黑树,再讲右子树构建成红黑树,左右子树
//和父节点构建一颗整树,同时左右字树的构建会递归,同样为先构建 子树的左右子树为一个红黑树,在合并。将一个问题拆分成了相同的子问题。
//那么如何保证平衡呢?很直接的将最后一层的节点都染红色,其他的全部为黑色。。。所以传入了 level(当前层数), redLevel(最的整数),
//使用 if(level == redLevel)将最后一层的节点全部染红,从而达到红黑树的平衡。
//了解了思想在看代码就可非常简单明了了
private final Entry<K,V> buildFromSorted(int level, int lo, int hi,
                                             int redLevel,
                                             Iterator<?> it,
                                             java.io.ObjectInputStream str,
                                             V defaultVal)
        throws  java.io.IOException, ClassNotFoundException {
        /*
         * Strategy: The root is the middlemost element. To get to it, we
         * have to first recursively construct the entire left subtree,
         * so as to grab all of its elements. We can then proceed with right
         * subtree.
         *
         * The lo and hi arguments are the minimum and maximum
         * indices to pull out of the iterator or stream for current subtree.
         * They are not actually indexed, we just proceed sequentially,
         * ensuring that items are extracted in corresponding order.
         */

        if (hi < lo) return null;

        int mid = (lo + hi) >>> 1;

        Entry<K,V> left  = null;
        if (lo < mid)
        	//先构建左子树
            left = buildFromSorted(level+1, lo, mid - 1, redLevel,
                                   it, str, defaultVal);

        // extract key and/or value from iterator or stream
        K key;
        V value;
        if (it != null) {
            if (defaultVal==null) {
                Map.Entry<?,?> entry = (Map.Entry<?,?>)it.next();
                key = (K)entry.getKey();
                value = (V)entry.getValue();
            } else {
                key = (K)it.next();
                value = defaultVal;
            }
        } else { // use stream
            key = (K) str.readObject();
            value = (defaultVal != null ? defaultVal : (V) str.readObject());
        }

        Entry<K,V> middle =  new Entry<>(key, value, null);

        // color nodes in non-full bottommost level red
        //将最后一层染红
        if (level == redLevel)
            middle.color = RED;
		
        if (left != null) {
            middle.left = left;
            left.parent = middle;
        }

        if (mid < hi) {
        	//在构建右子树
            Entry<K,V> right = buildFromSorted(level+1, mid+1, hi, redLevel,
            //最后左右字数和父节点合并                            
            middle.right = right;
            right.parent = middle;
        }

        return middle;
    }

三、内部类分析

  • Entry<K,V> :红黑树的节点类。
    • K:进行排序。
    • V:实际要存储的值。
    static final class Entry<K,V> implements Map.Entry<K,V> {
    	//用来排序
        K key;
        //用来存储值
        V value;
        //左子节点
        Entry<K,V> left = null;
        //右子节点
        Entry<K,V> right = null;
        //父节点
        Entry<K,V> parent;
        //颜色,默认为黑色。但是我们插入时会设为红色,即每次put往 TreeMap 中添加新节点,新节点都是红色的。
        //红色可以快速达到平衡。
        boolean color = BLACK;

        /**
         * Make a new cell with given key, value, and parent, and with
         * {@code null} child links, and BLACK color.
         */
        Entry(K key, V value, Entry<K,V> parent) {
            this.key = key;
            this.value = value;
            this.parent = parent;
        }

        /**
         * Returns the key.
         *
         * @return the key
         */
        public K getKey() {
            return key;
        }

        /**
         * Returns the value associated with the key.
         *
         * @return the value associated with the key
         */
        public V getValue() {
            return value;
        }

        /**
         * Replaces the value currently associated with the key with the given
         * value.
         *
         * @return the value associated with the key before this method was
         *         called
         */
        public V setValue(V value) {
            V oldValue = this.value;
            this.value = value;
            return oldValue;
        }

        public boolean equals(Object o) {
            if (!(o instanceof Map.Entry))
                return false;
            Map.Entry<?,?> e = (Map.Entry<?,?>)o;
            //可以看出红黑树节点的比较是key和value都相等才认为相等
            return valEquals(key,e.getKey()) && valEquals(value,e.getValue());
        }
		
        public int hashCode() {
            int keyHash = (key==null ? 0 : key.hashCode());
            int valueHash = (value==null ? 0 : value.hashCode());
            return keyHash ^ valueHash;
        }

        public String toString() {
            return key + "=" + value;
        }
    }
  • PrivateEntryIterator:抽象的Entry迭代器基类。
    • 使用了 设计模式 - 模版模式。使用了final 定义了获取元素的方法,防止子类重写。子类可直接使用,大大简化了代码量。
    abstract class PrivateEntryIterator<T> implements Iterator<T> {
    	//迭代过程中,下一个可以获取到的 Entry节点
        Entry<K,V> next;
        //上一次访问到的 Entry 节点,用于remove 进行删除,每次remove都是上次nextEntry迭代获取到的值,
        //所以不可连续调用remove
        Entry<K,V> lastReturned;
        //期待的版本号,用来校验迭代过程中是否发生过集合被修改的情况,被修改了会抛出并发修改异常。
        int expectedModCount;

		//传入的红黑树中序遍历的第一个节点
        PrivateEntryIterator(Entry<K,V> first) {
        	//更新版本号为集合的版本号
            expectedModCount = modCount;
            //因为还没访问过节点,所以是null
            lastReturned = null;
            //第一次访问当然是第一个节点了
            next = first;
        }

		//判断下一次迭代能否获取到元素
        public final boolean hasNext() {
            return next != null;
        }

		//迭代获取元素
        final Entry<K,V> nextEntry() {
        	//记录本次节点获取到的元素,用于返回
            Entry<K,V> e = next;
            //如果迭代过程中发生集合被修改,该节点被删除了,则抛出异常
            if (e == null)
                throw new NoSuchElementException();
            //检查迭代过程中集合是否被修改
            if (modCount != expectedModCount)
                throw new ConcurrentModificationException();
            //获取到e的后继节点,即中序遍历的下一个节点    
            next = successor(e);
            //记录这次访问到的节点
            lastReturned = e;
            return e;
        }

		//迭代获取上一个节点、即前驱节点,也是中序遍历的上一个节点
        final Entry<K,V> prevEntry() {
            Entry<K,V> e = next;
            if (e == null)
                throw new NoSuchElementException();
            if (modCount != expectedModCount)
                throw new ConcurrentModificationException();
            //获取前驱节点
            next = predecessor(e);
            lastReturned = e;
            return e;
        }
		//删除迭代得到的值
        public void remove() {
        	//如果上一次没有迭代,则无法删除,删除的前提是必须迭代过
            if (lastReturned == null)
                throw new IllegalStateException();
                //并发异常检查
            if (modCount != expectedModCount)
                throw new ConcurrentModificationException();
            // deleted entries are replaced by their successors
            //--------------------------------
            //这要连起来看,为什么要判断被删除节点是否有左右字树呢,这跟红黑树的性质有关,在红黑树中,如果被删除节点的度为2,
            //则使用后继节点覆盖被删除节点,并且删除掉用来覆盖的后继节点,后继节点度为0或1。
            if (lastReturned.left != null && lastReturned.right != null)
            	//这里虽然将next 设为了被删除节点,但是下次迭代也不会访问到这个被删除的节点,正如我说的度为2,
            	//删除过程中会更新为后继节点
                next = lastReturned;
            deleteEntry(lastReturned);
			//---------------------------------
			//更新版本号
            expectedModCount = modCount;
            //将上次访问元素置空,也说明连续调用remove会报错。
            lastReturned = null;
        }
    }
  • 再来看看基于模版PrivateEntryIterator的三个迭代器:
    • EntryIterator:获取 entry 节点的迭代器。
    • KeyIterator:获取 entry 里的 key 的迭代器。
    • ValueIterator:获取 entry 里的 value 的迭代器。
final class EntryIterator extends PrivateEntryIterator<Map.Entry<K,V>> {
		//这里传入的是中序遍历得到的第一个节点
        EntryIterator(Entry<K,V> first) {
            super(first);
        }
        public Map.Entry<K,V> next() {
        	//返回模版中nextEntry()获取到的节点
            return nextEntry();
        }
    }

final class KeyIterator extends PrivateEntryIterator<K> {
		//这里传入的是中序遍历得到的第一个节点
        KeyIterator(Entry<K,V> first) {
            super(first);
        }
        public K next() {
        	//返回模版中nextEntry()获取到的节点的key
            return nextEntry().key;
        }
    }

final class ValueIterator extends PrivateEntryIterator<V> {
		//这里传入的是中序遍历得到的第一个节点
        ValueIterator(Entry<K,V> first) {
            super(first);
        }
        public V next() {
       	 	//返回模版中nextEntry()获取到的节点
            return nextEntry().value;
        }
    }
  • 那么三个迭代器该如何获取到呢。TreeMap 也分别提供了内部类来获取相应的迭代器。
    • EntrySet:用来操作 entry 的集合,可获取EntryIterator迭代器 对 entry 进行迭代。
    • KeySet:用来操作 key 的集合,可获取 KeyIterator 迭代器对 key 进行迭代。
    • Values :用来操作 value 的集合,可获取 ValueIterator 对 value 进行迭代。
class EntrySet extends AbstractSet<Map.Entry<K,V>> {
        public Iterator<Map.Entry<K,V>> iterator() {
            return new EntryIterator(getFirstEntry());
        }

       ...
    }


static final class KeySet<E> extends AbstractSet<E> implements NavigableSet<E> {
        public Iterator<E> iterator() {
            if (m instanceof TreeMap)
                return ((TreeMap<E,?>)m).keyIterator();
            else
                return ((TreeMap.NavigableSubMap<E,?>)m).keyIterator();
        }
        
       ...
    }

class Values extends AbstractCollection<V> {
        public Iterator<V> iterator() {
            return new ValueIterator(getFirstEntry());
        }
        
        ...
    }

四、 方法分析

	//向TreeMap 中添加元素,如果 key 对应的entry 已存在,返回已存在 value 并使用传入的 value进行覆盖。如果没有key对应的
	//entry,返回null,红黑树的节点插入一定是插入到叶子节点中去的。我说的是:插入到叶子节点中去!!!,红黑树的叶子节点都是虚构
	//出来的空节点,我们插入位置就是这些空节点的位置,这个和的二叉树对叶子节点的定义不同,需要注意。
    public V put(K key, V value) {
    	//首先获取到根节点
        Entry<K,V> t = root;
        //如果根节点为空,则是第一次添加元素,无需调整平衡
        if (t == null) {
        	//这里是检查key,如果你传了比较器,并且比较器里是对null值做了处理的,ok没问题,但是如果传入的比较器不支持null,
        	//或者没传入比较器,那么是不允许key为 null 的,会报错
            compare(key, key); // type (and possibly null) check
			//新添加的节点为跟节点,新建节点默认为黑色
            root = new Entry<>(key, value, null);
            //节点数量 + 1
            size = 1;
            //版本 + 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 {
        	//用户没传入比较器,使用 Key class 自己的比较方法比较,同样查找新节点应该插入到哪个父节点下面
            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);
        //节点数量 + 1
        size++;
        //版本 + 1
        modCount++;
        return null;
    }


//插入元素后恢复红黑树的平衡
private void fixAfterInsertion(Entry<K,V> x) {
		//插入新元素默认为红色,红色便可以满足红黑树的四条性质,可以更快速满足五条性质恢复平衡
        x.color = RED;
		//父节点为红色
        while (x != null && x != root && x.parent.color == RED) {
        	//这个if和下面的else 只需要看一个即可,是完全对称的操作。
        	//parent = grand.left,即父节点是祖父节点的左节点 
            if (parentOf(x) == leftOf(parentOf(parentOf(x)))) {
            	//获取到 uncle 节点 
                Entry<K,V> y = rightOf(parentOf(parentOf(x)));
                //情况3:如果uncle是红色节点,则进行上溢操作来恢复平衡
                if (colorOf(y) == RED) {
                	//父节点染黑
                    setColor(parentOf(x), BLACK);
                    //uncle 节点染黑色
                    setColor(y, BLACK);
                    //grand 染成红色
                    setColor(parentOf(parentOf(x)), RED);
                    //循环迭代向上调整平衡
                    x = parentOf(parentOf(x));
                } else {
                	//情况2:uncle 为黑色,则通过旋转来回复平衡
                	//父节点是grand的右节点,即 R
                    if (x == rightOf(parentOf(x))) {
                        x = parentOf(x);
                        //左旋转
                        rotateLeft(x);
                    }
        			//父节点染黑色
                    setColor(parentOf(x), BLACK);
                    //grand节点染红
                    setColor(parentOf(parentOf(x)), RED);
                    //右旋转
                    rotateRight(parentOf(parentOf(x)));
                }
            } else {
            	//对称操作 ,left变right,right变left
                Entry<K,V> y = leftOf(parentOf(parentOf(x)));
                //情况3:如果uncle是红色节点,则进行上溢操作来恢复平衡
                if (colorOf(y) == RED) {
                    setColor(parentOf(x), BLACK);
                    setColor(y, BLACK);
                    setColor(parentOf(parentOf(x)), RED);
                    x = parentOf(parentOf(x));
                } else {
                	//情况2:uncle 为黑色,则通过旋转来回复平衡
                    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;
    }
//删除 节点k = key 的节点。
public V remove(Object key) {
		//获取到该key 的节点
        Entry<K,V> p = getEntry(key);
        //如果为存储为 key 的节点,则删除失败。
        if (p == null)
            return null;
		//找到了 为key 的几点,获取该节点的value,用于返回告知用户哪个 value 被删除了
        V oldValue = p.value;
        //删除查找到的节点p
        deleteEntry(p);
        //返回查找到的 value
        return oldValue;
    }

//删除节点p
private void deleteEntry(Entry<K,V> p) {
		//版本 + 1
        modCount++;
        //节点数量 - 1
        size--;
		
        // If strictly internal, copy successor's element to p and then make p
        // point to successor.
        // //情况1:如果当前节点为度为2,使用后继节点替换掉要被删除的节点p,在删除掉后继节点
        if (p.left != null && p.right != null) {
        	//p 的后继节点p
            Entry<K,V> s = successor(p);
            //使用后继节点替换被删除的节点
            p.key = s.key;
            p.value = s.value;
            //在删除后继节点,因为p的含义就是将要被删除的节点
            p = s;
        } // p has 2 children

        // Start fixup at replacement node, if it exists.
		//执行到这里说明 p 的节点度为1 或 o
		//节点p的孩子节点,会被用来替换掉节点p,至于如何替换还需分情况讨论
        Entry<K,V> replacement = (p.left != null ? p.left : p.right);
		情况1:如果度为1
        if (replacement != null) {
            // Link replacement to parent
            //获取父节点
            replacement.parent = p.parent;
            //如果被删除节点p是根节点,则孩子节点就是新的根节点了
            if (p.parent == null)
                root = replacement;
            //被删除节点不是跟节点,且p是父节点的左子节点,则更新父节点的左子节点为 p 的孩子节点
            else if (p == p.parent.left)
                p.parent.left  = replacement;
            else
            //同样的被删除节点不是跟节点,且p是父节点的右子节点,则更新父节点的右子节点为 p 的孩子节点
                p.parent.right = replacement;

            // Null out links so they are OK to use by fixAfterDeletion.
            //断开节点p
            p.left = p.right = p.parent = null;

            // Fix replacement
            //如果被删除节点p度为1 且 是红色节点,则无需调整红黑树的平衡,删除结束。
            if (p.color == BLACK)
            	//这个对应情况2:node节点为黑色,且子节点为红色,则使用子节点代替node节点,并染黑即可,染黑在 fixAfterDeletion里会体现出来
                fixAfterDeletion(replacement);
        } else if (p.parent == null) { // return if we are the only node.
            //只有节点 p 的情况
            root = null;
        } else { //  No children. Use self as phantom replacement and unlink.
        	情况3和4:被删除节点为黑色,调整平衡
            if (p.color == BLACK)
                fixAfterDeletion(p);
			//删除node节点,node可能调整了位置,比如3.3情况,会发生上溢,所以还需要判断是否为null
            if (p.parent != null) {
                if (p == p.parent.left)
                    p.parent.left = null;
                else if (p == p.parent.right)
                    p.parent.right = null;
                p.parent = null;
            }
        }
    }
	//节点被删除后,调整红黑树的平衡
    private void fixAfterDeletion(Entry<K,V> x) {
    	//如果是前面的情况2,不会走循环,直接染黑即可。
        while (x != root && colorOf(x) == BLACK) {
        	//如果当前节点x 是父节点的左节点
            if (x == leftOf(parentOf(x))) {
            	//获取到节点 x 的兄弟接单
                Entry<K,V> sib = rightOf(parentOf(x));
				 //情况4:被删除节点为黑色,且兄弟节点为红色
                if (colorOf(sib) == RED) {
                    setColor(sib, BLACK);
                    setColor(parentOf(x), RED);
                    rotateLeft(parentOf(x));
                    //旋转之后,兄弟节点变化了,进行更新.之后套用 情况3.2
                    sib = rightOf(parentOf(x));
                }
				//到这里 sibling兄弟节点 必为黑色了
				//情况3.2 和 情况 3.3:被删除节点为黑色,且兄弟节点为黑色,且兄弟节点没有一个红色子节点
                if (colorOf(leftOf(sib))  == BLACK &&
                    colorOf(rightOf(sib)) == BLACK) {
                    //将兄弟节点染红,然后递归调用
                    setColor(sib, RED);
                    x = parentOf(x);
                } else {
                	 //情况3.1 : 被删除节点node为黑,兄弟节点为黑,且兄弟节点至少有一个红色子节点,则需要判断旋转方向进行旋转即可
                    if (colorOf(rightOf(sib)) == BLACK) {
                        setColor(leftOf(sib), BLACK);
                        setColor(sib, RED);
                        rotateRight(sib);
                        sib = rightOf(parentOf(x));
                    }
                    //旋转之后新的父节点要继承原先的父节点
                    setColor(sib, colorOf(parentOf(x)));
                    setColor(parentOf(x), BLACK);
                    setColor(rightOf(sib), BLACK);
                    rotateLeft(parentOf(x));
                    //赋值root,结束循环,因为root必为黑
                    x = root;
                }
            } else { // symmetric
            	//全是对称情况,left 变 right ,right 变 left
                Entry<K,V> sib = leftOf(parentOf(x));
				//情况4:被删除节点为黑色,且兄弟节点为红色
                if (colorOf(sib) == RED) {
                    setColor(sib, BLACK);
                    setColor(parentOf(x), RED);
                    rotateRight(parentOf(x));
                    //旋转之后,兄弟节点变化了,进行更新.之后套用 情况3.2
                    sib = leftOf(parentOf(x));
                }
				//到这里 sibling兄弟节点 必为黑色了
				//情况3.2 和 情况 3.3:被删除节点为黑色,且兄弟节点为黑色,且兄弟节点没有一个红色子节点
                if (colorOf(rightOf(sib)) == BLACK &&
                    colorOf(leftOf(sib)) == BLACK) {
                    setColor(sib, RED);
                    x = parentOf(x);
                } else {
                	//情况3.1 : 被删除节点node为黑,兄弟节点为黑,且兄弟节点至少有一个红色子节点,则需要判断旋转方向进行旋转即可
                    if (colorOf(leftOf(sib)) == BLACK) {
                        setColor(rightOf(sib), BLACK);
                        setColor(sib, RED);
                        rotateLeft(sib);
                        sib = leftOf(parentOf(x));
                    }
                    //旋转之后新的父节点要继承原先的父节点
                    setColor(sib, colorOf(parentOf(x)));
                    setColor(parentOf(x), BLACK);
                    setColor(leftOf(sib), BLACK);
                    rotateRight(parentOf(x));
                    //赋值root,结束循环,因为root必为黑
                    x = root;
                }
            }
        }
        setColor(x, BLACK);
    }
  • TreeMap 最核心的就是添加元素和删除元素的方法,当然还有很多其他方法但也都是操作红黑树的技巧,遍不在过多介绍了。在说一个TreeMap 获取得带的方法。

  • values():这个方法可以取到 红黑树的所有键值对里的 values 值,测试如下:

    在这里插入图片描述

  • values()方法源码如下:

    //这是 TreeMap 继承自父类 AbstractMap 的字段
    transient Collection<V> values;
    
    public Collection<V> values() {
    		//TreeMap 第一次调用该方法之前,values 默认是空的
            Collection<V> vs = values;
            if (vs == null) {
            	//所以第一次会执行该代码
                vs = new Values();
                values = vs;
            }
            return vs;
        }
        class Values extends AbstractCollection<V> {
            public Iterator<V> iterator() {
                return new ValueIterator(getFirstEntry());
            }
    
            public int size() {
                return TreeMap.this.size();
            }
    
            public boolean contains(Object o) {
                return TreeMap.this.containsValue(o);
            }
    
            public boolean remove(Object o) {
                for (Entry<K,V> e = getFirstEntry(); e != null; e = successor(e)) {
                    if (valEquals(e.getValue(), o)) {
                        deleteEntry(e);
                        return true;
                    }
                }
                return false;
            }
    
            public void clear() {
                TreeMap.this.clear();
            }
    
            public Spliterator<V> spliterator() {
                return new ValueSpliterator<K,V>(TreeMap.this, null, null, 0, -1, 0);
            }
        }
    
  • 可能会有如下疑问:

    1. 为何 TreeMap 仅仅是调用了 values方法,就可以拿到所有的键值对的value 值?
    2. 看源码也仅仅是调用了 Values 无参构造函数进行了实例化,并没有调用迭代器方法,没有经理迭代的过程,那么如何将键值对的 value 全部拿给 集合 Value 并返回的呢?
  • 我们可以观察下测试代码的字节码:

    public class Test {
        public Test() {
        }
    
        public static void main(String[] args) {
            TreeMap<Integer, Integer> treeMap = new TreeMap();
    
            for(int i = 0; i < 5; ++i) {
                treeMap.put(i, i);
            }
    
            Collection<Integer> values = treeMap.values();
            Iterator var3 = values.iterator();
    
            while(var3.hasNext()) {
                Integer value = (Integer)var3.next();
                System.out.println(value);
            }
    
            System.out.println("");
        }
    }
    
    
  • 真相大白了,其实你去获取集合 values 的值的时候,首先会获取 Value 里的迭代器,然后调用该迭代器获取值,自始至终并没有将键值对的value取出来的给Value集合的过程,所以这种想法是错误的,我们在看下Value获取迭代的代码:就是获取到我们前面说的 Value的迭代器:ValueIterator,所以获取到的 value 其实是直接从键值对里面遍历拿到的。Value集合可以想象是外面套的一层壳,内部还是操作键值对。这个在很多其他源码中都有体现,比如HashMap中。

    在这里插入图片描述

五、 总结

  • TreeMap是一个有序的容器,底层是使用了红黑树存储来存储数据,来保证它的有序性,通过红黑树节点的k来进行排序。
  • 默认是不支持存储key 为null的,因为 在使用 compare 比较之前会强行转换为 Comparable,所以key 为null 会报错,同时也说明,如果没有传入比较器,那么传入的key 的数据类型得是 Comparable 的实现类。
  • 为什么采用红黑树,而不是采用像AVL这种同样是二叉平衡二叉搜索树呢?
    • 首先确实单单从搜索速率上来说AVL更快,因为AVL数是一种强平衡树,即左右字数高度差不为超过1,更加平衡,所以层级也就越低,搜索时可以更少的判断是去左子树查还是右子树差。而红黑树是弱平衡的,高度差会超过1,是通过五条性质来维护平衡,所以层数更高,所以搜索时,左右字数判断次数可能会更多。
    • TreeMap 不仅仅用来搜索,我们还会对其进行大量的添加与删除操作,而添加与删除的性能,红黑树大于AVL树,因为AVL恢复平衡需要旋转,而旋转后可能导致父类不平衡,继续向上调整直到平衡,包括删除也是同理,而红黑树则只需要通过一次或两次的旋转操作即可达到平衡,即便是出现上溢的情况,也只需通过染色即可恢复平衡,所需速度更快。
    • 所以基于还会有大量添加与删除操作的场景,红黑树的性能由于AVAL树,所以实际应用中更多的选择红黑树。
  • 同时提供了三种迭代器,分别是 节点 entry 的迭代器,key 的迭代器和value 的迭代器,可方便获取想要的信息,同时迭代器的设计包括 TreeMap 的设计都用到了 模版模式,比如 TreeMap 中定义了 抽象的 迭代器基类,并实现了通用的方法,在其他的迭代器中继承他即可使用这些方法,大大提高了代码利用率。
  • 23
    点赞
  • 22
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值