Skip to content

Latest commit

 

History

History
 
 

Folders and files

NameName
Last commit message
Last commit date

parent directory

..
 
 
 
 

README.md

前言

不论是 HashMap 还是 ConcurrentHashMap、或者是 TreeMap,在面试和工作中都会用到。尤其是面试,这三样基本都会被问

这篇文章就介绍下TreeMap的源码

类继承图

Cloneable 和 Serializable 接口仅仅只是一个标识,可以被克隆和可被序列化

我们主要看右边的那几个类和接口

Map

Map 是 所有 使用 键值对类型存储的结构的顶级接口,不论是 HashMapConcurrentHashMapTreeMapHashTable等等 都要实现这个接口

它里面定义了N多种 Map这种数据结构需要用到的方法,还有他最最重要的结构 Entry/Node

他的这些基本方法应该不需要我来介绍,看名字应该就能知道他的用途

SortedMap

这个接口是就是区分和 HashMap等这些没有顺序的Map 很重要的接口

它里面有一个 比较器Comparator,这个接口就是用来放置比较规则,让我们放进去的 entry 按照什么规则排列

package java.util;

public interface SortedMap<K,V> extends Map<K,V> {
    
    Comparator<? super K> comparator();

    // 截取Map,输入他的起止 key。因为他是有序的
    SortedMap<K,V> subMap(K fromKey, K toKey);

  	// 截取Map,从头到你输入的Key
    SortedMap<K,V> headMap(K toKey);

   
  	// 截取Map,从你输入的Key到 最后一个元素
    SortedMap<K,V> tailMap(K fromKey);

    // 第一个Key
    K firstKey();

   	// 最后一个Key
    K lastKey();

    // 获取所有Key,并返回 一个Set
    Set<K> keySet();

    // 返回所有Value
    Collection<V> values();

    // 返回键值对
    Set<Map.Entry<K, V>> entrySet();
}

NavigableMap

从他的名字来看,就是 可导航/ 可定位的Map。这个是对上面的 SortedMap的一个补充,他增加了一些方法

public interface NavigableMap<K,V> extends SortedMap<K,V> {
    // 返回比这个 键 更小的 键值对。一定比这个键小
    Map.Entry<K,V> lowerEntry(K key);

   	// 返回比这个 键 更小的 键值对,一定比这个键小
    K lowerKey(K key);
  
  	// 返回 小于或等于 输入键 中的最大的键值对
    Map.Entry<K,V> floorEntry(K key);
  
    K floorKey(K key);

  
  	// 返回 大于或等于 输入键 中的最小的键值对
    Map.Entry<K,V> ceilingEntry(K key);

    // 返回 大于或等于 输入键 中的最小的键
    K ceilingKey(K key);

    // 返回比这个 键 更大的 键值对。一定输入的这个键大
    Map.Entry<K,V> higherEntry(K key);

    K higherKey(K key);

    // 返回第一个键值对
    Map.Entry<K,V> firstEntry();

    // 返回最后一个键值对
    Map.Entry<K,V> lastEntry();

    // 移除并且返回第一个键值对
    Map.Entry<K,V> pollFirstEntry();
  
    Map.Entry<K,V> pollLastEntry();

   	// 返回逆序的键值对
    NavigableMap<K,V> descendingMap();
  	
  	// 返回正序的键值对
    NavigableSet<K> navigableKeySet();

    NavigableSet<K> descendingKeySet();

   	// 返回截取的Map 。开头结尾,各有一个bool值控制是否包含该键值对
    NavigableMap<K,V> subMap(K fromKey, boolean fromInclusive,
                             K toKey,   boolean toInclusive);

    // 返回截取的Map 。从开头到你输入的key,有一个bool值控制是否包含该键值对
    NavigableMap<K,V> headMap(K toKey, boolean inclusive);

    
    NavigableMap<K,V> tailMap(K fromKey, boolean inclusive);

   	// 跟之前类似,不过默认 左闭右开
    SortedMap<K,V> subMap(K fromKey, K toKey);

    // 跟上面类似,不过默认不包括输入的key
    SortedMap<K,V> headMap(K toKey);

    // 跟之前的类似,默认包括输入的key
    SortedMap<K,V> tailMap(K fromKey);
}

构造函数

public TreeMap() {
    comparator = null;
}


public TreeMap(Comparator<? super K> comparator) {
    this.comparator = comparator;
}


public TreeMap(Map<? extends K, ? extends V> m) {
    comparator = null;
    putAll(m);
}


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) {
    }
}

