TreeMap源码

TreeMap

基于红黑树(Red-Black tree)的 NavigableMap 实现。该映射根据其键的自然顺序进行排序,或者根据创建映射时提供的 Comparator 进行排序,具体取决于使用的构造方法。此实现为 containsKey、get、put 和 remove 操作提供受保证的 log(n) 时间开销。
这些算法是 Cormen、Leiserson 和 Rivest 的 Introduction to Algorithms 中的算法的改编。

关于TreeMap中红黑树:TreeMap红黑树分析

TreeMap的继承实现结构

public class TreeMap<K,V>
extends AbstractMap<K,V>
implements NavigableMap<K,V>, Cloneable, java.io.Serializable

TreeMap<K,V>:TreeMap是以key-value形式存储数据的。

extends AbstractMap<K,V>:继承了AbstractMap,实现Map接口时需要实现的工作量大大减少了。

implements NavigableMap:实现了NavigableMap,可以返回特定条件最近匹配的导航方法。方法 lowerEntry、floorEntry、ceilingEntry 和 higherEntry 分别返回与小于、小于等于、大于等于、
大于给定键的键关联的 Map.Entry 对象,如果不存在这样的键,则返回 null。

implements Cloneable:表明其可以调用clone()方法来返回实例的field-for-field拷贝。

implements Serializable:表明该类是可以序列化的。

主要字段:

/**
 * treeMap的排序规则,如果为null,则根据键的自然顺序进行排序
 */
private final Comparator<? super K> comparator;

/**
 * 红黑数的根节点
 */
private transient Entry<K,V> root;

/**
 * 红黑树节点的个数
 */
private transient int size = 0;

/**
 * treeMap的结构性修改次数。实现fast-fail机制的关键。
 */
private transient int modCount = 0;

/**
 * 视图只有在第一次请求时创建并只创建一个
 */
private transient EntrySet entrySet;
private transient KeySet<K> navigableKeySet;
private transient NavigableMap<K,V> descendingMap;

构造方法:

  • TreeMap():使用key的自然排序来构造一个空的treeMap。
  • TreeMap(Comparator<? super K> comparator):使用给定的比较器来构造一个空的treeMap。
  • TreeMap(Map<? extends K, ? extends V> m):使用key的自然排序来构造一个treeMap,treeMap包含给定map中所有的键值对。
    public TreeMap(Map<? extends K, ? extends V> m) { comparator = null; putAll(m); }
  • TreeMap(SortedMap<K, ? extends V> m):使用指定的sortedMap来构造treeMap。treeMap中含有sortedMap中所有的键值对,键值对顺序和sortedMap中相同。使用一个有序的map构造新的treemap。这个方法需要线性时间

主要方法:

get方法:

 /**
 * 通用二叉树的查找算法。提供log2(n)的复杂度
 * @param key
 * @return
 */
final Entry<K,V> getEntry(Object key) {
    // Offload comparator-based version for sake of performance
    // 直接使用指定的比较器查找
    if (comparator != null)
        return getEntryUsingComparator(key);// 与当前方法类似,只是使用compare(o1,o2)比较
    // 不允许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);
        // key小于当前节点取左节点比较
        if (cmp < 0)
            p = p.left;
        // key大于当前节点取右节点比较
        else if (cmp > 0)
            p = p.right;
        // 找到
        else
            return p;
    }
    // 没有找到
    return null;
}

 /**
 * 返回大于等于key的最小键。如果不存在就返回null。
 * 算法:
 * 1.如果key < 当前节点p,寻找左子树,若左子树不存在,p就是最小大于key的
 * 2.如果key > 当前节点p,寻找右子树,若右子树不存在,需要向上回溯第一个大于key的键
 * 即key < 节点p,使用左链接(一个大于等于key的键的左子树中不存在更小的大于等于key的键)
 * @param key
 * @return
 */
final Entry<K,V> getCeilingEntry(K key) {
    Entry<K,V> p = root;
    while (p != null) {
        // 静态方法,比较器comparator为空就是用默认的比较
        int cmp = compare(key, p.key);
        // key比当前节点小
        if (cmp < 0) {
            // 当前节点存在更小的子节点
            if (p.left != null)
                p = p.left;
            // 没有更小的子节点,当前节点就是树中最小大于key的节点
            else
                return p;
        } 
        // key比当前节点大
        else if (cmp > 0) {
            // 当前节点存在更大的子节点
            if (p.right != null) {
                p = p.right;
            }
            // 当前节点没有更大的节点能大于key,即子树中不存在大于key的键。只能向上回溯第一个使用左链接比较的节点
            // 即当一个节点p大于key时,仍然在左子树中寻找小于p大于key的键,那么第一个左链接节点必定是最小大于key的
            else {
                Entry<K,V> parent = p.parent;
                Entry<K,V> ch = p;
                // parent.left连接时退出循环或不存在大于等于key的键
                while (parent != null && ch == parent.right) {
                    ch = parent;
                    parent = parent.parent;
                }
                return parent;
            }
        } 
        // 找到key相等节点p
        else
            return p;
    }
    return null;
}

 /**
 * 取小于等于key的最大键。遇到第一个key.compareTo(p) > 0的就会到右子树中取最小值
 * 算法:
 * 1.如果key < 当前节点p,若p的左子树存在,就查找左子树,如果不存在,就回溯找第一个小于key的键
 * 即k > 节点p,使用右连接的父节点(原理是当一个节点小于key,就会查找右子树是否有大一点的节点小于key)
 * 2.如果key > 当前节点p,寻找右子树,若右子树不存在就返回p最小大于等于key的
 * @param key
 * @return
 */
