集合框架--TreeMap 和 TreeSet

93 阅读11分钟

1. 简介

TreeMap 是一个通过红黑树实现有序的 key-value 的集合,其按照自然排序或是指定的方式进行排序,取决于我们所使用的构造方法,所以说,TreeMap 是有序的,我们可以指定其排序方式。

TreeMap 是线程不安全的。

public class TreeMap<K,V>
    extends AbstractMap<K,V>
    implements NavigableMap<K,V>, Cloneable, java.io.Serializable
{}
  • 继承 AbstractMap 类,表明 TreeMap 为一个 Map,即支持 key-value 的集合
  • 实现了 NavigableMap 接口,支持一系列的导航方法,具备针对给定搜索目标返回最接近匹配项的导航方法
  • 实现了 Cloneable,支持克隆

TreeMap 是 Map 接口的具体实现,它的应用场景与 Map 基本上相同。Map 用于保存具有映射关系的数据,因此 Map 集合里保存这两组值,一组用于保存 Map 的key,另一组保存 Map 的 value。key 和 value 都可以是任何引用类型的数据。Map 的 key 不允许重复,即同一个 Map 对象的任何两个 key 通过 equals 方法比较结果总是返回 false。Java 是先实现了 Map,然后通过包装了一个所有 value 都为null 的 Map 就实现了 Set 集合,Map 的这些实现类和子接口中 key 集的存储形式和 Set 集合完全相同(key不能重复),Map 的这些实现类和子接口中的 value 集的存储形式和 List 非常相似(value 可以重复、根据索引来查找)。TreeMap 通常比 HashMap、HashTable 要慢,尤其是再插入、删除 key-value 的时候更慢,因为 TreeMap 底层是使用的红黑树来进行管理键值对的。但是好处是 TreeMap 中的 key-value 对总是处于有序状态的,不需要专门进行排序操作。虽然 TreeMap 在插入和删除方面性能比较差,但是在分类处理的时候作用很大,遍历的速度很快。

2. 成员变量

// 比较器,用来给 TreeMap 排序
private final Comparator<? super K> comparator;
// Entry 类型,红黑树的根节点
private transient Entry<K,V> root;
// 红黑树的节点的总数
private transient int size = 0;
// 红黑树的修改次数
private transient int modCount = 0;
​
// 红黑树的颜色 - 红色
private static final boolean RED   = false;
// 红黑树的颜色 - 黑色
private static final boolean BLACK = true;

Entry 类是 TreeMap 的内部类,作为树的结点

/**
 * Node in the Tree.  Doubles as a means to pass key-value pairs back to
 * user (see Map.Entry).
 */
static final class Entry<K,V> implements Map.Entry<K,V> {
    K key; // 键
    V value; // 值
    Entry<K,V> left;  // 左孩子结点
    Entry<K,V> right;  // 右孩子结点
    Entry<K,V> parent;  // 父结点
    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;
        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;
    }
}

3. 构造器

/**
 * 无参构造器
 * Constructs a new, empty tree map, using the natural ordering of its
 * keys.  
 * 构造一个新的空的map,使用自然顺序来对 key 进行排序。
 */
public TreeMap() {
    comparator = null;
}
​
/**
 * Constructs a new, empty tree map, ordered according to the given
 * comparator. 
 * 使用给定的比较器来构建一个新的空的 map
 */
public TreeMap(Comparator<? super K> comparator) {
    this.comparator = comparator;
}
​
/**
 * Constructs a new tree map containing the same mappings as the given
 * map, ordered according to the <em>natural ordering</em> of its keys.
 * 使用已有的 map 集合来构建 TreeMap
 */
public TreeMap(Map<? extends K, ? extends V> m) {
    comparator = null;
    // 将 m 中的节点全部添加到 TreeMap 中
    putAll(m);
}
​
/**
 * Constructs a new tree map containing the same mappings and
 * using the same ordering as the specified sorted map.  This
 * method runs in linear time.
 *  构造包含相同映射并使用与指定排序映射相同的排序的新树映射。此方法在线性时间内运行
 * @param  m the sorted map whose mappings are to be placed in this map,
 *         and whose comparator is to be used to sort this map
 * @throws NullPointerException if the specified map is null
 */
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) {
    }
}
  • putAll 方法
// 将 map 中的节点全部添加到 TreeMap 中
public void putAll(Map<? extends K, ? extends V> map) {
    int mapSize = map.size();
    if (size==0 && mapSize!=0 && map instanceof SortedMap) {
        Comparator<?> c = ((SortedMap<?,?>)map).comparator();
        if (c == comparator || (c != null && c.equals(comparator))) {
            ++modCount;
            try {
                buildFromSorted(mapSize, map.entrySet().iterator(),
                                null, null);
            } catch (java.io.IOException cannotHappen) {
            } catch (ClassNotFoundException cannotHappen) {
            }
            return;
        }
    }
    super.putAll(map);
}
  • buildFromSorted 方法
