TreeSet & TreeMap详解

Mr.LR2022年3月2日
大约 4 分钟

Map - TreeSet & TreeMap详解

基本介绍

  1. TreeMap是基于红黑树的实现,也是记录了key-value的映射关系,该映射根据key的自然排序进行排序或者根据构造方法中传入的比较器进行排序,也就是说TreeMap是有序的key-value集合。
  2. TreeMap底层通过红黑树(Red-Black tree)实现,也就意味着containsKey(), get(), put(), remove()都有着log(n)的时间复杂度。
  3. TreeMap的key不可以添加null。
  4. TreeMap是线程不安全的。

TreeMap底层实现

底层数据结构

底层基于红黑树的实现,按照key的自然顺序排序,或者根据构造方法传入的比较器排序。

关于红黑树参考

构造函数

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

get方法

public V get(Object key) {
    TreeMap.Entry<K,V> p = getEntry(key);
    return (p==null ? null : p.value);
}
final TreeMap.Entry<K,V> getEntry(Object key) {
    // 如果有比较器,则走比较器的方法
    if (comparator != null)
        return getEntryUsingComparator(key);
    if (key == null)//这里也可以看出 key 不可以为空
        throw new NullPointerException();
    @SuppressWarnings("unchecked")
    Comparable<? super K> k = (Comparable<? super K>) key;
    TreeMap.Entry<K,V> p = root;
    //根据红黑树的性质,从根节点开始找对应的key
    //依次在子节点中找直到找到key 
    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;
}

put方法

  1. 判断是否有比较器
  2. 如果找到对应key,则不添加,只覆盖value
  3. 添加结束后,维护红黑树的平衡性
public V put(K key, V value) {
    Entry<K,V> t = root;
    if (t == null) {//根节点为空,则是第一个元素
        compare(key, key); // 校验 确保key是可排序的类,以及key不能为null
		//第三个参数为父节点的entry,根节点没有父节点,所以为null
        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 {
            //do...while是为了找到该key所要存放的位置(找到父节点)
            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;
}

remove方法

  • 找到key对应的节点
  • 进行删除
    • 删除的节点,没有左右子树,则直接走删除逻辑,然后维护红黑树平衡性。
    • 删除的节点,有一个孩子(左子树或者右子树),则让孩子顶替自己的位置,然后维护红黑树平衡性。
    • 删除的节点,有两个孩子,让删除节点的后继,顶替自己的位置,然后维护红黑树平衡性。

public V remove(Object key) {
    //获取到该key对应的节点 和get相同
    TreeMap.Entry<K,V> p = getEntry(key);
    if (p == null)
        return null;

    V oldValue = p.value;
    deleteEntry(p);
    return oldValue;
}
private void deleteEntry(TreeMap.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) {
        //找到与p数值的后继,即删除节点的右子树中的最小节点
        TreeMap.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.
    // 找到所要替代的节点
    TreeMap.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;

        // 删除的节点为黑色节点,需要进行平衡
        if (p.color == BLACK)
            fixAfterDeletion(replacement);
    //此时replacement为null(表明 p没有左右子树),并且p没有父节点,表明该树只有一个根节点	    
    } else if (p.parent == null) { // return if we are the only node.
        root = null;
    //此时replacement为null(表明 p没有左子树也没有右子树),表明该节点为叶子节点    
    } else { //  No children. Use self as phantom replacement and unlink.
        // 删除的节点为黑色节点,需要进行平衡
        if (p.color == BLACK)
            fixAfterDeletion(p);
		// 将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;
        }
    }
}

TreeSet

TreeSet底层就是TreeMap

// TreeSet是对TreeMap的简单包装
public class TreeSet<E> extends AbstractSet<E>
    implements NavigableSet<E>, Cloneable, java.io.Serializable
{
	......
    private transient NavigableMap<E,Object> m;
    // Dummy value to associate with an Object in the backing Map
    private static final Object PRESENT = new Object();
    public TreeSet() {
        this.m = new TreeMap<E,Object>();// TreeSet里面有一个TreeMap
    }
    ......
    public boolean add(E e) {
        return m.put(e, PRESENT)==null;
    }
    ......
}
上次编辑于: 2022/9/26 18:02:00
贡献者: liurui-60837