final Entry<K,V> getFloorEntry(K key) {
    Entry<K,V> p = root;
    while (p != null) {
        int cmp = compare(key, p.key);
        // key比当前节点大
        if (cmp > 0) {
            if (p.right != null)
                p = p.right;
            // 找不到比key更大的键,就返回当前键是小于key的最大键
            else
                return p;
        } 
        // key比当前节点小
        else if (cmp < 0) {
            if (p.left != null) {
                p = p.left;
            } 
            // 找不到
            else {
                // 如果左子树中不存在跟接近key的节点就返回第一个右链接的父节点
                Entry<K,V> parent = p.parent;
                Entry<K,V> ch = p;
                // 当parent使用右链接时退出循环或不存在小于等于key的键
                while (parent != null && ch == parent.left) {
                    ch = parent;
                    parent = parent.parent;
                }
                return parent;
            }
        } 
        // 相等
        else
            return p;

    }
    return null;
}

put方法:

 /**
 * 在map中关联指定的键值对。不允许插入null。只对插入操作进行红黑树修复。提供log(n)复杂度
 */
public V put(K key, V value) {
    Entry<K,V> t = root;
    // 根节点为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;
    // 使用比较器查找到key进行替换操作
    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);
    }
    // 使用默认的顺序查找到key进行替换操作
    else {
        // 不允许为null
        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使用putAll提供线性时间。好于非空treemap的n*logn
 * @author Navy D
 */
public void putAll(Map<? extends K, ? extends V> map) {
    int mapSize = map.size();
    // treemap是空的  参数map是sortedMap
    if (size==0 && mapSize!=0 && map instanceof SortedMap) {
        Comparator<?> c = ((SortedMap<?,?>)map).comparator();
        // 比较器一样为null或同一个对象
        if (c == comparator || (c != null && c.equals(comparator))) {
            ++modCount;
            try {
                // 使用迭代器通过key构建排序数据,需要线性时间 这个方法暂时没用到就不看了,用到再更新吧
                buildFromSorted(mapSize, map.entrySet().iterator(),
                                null, null);
            } catch (java.io.IOException cannotHappen) {
            } catch (ClassNotFoundException cannotHappen) {
            }
            return;
        }
    }
    // abstractMap.putAll方法采用for调用put方法
    super.putAll(map);
}

delete方法:

 /**
 * 在map中删除指定的键和关联的值
 */
public V remove(Object key) {
    // 查找
    Entry<K,V> p = getEntry(key);
    if (p == null)
        return null;

    V oldValue = p.value;
    // 删除键值对
    deleteEntry(p);
    return oldValue;
}

 /**
 * 删除节点p且平衡树
 * 对于有两个子节点的删除节点p,使用右子树的最小节点替换,
 * 然后根据这个最小节点是叶子结点就先修复再删除,若有一个右子树就使用右子树中节点与父节点连接
 * 对于有一个子节点的删除节点p,使用删除节点的子节点与父节点连接
 * 对于没右子节点就修复后再删除
 * @param p
 */
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.
    // 待删除结点有两个孩子用中序后继覆盖删除节点
    if (p.left != null && p.right != null) {
        // 返回p的右子树中的最小节点
        Entry<K,V> s = successor(p);
        // 将后继的键值覆盖删除点p的键值  没有更换颜色
        p.key = s.key;
        p.value = s.value;
        // 将p引用为后继节点 注意不是覆盖到删除节点 删除点p已经被替换为后继
        p = s;
    } // p has 2 children

    // Start fixup at replacement node, if it exists.
    // 如果删除节点只有一个孩子就返回这个孩子。注意如果进入上一个if,返回的p如果是叶节点,即replacement=null
    // p如果不是叶节点,只会存在右子节点
    Entry<K,V> replacement = (p.left != null ? p.left : p.right);
    // 删除节点存在一个子节点(左或右),父节点链接删除节点的子节点
    if (replacement != null) {
        // Link replacement to parent
        // 连接p的父节点与替换节点  删除p
        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
        // 只有删除点是BLACK的时候,才会触发删除修复,因为删除RED节点不会破坏红黑树的任何约束,而删除BLACK节点会导致有不同个数的黑色节点
        if (p.color == BLACK)
            fixAfterDeletion(replacement);
    }
    // 只有root一个节点
    else if (p.parent == null) { // return if we are the only node.
        root = null;
    }
    // 没有孩子节点 p是叶子结点
    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;
        }
    }
}

iterator方法:初步看了一下源码,类有点多,等到需要时再更新吧


first_edited_20171016

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 219,869评论 6 508
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 93,716评论 3 396
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 166,223评论 0 357
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 59,047评论 1 295
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 68,089评论 6 395
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,839评论 1 308
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,516评论 3 420
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,410评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,920评论 1 319
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 38,052评论 3 340
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 40,179评论 1 352
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,868评论 5 346
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,522评论 3 331
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 32,070评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 33,186评论 1 272
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,487评论 3 375
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 45,162评论 2 356

推荐阅读更多精彩内容