// 通过递归方法将 SortedMap 中的元素逐个关联
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);
}

4. put 方法

/**
 * Associates the specified value with the specified key in this map.
 * If the map previously contained a mapping for the key, the old
 * value is replaced.
 *  将指定值与此映射中的指定键相关联。如果映射先前包含该键的映射,则将替换旧值。
 * @param key key with which the specified value is to be associated
 * @param value value to be associated with the specified key
 *
 * @return the previous value associated with {@code key}, or
 *         {@code null} if there was no mapping for {@code key}.
 *         (A {@code null} return can also indicate that the map
 *         previously associated {@code null} with {@code key}.)
 * @throws ClassCastException if the specified key cannot be compared
 *         with the keys currently in the map
 * @throws NullPointerException if the specified key is null
 *         and this map uses natural ordering, or its comparator
 *         does not permit null keys
 */
public V put(K key, V value) {
    // t 表示二叉树的根节点
    Entry<K,V> t = root;
    // 如果 t == null 即树为空,则根据 key value 创建一个 Entry 节点赋值给 root,大小设置为 1,返回 null
    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;
    // 如果给定的排序算法 cpr 不为空,则使用 cpr 的排序算法创建 TreeMap 集合
    if (cpr != null) {
        do {
            parent = t; // 第一次循环指向根节点,之后指向上一次循环后的 t
            cmp = cpr.compare(key, t.key);
            // 如果 cmp < 0 ,表示新增节点的 key 小于当前 key 的值,则以当前节点的左孩子作为新的当前节点
            // 否则,以当前节点的右孩子作为新的当前节点
            if (cmp < 0)
                t = t.left;
            else if (cmp > 0)
                t = t.right;
            else
                return t.setValue(value);
        } while (t != null);
    }
    // 如果 cpr 为空,则采用默认的排序算法进行创建
    else {
        // 判断 key 的合法性
        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);
    }
    // 根据要添加的 key value 以及上面找到的 parent 来构建新的 Entry 节点
    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;
}
/**
 * 红黑树平衡调整处理
 * 1. 新增一个红色节点,这个红色节点会添加在黑色节点下:不需要调整
 * 2. 新增一个红色节点,会有 6 中情况
 * 两种(左中右)的不需要调整
 * 根左左  根右右  旋转一次
 * 根左右  根右左  旋转两次
 * 3. 新增节点是红色 + 爷爷节点是黑色,父节点和叔叔节点为红色
 * 调整为:爷爷节点变为红色,父亲和叔叔节点变为黑色
 * 如果爷爷节点是 root 节点,则调整为 黑色
 */
