TreeMap简要分析

前言

学java也快一年了,说实话TreeMap还真没怎么用过,也一直没去了解它。正因为这样,每次提到TreeMap都有点露怯,正好借着这次夯实基础,把TreeMap给认真摸一遍。

概述

public class TreeMap<K,V>
    extends AbstractMap<K,V>
    implements NavigableMap<K,V>, Cloneable, java.io.Serializable
复制代码
  • 可以看到跟hashmap一样继承自AbstractMap,说明TreeMap也是键值对结构
  • NavigableMap就是TreeMap的出彩之处,这个接口跟元素的排列大小顺序有关,具体怎么个有关法,我们后边介绍,这里知道就好啦。
private final Comparator<? super K> comparator;

private transient Entry<K,V> root;

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;
}
复制代码

这不是说曹操曹操到,刚说实现了一个跟顺序有关的接口,这里就出现了一个比较器。通过比较器实现TreeMap内部元素的排列顺序比较们。我们知道HashMap底层是个entry数组,这里则是树的根节点root,其实TreeMap底层就是红黑树,没错HashMap中当节点变多就会变成的那颗红黑树。我们可以看到每个树节点持有对左右子树以及父节点的引用。

接着来看一些常用的Api吧

初始化

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);
}
复制代码

可以看到初始化的过程中都有比较器的身影,如果比较器是null,则会按照键的自然排序规则来排序,否则按照我们的自定义比较器来进行比较排序。

查找

public V get(Object key) {
        Entry<K,V> p = getEntry(key);
        return (p==null ? null : p.value);
}

final Entry<K,V> getEntry(Object key) {
        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;
}    
复制代码
  • 如果我们定义了比较器则使用自定义比较器比较规则来查找
  • 可以看到key不能为空,否则会报NPE。
  • 根据红黑树规则去比较树节点直到找到key相等的节点,否则返回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;
    }
复制代码
  • 如果treeMap是空的,换言之root节点为空,compare(key,key)确保要添加的key不为null,然后由此key构造root节点
  • 此时root不为空,如果比较器存在,根据比较器去找要添加的key对应的树节点,找到的话直接赋值value
  • 如果比较器不存在,首先确保key不为空,使用默认比较规则比较找树节点,找到的话直接赋值value
  • 如果上边两步没找到,构造新节点根据插入,再调整红黑树

总结

其实说到这里,也差不多了,这玩意儿不怎么常用,了解一下,如果真用到可能还是再去乖乖看一遍为好,毕竟早晚会忘不用的话。。只要知道TreeMap的核心是比较器,各种方法的核心基本都是基于比较器实现的,红黑树我真的没去好好看过,所以这里就基本跳了(逃,等后边有时间好好研究一下。另外,TreeMap的查找时间复杂度是O(log n),相比之下还是不如hashmap来的快,不过胜在有顺序,如果按自然顺序或者比较器规则来遍历元素,TreeMap更有优势。

公众号:程序员二狗

每日原创文章,一起交流学习

猜你喜欢

转载自juejin.im/post/5db003c6e51d45252e697358