他一共有 4 个构造方法,不同于我们常见的HashMap,他这个是没有大小一说的,因为他底层不是使用的数组,所以我们也不能一开始给他指定一个大小

如果 我们不指定他的比较器,那么他使用的就是默认的比较器,比如你是自定义的类,没有实现Comparable接口,那么就会报错

如果你传入的是基本类型或者String,那么他就会用他们自己的比较规则去比较

put

/**
	* 插入逻辑如果不看红黑树调整其实非常好理解,就是一个二叉搜索树的插入;逻辑
	*/
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;
  	// 这边是如果有比较器的情况,但是默认是空的,就走下面的else语句
    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;
}

红黑树

/**
 *	红黑树最最关键的函数。 左旋右旋都在其中
 *  为了方便,我们先声明一些定义  我们把当前操作的节点称为 关注节点。比如 下面一开始 我们的关注节点就是 x
 */
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)))) {
          	// 取到他的叔叔节点 y
            Entry<K,V> y = rightOf(parentOf(parentOf(x)));
          	// case1: 如果他的叔叔节点是红色
            if (colorOf(y) == RED) {
              	// 1、设置父亲和叔叔节点为黑色
                setColor(parentOf(x), BLACK);
                setColor(y, BLACK);
              	// 2、设置祖父节点为红色
                setColor(parentOf(parentOf(x)), RED);
              	// 3、把关注节点变成祖父节点
                x = parentOf(parentOf(x));
            } else {
              	// case2: 如果他的叔叔节点是黑色,并且关注节点 x 是其父节点的右子节点
                if (x == rightOf(parentOf(x))) {
                  	// 1. 把关注节点变成他的父节点
                    x = parentOf(x);
                  	// 2.进行左旋
                    rotateLeft(x);
                }
              	// 1.把父节点设置成黑色
                setColor(parentOf(x), BLACK);
              	// 2。祖父节点设置成红色
                setColor(parentOf(parentOf(x)), RED);
                // 3.右旋
              	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;
}

左旋代码(右旋和左旋类似,逻辑差不多)

private void rotateLeft(Entry<K,V> p) {
    if (p != null) {
      	// 把p之前祖父节点的右子树设置成本节点的左子树
        Entry<K,V> r = p.right;
        p.right = r.left;
        if (r.left != null)
            r.left.parent = p;
        r.parent = p.parent;
      	// 把p的祖父节点换成父节点
        if (p.parent == null)
            root = r;
        else if (p.parent.left == p)
            p.parent.left = r;
        else
            p.parent.right = r;
        r.left = p;
        p.parent = r;
    }
}

大家可以看下极客时间的这篇红黑树的文章,但是有些地方由于版本问题可能不太一样

delete

private void deleteEntry(Entry<K,V> p) {
    modCount++;
    size--;

    // If strictly internal, copy successor's element to p and then make p
    // point to successor.
    if (p.left != null && p.right != null) {
        Entry<K,V> s = successor(p);
        p.key = s.key;
        p.value = s.value;
        p = s;
    } // p has 2 children

    // Start fixup at replacement node, if it exists.
  	// 修正一下替换的那个节点。 注意现在这个 p  是之前的 s。并且这 p 要么只有右子节点,要么没有子节点。如果他有左子节点,那之前找的继承者就不对
    Entry<K,V> replacement = (p.left != null ? p.left : p.right);

  	// 如果他有右子节点,
    if (replacement != null) {
        // Link replacement to parent
      	// 将这个的parent 指向原来 s 的 parent。 因为 s 已经没有用了,要被清除掉
        replacement.parent = p.parent;
        if (p.parent == null)
            root = replacement;
        else if (p == p.parent.left)
            p.parent.left  = replacement;
        else
            p.parent.right = replacement;

        // Null out links so they are OK to use by fixAfterDeletion.
      	// 清除 一开始的 s节点
        p.left = p.right = p.parent = null;

        // Fix replacement
        if (p.color == BLACK)
          	// 删除后的平衡调整
            fixAfterDeletion(replacement);
    } else if (p.parent == null) { // return if we are the only node.
        root = null;
    } else { //  No children. Use self as phantom replacement and unlink.
      	// 说明之前的继任者没有左右子节点,就不需要链接了,直接调整
        if (p.color == BLACK)
            fixAfterDeletion(p);
				// 删除 原来的 s 节点
        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;
        }
    }
}
/**
 * Returns the successor of the specified Entry, or null if no such.
 * 找到他的继任者,他的继任者应该是比他大一点的那个节点(如果有的话)
 */