private void fixAfterInsertion(Entry<K,V> x) {
    // 设置插入节点的颜色为红色节点
    x.color = RED;
    while (x != null && x != root && x.parent.color == RED) { // 不是根节点且父节点为红色
        // 1. x 的父节点是 x 的爷爷节点的左子节点,4种情况需要处理
        if (parentOf(x) == leftOf(parentOf(parentOf(x)))) {
            // 满足条件的 4种情况,根据是否又叔叔节点来一分为二进行处理
            // 当前节点的叔叔节点
            Entry<K,V> y = rightOf(parentOf(parentOf(x)));
            if (colorOf(y) == RED) {
                // 叔叔节点存在,满足上面条件 3 的情况
                setColor(parentOf(x), BLACK); // 父节点设置为黑色
                setColor(y, BLACK); // 叔叔节点设置为黑色
                setColor(parentOf(parentOf(x)), RED); // 爷爷节点设置为红色
                // 爷爷节点变为红色,如果不是根节点需要进行递归处理
                x = parentOf(parentOf(x));
            } else { 
                // 情况2,判断进行一次旋转还是进行两次旋转
                if (x == rightOf(parentOf(x))) {
                    // 插入节点是父节点的右子节点,先根据父节点做一次左旋
                    x = parentOf(x);
                    rotateLeft(x);
                }
                setColor(parentOf(x), BLACK);
                setColor(parentOf(parentOf(x)), RED);
                // 根据爷爷节点右旋转
                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 static <K,V> Entry<K,V> leftOf(Entry<K,V> p) {
    return (p == null) ? null: p.left;
}
// 获得右孩子节点
private static <K,V> Entry<K,V> rightOf(Entry<K,V> p) {
    return (p == null) ? null: p.right;
}
// 获得节点的颜色
private static <K,V> boolean colorOf(Entry<K,V> p) {
    return (p == null ? BLACK : p.color);
}
// 着色
private static <K,V> void setColor(Entry<K,V> p, boolean c) {
    if (p != null)
        p.color = c;
}
// 获得节点的父节点
private static <K,V> Entry<K,V> parentOf(Entry<K,V> p) {
    return (p == null ? null: p.parent);
}
// 左旋
private void rotateLeft(Entry<K,V> p) {
    if (p != null) {
        // 定义一个新的节点 r
        Entry<K,V> r = p.right;
        // p 节点的右边子树指向 r 节点的左子树
        p.right = r.left;
        if (r.left != null)
            // 修改 r 节点的左子树的父节点的致指向到 p 节点
            r.left.parent = p;
        // 不管 p 有没有父节点,都让 r 的父节点指向 p 的父节点
        r.parent = p.parent;
        if (p.parent == null)
            // 如果 p 没有父节点,则表示为根节点,直接设置根节点指向 r 节点
            root = r;
        else if (p.parent.left == p)
            // 判断 p 是父节点的左子节点还是右子节点
            p.parent.left = r;
        else
            p.parent.right = r;
        // 最后设置 p 为 r 的左子树
        r.left = p;
        // p 节点的父节点指向 r 节点
        p.parent = r;
    }
}
// 右旋
private void rotateRight(Entry<K,V> p) {
    if (p != null) {
        Entry<K,V> l = p.left;
        p.left = l.right;
        if (l.right != null) l.right.parent = p;
        l.parent = p.parent;
        if (p.parent == null)
            root = l;
        else if (p.parent.right == p)
            p.parent.right = l;
        else p.parent.left = l;
        l.right = p;
        p.parent = l;
    }
}

5. remove 方法

public V remove(Object key) {
    // 根据 key 找到节点
    Entry<K,V> p = getEntry(key);
    if (p == null)
        return null;
    // 将要删除节点的值保存下来用于返回
    V oldValue = p.value;
    // 真正执行删除的方法
    deleteEntry(p);
    return oldValue;
}

// 根据 key 得到节点方法
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;
}
/**
 * Delete node p, and then rebalance the tree. 
 * 返回指定项的后继项,如果没有,则返回null。
 * 删除节点方法  3种情况
 * 	1. 删除节点为叶子节点,直接删除
 * 	2. 删除节点有一个子节点,用子节点来替代
 * 	3. 删除节点有两个子节点,获取对应的前驱或者后继节点来替代
 * 可以转换为 1 2 的情况
 */
 */
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.
     // 有左右孩子节点,转换为 1 2 情况进行处理
    if (p.left != null && p.right != null) {
        // 找到要删除节点的后继节点
        Entry<K,V> s = successor(p);
        // 将后继节点的 key 值赋值给 要删除的节点 p
        p.key = s.key;
        // 将后继节点的 value 值赋值给要删除的节点 p
        p.value = s.value;
        // 设置要删除的节点为 p 的后继节点
        p = s;
    } // p has 2 children
    // Start fixup at replacement node, if it exists.
     // 获取要替换的节点
    Entry<K,V> replacement = (p.left != null ? p.left : p.right);
    if (replacement != null) {
        // Link replacement to parent
        // 删除一个子节点的情况
        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.
        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);
        // 再删除
        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;
        }
    }
}
  • fixAfterDeletion 方法,用于删除节点元素之后进行平衡处理
/**
 * 删除后的调整操作
 * 2-3-4 树删除操作
 * 1. 自己能搞定,对应的叶子节点是 3 节点或者 4 节点
 * 2. 自己搞不定,需要兄弟节点借,但是兄弟节点不借,找父亲节点借,父亲下来,然后兄弟节点找一个人代替父亲当家
 * 3. 跟兄弟节点借,兄弟没有
 */
private void fixAfterDeletion(Entry<K,V> x) {
    // 情况 2 和 3
    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 {
                // 情况2,向兄弟借,兄弟有借
                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);
}

6. TreeSet

TreeSet 是对 TreeMap 的简单包装,对 TreeSet 的函数调用都会转换成合适的 TreeMap 方法

public class TreeSet<E> extends AbstractSet<E>
    implements NavigableSet<E>, Cloneable, java.io.Serializable
{
    
    public TreeSet() {
    	this(new TreeMap<E,Object>());
	}
    
    public boolean add(E e) {
    	return m.put(e, PRESENT)==null;
	}
    
}

参考文献:

juejin.cn/post/684490…