TreeMap

Posted arax

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了TreeMap相关的知识,希望对你有一定的参考价值。

TreeMap

下文中提到的比较大小,>0,<0 都是指key用比较器比较大小

简介

TreeMap是一个有序的Map,是基于红黑树实现的。排序根据Map创建时传入的Comaprable或Comparator实现排序,如果没有传,则默认按插入数据的顺序。

TreeMap 的get,containsKey,get,put,remove 操作使用的时间为log(n),因为每次查找都是从树的根节点开始一层一层向下查找,

TreeMapy也是非线程安全的,所以,如果想使用线程安全的sorted map 可以用如下代码获取

SortedMap m = Collections.synchronizedSortedMap(new TreeMap(...))

和大多数非线程安全的集合一样,如果在迭代器创建后修改了TreeMap的结构,会抛出ConcurrentModificationException异常。

内部有一个比较器Comparator comparator 维护TreeMap的内部数据顺序,如果比较器为空,那么TreeMap的存储顺序就按插入的顺序存储。

有一个根节点 Entry root 内部包含父节点,左子节点,右子节点 颜色是否是黑色。entry是TreeMap中的元素节点,所有这些节点按red-black tree 排列,形成了TreeMap。

size 记录当前map中节点的个数

modCount 记录Treemap的结构性修改。

部分代码讲解

  • getEntry

    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;
    }

    上面这段代码的意思,从根节点开始找,如果比较值<0 ,证明当前节点的key大了(按照comparable比较),
    则向树的左子节点查找,如果比较值>0,则向树的右子节点上查找。知道查找到为止。

其中函数中getEntryUsingComparator()的比较思路也是这样。

  • getCeilingEntry

    // 获取当前值对应的entry,如果没有找到,则返回在map中比key大一点的entry
    final Entry<K,V> getCeilingEntry(K key) {
        Entry<K,V> p = root;
        while (p != null) {
            int cmp = compare(key, p.key);
            // 如果p比查找的节点大
            if (cmp < 0) {
            // 如果当前节点的左子节点不为空,向这个节点的左边查找
                if (p.left != null)
                    p = p.left; 
                else
                // 如果当前节点的左子节点为空,那么比查找值大一点的就是p这个节点
                    return p;
            } else if (cmp > 0) { // 如果p比查找的节点小。向右查找
                if (p.right != null) { // 如果p的右子节点不为空,继续向有查找
                    p = p.right;
                } else {
                // 如果右节点为空,这就麻烦了,要从p网上找,直到父节点为空
                // 或者找到了父节点的右子节点不在p到父(父...)节点的路径上
                // 拿笔在纸上画一画就明白了
                    Entry<K,V> parent = p.parent;
                    Entry<K,V> ch = p;
                    while (parent != null && ch == parent.right) {
                        ch = parent;
                        parent = parent.parent;
                    }
                    return parent;
                }
            } else
                return p;
        }
        return null;
    }

和上面的逻辑类似,寻找给定key对应的entry,如果没找到,则返回比给定key小的最大的key对应的entry

  • getFloorEntry
 final Entry<K,V> getFloorEntry(K key) {
        Entry<K,V> p = root;
        while (p != null) {   // 如果根节点为空直接返回null
            int cmp = compare(key, p.key);
            if (cmp > 0) {  // 如果p.key比给定的key小,则向右找
                if (p.right != null)
                    p = p.right;
                else
                    return p;
            } else if (cmp < 0) {  // 如果p.key比给定的key大,则向左找
                if (p.left != null) {
                    p = p.left;
                } else {     
                    // 如果p的左子节点为空,那么就要一直往上找,一直找到一个一个节点,其左子节点,
                    // 一直找到一个节点,不在p到其父(父...)节点的连线上。
                    Entry<K,V> parent = p.parent;
                    Entry<K,V> ch = p;
                    while (parent != null && ch == parent.left) {
                        ch = parent;
                        parent = parent.parent;
                    }
                    return parent;
                }
            } else
                return p;

        }
        return null;
    }

下面到了最精彩的地方了,那就是put方法,这是TreeMap的核心,
本质上是向红黑树上插入一个节点。建议先看一下红黑树插入数据的操作,这样会很容易明白这段逻辑。
put的核心就是代码中最后的fixAfterInsertion(e)

  • 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;
        if (cpr != null) { // 如果比较器不为空,根据比较器比较key的大小,确定要插入entry的存放位置
            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); // 如果找到了之前存放过该key的节点,直接替换其中的值
            } while (t != null);
        }
        else {
            // 如果没有没有比较器,则key 不可以为空,且必须实现了Comparable接口
            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);
        }
        // 如果上面没有查找到,这时候树上的指针已经移动到parent节点,
        // 当前key与parent节点比较大小确定新增的节点放到左子节点还是右子节点
        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的主要内容,如果未能解决你的问题,请参考以下文章

Java集合源代码剖析TreeMap源代码剖析

Java集合源代码剖析TreeMap源代码剖析

TreeMap - 源代码学习笔记

TreeMap实现原理

Scala中的TreeMap键和迭代

Java集合框架 Map接口实现类--TreeMap的使用 & TreeMap和TreeSet的关系