static <K,V> TreeMap.Entry<K,V> successor(Entry<K,V> t) {
    if (t == null)
        return null;
  	// 如果他有右节点,就一直往左找,根据二叉排序树的规则
    else if (t.right != null) {
        Entry<K,V> p = t.right;
        while (p.left != null)
            p = p.left;
        return p;
    } else {
      	// 不然就往上找,这里有两种情况,一种是他在父节点的左子树,一种是他在父节点的右子树
        Entry<K,V> p = t.parent;
        Entry<K,V> ch = t;
      	// 如果在左子树,那么比他大一个的就是他的父节点。(因为上面已经说这个节点没有右子树了)
      	// 如果是右子树,那么就要一直往上溯源
        while (p != null && ch == p.right) {
            ch = p;
            p = p.parent;
        }
        return p;
    }
}
/**
 *	删除后进行平衡的代码
 */
private void fixAfterDeletion(Entry<K,V> x) {
    while (x != root && colorOf(x) == BLACK) {
      	// 如果在左子树
        if (x == leftOf(parentOf(x))) {
            // 找他的兄弟节点
            Entry<K,V> sib = rightOf(parentOf(x));
						// 如果是红色
            if (colorOf(sib) == RED) {
                setColor(sib, BLACK);
                setColor(parentOf(x), RED);
                rotateLeft(parentOf(x));
                // 再次找到他的兄弟节点
                sib = rightOf(parentOf(x));
            }
						// 如果 他的兄弟节点的左右子树都为黑色
            if (colorOf(leftOf(sib))  == BLACK &&
                colorOf(rightOf(sib)) == BLACK) {
                setColor(sib, RED);
              	// 关注点变成 他的父节点
                x = parentOf(x);
            } else {
              	// 如果他的兄弟节点的右子节点为黑色
                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));
                x = root;
            }
        } else { // symmetric
          	// 与上面的类似,只不过镜像
            Entry<K,V> sib = leftOf(parentOf(x));

            if (colorOf(sib) == RED) {
                setColor(sib, BLACK);
                setColor(parentOf(x), RED);
                rotateRight(parentOf(x));
                sib = leftOf(parentOf(x));
            }

            if (colorOf(rightOf(sib)) == BLACK &&
                colorOf(leftOf(sib)) == BLACK) {
                setColor(sib, RED);
                x = parentOf(x);
            } else {
                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));
                x = root;
            }
        }
    }

    setColor(x, BLACK);
}

逻辑

插入

  1. 如果关注节点是根节点或者父节点是黑色,就不用调整,因为不会影响红黑树结构
  2. 否则
    • 如果他是祖父节点的左子树
      • 如果他的叔叔节点是红色,
        1. 设置他的父亲节点和叔叔节点为黑色
        2. 设置他的祖父节点为红色
        3. 把关注节点换成他的祖父节点,然后重复 1
      • 如果他的叔叔节点是黑色
        • 如果他是父节点的左子树
        • 如果他是父节点的右子树
          1. 关注节点变成他的父节点
          2. 进行围绕关注节点左旋
        • 把关注节点的父节点变成黑色
        • 把关注节点的祖父节点变成红色
        • 围绕祖父节点进行右旋
    • 如果他是祖父节点的右子树
      • 对上述做镜像操作

删除

  1. 首先找到他的继承节点。(比他大的最小的那个节点)
  2. 然后对其进行处理,删除掉替换的节点
  3. 删除后进行树平衡(fixAfterDeletion)
    • 只要当前关注的节点不是根节点(因为在调用这个函数之前已经设置过他是黑色)
    • 如果他是父亲节点的左子树(注意和插入区分)
      • 如果他的兄弟节点是红色
        • 把它的兄弟节点设置成黑色,把它父亲节点设置成红色
        • 以他父亲节点做左旋
        • 关注点变成他的兄弟节点
      • 如果他的兄弟节点的左右子节点都是黑色
        • 把它兄弟节点设置成红色
        • 关注节点变成他的父亲节点
    • 否则
      • 如果他的兄弟节点的右子节点为黑色,他的兄弟节点的左子节点为红色
        1. 设置他的兄弟节点为红色
        2. 以他的兄弟节点右旋
        3. 把他的兄弟节点指向他的父亲节点的右子节点
      • 把他的兄弟节点设置成他父亲节点的颜色
      • 把他的父亲节点设置成黑色
      • 把它的兄弟节点的右子节点设置成黑色
      • 以他的父亲节点左旋
      • 把当前的关注节点变成root