TreeMap源码分析

TreeMap是NavbagableMap的实现,底层基于红黑树。这个Map按照Comparable将键值排序,或者按照在创建Map时提供的Compartor。
TreeMap的类继承关系图如下:
TreeMap类继承关系图
TreeMap与HashMap的一个重要区别是:TreeMap不支持键

Comparable与Comparator

Comparable接口用于自身与另外一个对象比较,其接口定义如下:

public interface Comparable<T> {
    public int compareTo(T o);
}

下面以Integer为例,该类实现了Comparable接口,其compare()方法如下:

public int compareTo(Integer anotherInteger) {
        return compare(this.value, anotherInteger.value);
    }

    //拿自己的Integer的值与另一个对象比较
    public static int compare(int x, int y) {
        return (x < y) ? -1 : ((x == y) ? 0 : 1);
    }

而Comparator接口表示比较两个对象,接口如下:

public interface Comparator<T> {
    int compare(T o1, T o2);

    ...//1.8增加的方法
}

Comparator一般用于作为Arrays或Collections排序时的一个参数,用于比较数组或集合中元素的顺序。
TreeMap是一个排序了的Map,所以依靠Comparator参数将元素排序。

构造方法

TreeMap中有一个compartor的字段如下:

  private final Comparator<? super K> comparator;

可以看到该字段为final,所以必须在构造方法中指定,如果为null,那么将使用键值的自然排序,否则使用该Compartor定下的规则。
所以TreeMap的每个构造方法中都对comparator字段进行了初始化,如下:

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

基本操作

TreeMap的底层是基于红黑树的实现,所以像get、put、remove、containsKey这些方法都会花费log(n)的时间复杂度。这儿不会着重于红黑树的具体实现以及转换,只要知道TreeMap的基本思路就可以了。

put操作

TreeMap的put方法如下所示:

public V put(K key, V value) {
        //得到红黑树根结点
        Entry<K,V> t = root;
        //如果Map为空
        if (t == null) {
            compare(key, key); // type (and possibly null) check

            //新建红黑树根结点
            root = new Entry<>(key, value, null);
            size = 1;
            modCount++;
            return null;
        }
        //如果Map不为空,找到插入新节点的父节点
        int cmp;
        Entry<K,V> parent;
        Comparator<? super K> cpr = comparator;
        //如果提供了Compartor
        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);
        }
        //没有提供Comparator
        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;
    }

从put方法可以看到,有几步流程:
1. 如果Map为空,那么直接将新插入的值作为根结点。此时,如果提供了Compartor,就得看Compartor是否支持null键值;如果没有提供Compartor,那么将会抛出NullPointerException。
2. 如果Map不为空,那么需要找到新插入的键值的父节点。在查找过程中,如果遇到了键值相等的,那么将会调用Entry.setValue()更新值。
3. 一旦找到了父节点,那么插入新节点,尺寸+1

另外,我们需要注意到:
1. 如果没有提供Comparator,那么将不支持Null的键;如果提供了Comparator,那么是否支持Null的键将取决于Comparator的具体实现;
2. 如果没有提供Comparator,那么将会将键强制转换成Comparable接口,所以没有提供Comparator,那么键必须得实现Comparable接口,否则将抛出ClassCastException。

get操作

TreeMap的get方法根据键得到值,由于红黑树是一颗二叉搜索树,所以查询键值的操作很快,其实现如下:

 //根据键得到值,如果不存在,那么返回null
 public V get(Object key) {
        Entry<K,V> p = getEntry(key);
        return (p==null ? null : p.value);
    }

//根据键得到Entry节点,如果不存在,那么返回null
final Entry<K,V> getEntry(Object key) {
        //如果提供了Comparator
        if (comparator != null)
            return getEntryUsingComparator(key);
        //如果没有提供Comparator
        //由于put不允许键为null,那么get也不允许键为null
        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;
    }

从上面可以看到,get()方法的流程:
1. 如果提供了Comparator,那么使用getEntryUsingComparator()方法
2. 如果没有提供Comparator,并且键为null,抛出NullPointerException
3. 如果没有提供Comparator且键不为null,将键强制转换为Comparable接口,如果键没有实现,那么抛出ClassCastExceotion
4. 如果没有提供Comparator且键不为null,且键实现了Comparable接口,那么从根结点开始遍历红黑树,一旦找到则返回节点,否则返回null

下面看一下getEntryUsingComparator()方法,该方法也是从根节点开始遍历,与使用Comparable接口的遍历类似:

final Entry<K,V> getEntryUsingComparator(Object key) {
        @SuppressWarnings("unchecked")
            K k = (K) key;
        Comparator<? super K> cpr = comparator;
        if (cpr != null) {
            Entry<K,V> p = root;
            while (p != null) {
                int cmp = cpr.compare(k, p.key);
                if (cmp < 0)
                    p = p.left;
                else if (cmp > 0)
                    p = p.right;
                else
                    return p;
            }
        }
        return null;
    }

remove()方法

remove()方法用于删除键对应的节点,如果不存在,则返回null,其内部实现包含了getEntry()查找节点的步骤,一旦找到了,再执行删除操作,实现如下:

 public V remove(Object key) {
        //查找节点
        Entry<K,V> p = getEntry(key);
        //如果Map中不包含节点,返回null
        if (p == null)
            return null;

        V oldValue = p.value;
        //删除节点
        deleteEntry(p);
        return oldValue;
    }

从代码可以看到,remove()方法主要有两步:
1. getEntry()得到待删除的节点
2. 如果Map中不包含节点,返回null;否则调用deleteEntry()删除节点

deleteEntry()方法中删除接节点后,为了维持红黑树的结构,还需要进行调整,这儿就不说明了。

总结

TreeMap中比较元素的规则可能来自于两方面,一方面是Comparator,另一方面是使用键值的Comparable接口中的方法。这两种方式有很大的区别:使用键的Comparable接口,那么就不允许null的键;而如果使用Comparator接口,那么是否支持null的键将由Comparator的实现决定。另外需要铭记的就是TreeMap的底层是使用的红黑树的结构,所以其get、put、remove方法中都涉及了树的操作,只要记住这一点,理解起各个方法时就容易多了。

相关推荐
©️2020 CSDN 皮肤主题: 技术工厂 设计师:CSDN官方博客 返回首页