OpenJDK 源代码阅读之 TreeMap

概要

  • 类继承关系
java.lang.Object
    java.util.AbstractMap<K,V>
        java.util.HashMap<K,V>
  • 定义
public class TreeMap<K,V>
    extends AbstractMap<K,V>
    implements NavigableMap<K,V>, Cloneable, java.io.Serializable
  • 要点

1) 基于 NavigableMap 实现的红黑树

2) 按 natrual ordering 或者 Comparator 定义的次序排序

3) 基本操作containsKey,get,put 有 log(n) 的时间复杂度

4) 非线程安全

实现

  • Comparator
private final Comparator<? super K> comparator;

这说明提供的 Comparator 参数类型是 K 的基类就行。这似乎意味着基类的 Comparator 与导出类的要一致。

  • Entry
private transient Entry<K,V> root = null;

root 是这棵红黑树的根,那么从 Entry 的定义可以体现树的结构:

static final class Entry<K,V> implements Map.Entry<K,V> {
    K key;
    V value;
    Entry<K,V> left = null;
    Entry<K,V> right = null;
    Entry<K,V> parent;
    boolean color = BLACK;

    ...
}

注意这是个 static final 类,left,right,parent 分别指向左子树,右子树,父结点, color 颜色默认为黑。

  • containsKey
public boolean containsKey(Object key) {
    return getEntry(key) != null;
}
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();
    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;
}

关键操作 containsKey 是通过调用 getEntry 完成其功能的。可以看出,这是通过在红黑树上进行查找完成的,每次比较都会下降到树的下一层,由于红黑树的平衡性,时间复杂度为 log(n)

  • containsValue
public boolean containsValue(Object value) {
    for (Entry<K,V> e = getFirstEntry(); e != null; e = successor(e))
        if (valEquals(value, e.value))
            return true;
    return false;
}

可以看出,对 value 的查找,与对 key 是不同的。是通过 getFirstEntry 取得第一个结点,再通过 successor 遍历实现。

final Entry<K,V> getFirstEntry() {
    Entry<K,V> p = root;
    if (p != null)
        while (p.left != null)
            p = p.left;
    return p;
}

可以看出,这是找到了树中最左边的结点,如果左子树中的值小于右子树,这就意味是第一个比较的结点是最小的结点。

static <K,V> TreeMap.Entry<K,V> successor(Entry<K,V> t) {
    if (t == null)
        return null;
    else if (t.right != null) {
        Entry<K,V> p = t.right;
        while (p.left != null)
            p = p.left;
        return p;
    } else {
        Entry<K,V> p = t.parent;
        Entry<K,V> ch = t;
        while (p != null && ch == p.right) {
            ch = p;
            p = p.parent;
        }
        return p;
    }
}

successor 其实就是一个结点的 下一个结点,所谓 下一个,是按次序排序后的下一个结点。从代码中可以看出,如果右子树不为空,就返回右子树中最小结点。如果右子树为空,就要向上回溯了。在这种情况下,t 是以其为根的树的最后一个结点。如果它是其父结点的左孩子,那么父结点就是它的下一个结点,否则,t 就是以其父结点为根的树的最后一个结点,需要再次向上回溯。一直到 ch 是 p 的左孩子为止。

  • getCeilingEntry
/**
 * Gets the entry corresponding to the specified key; if no such entry
 * exists, returns the entry for the least key greater than the specified
 * key; if no such entry exists (i.e., the greatest key in the Tree is less
 * than the specified key), returns {@code null}.
 */
final Entry<K,V> getCeilingEntry(K key) {
    Entry<K,V> p = root;
    while (p != null) {
        int cmp = compare(key, p.key);
        if (cmp < 0) {
            if (p.left != null)
                p = p.left;
            else
                return p;
        } else if (cmp > 0) {
            if (p.right != null) {
                p = p.right;
            } else {
                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;
}

这个函数看起来有点奇怪,可以从 return 语句,猜想一下这是在做什么,我觉得是在找一个 x.key
>= key
 的元素,并且 x是满足条件的元素中最小的。从 23 行看,找到的 pkey 值与参数 key 相等,从 8 行看,又有 key
<= p.key
,并且p.left == null

put

/**
 * Associates the specified value with the specified key in this map.
 * If the map previously contained a mapping for the key, the old
 * value is replaced.
 *
 * @param key key with which the specified value is to be associated
 * @param value value to be associated with the specified key
 *
 * @return the previous value associated with {@code key}, or
 *         {@code null} if there was no mapping for {@code key}.
 *         (A {@code null} return can also indicate that the map
 *         previously associated {@code null} with {@code key}.)
 * @throws ClassCastException if the specified key cannot be compared
 *         with the keys currently in the map
 * @throws NullPointerException if the specified key is null
 *         and this map uses natural ordering, or its comparator
 *         does not permit null keys
 */
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();
        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 的过程,其实是将 (key,
value)
 加入到红黑树中的过程。如果树是空的,那么创建根结点。否则就要在树中插入结点。这个过程根据 comparator 是否存在设置成了两种方式,其实没什么区别,就是比较方式的不同,都是在树中查找一个合适的位置,如果 key 在树中,就 setValue 设置新值,否则,就在 60-64 行插入新结点。这里有个重要的地方是 65 行的fixAfterInsertion ,这个很重要,因为这是一棵红黑树,红黑树关键的思想是要保持它的平衡性,插入结点后,平衡性可能被破坏。所以需要 fix

/** From CLR */
private void fixAfterInsertion(Entry<K,V> x) {
    x.color = RED;

    while (x != null && x != root && x.parent.color == RED) {
        if (parentOf(x) == leftOf(parentOf(parentOf(x)))) {
            Entry<K,V> y = rightOf(parentOf(parentOf(x)));
            if (colorOf(y) == RED) {
                setColor(parentOf(x), BLACK);
                setColor(y, BLACK);
                setColor(parentOf(parentOf(x)), RED);
                x = parentOf(parentOf(x));
            } else {
                if (x == rightOf(parentOf(x))) {
                    x = parentOf(x);
                    rotateLeft(x);
                }
                setColor(parentOf(x), BLACK);
                setColor(parentOf(parentOf(x)), RED);
                rotateRight(parentOf(parentOf(x)));
            }
        } else {
            Entry<K,V> y = leftOf(parentOf(parentOf(x)));
            if (colorOf(y) == RED) {
                setColor(parentOf(x), BLACK);
                setColor(y, BLACK);
                setColor(parentOf(parentOf(x)), RED);
                x = parentOf(parentOf(x));
            } else {
                if (x == leftOf(parentOf(x))) {
                    x = parentOf(x);
                    rotateRight(x);
                }
                setColor(parentOf(x), BLACK);
                setColor(parentOf(parentOf(x)), RED);
                rotateLeft(parentOf(parentOf(x)));
            }
        }
    }
    root.color = BLACK;
}

这是 fixAfterInsertion 的源代码,我不具体解释了,这是一个根据不同情况进行旋转,调整结点颜色的过程,可以参考《算法导论》中的解释。

  • remove
public V remove(Object key) {
    Entry<K,V> p = getEntry(key);
    if (p == null)
        return null;

    V oldValue = p.value;
    deleteEntry(p);
    return oldValue;
}

删除的过程主要调用 delteEntry 完成:

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) {
        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.
    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;

        // Fix replacement
        if (p.color == BLACK)
            fixAfterDeletion(replacement);
    } else if (p.parent == null) { // return if we are the only node.
        root = null;
    } 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;
        }
    }
}

这个过程同样是与红黑树的性质相关的。在树中删除一个结点,那他的孩子怎么办啊,红黑树不平衡了怎么办啊,树空了怎么办啊,都需要考虑到。

  • clear
public void clear() {
    modCount++;
    size = 0;
    root = null;
}

居然就是把值都清空了。。

  • clone
/**
 * Returns a shallow copy of this {@code TreeMap} instance. (The keys and
 * values themselves are not cloned.)
 *
 * @return a shallow copy of this map
 */
public Object clone() {
    TreeMap<K,V> clone = null;
    try {
        clone = (TreeMap<K,V>) super.clone();
    } catch (CloneNotSupportedException e) {
        throw new InternalError();
    }

    // Put clone into "virgin" state (except for comparator)
    clone.root = null;
    clone.size = 0;
    clone.modCount = 0;
    clone.entrySet = null;
    clone.navigableKeySet = null;
    clone.descendingMap = null;

    // Initialize clone with our mappings
    try {
        clone.buildFromSorted(size, entrySet().iterator(), null, null);
    } catch (java.io.IOException cannotHappen) {
    } catch (ClassNotFoundException cannotHappen) {
    }

    return clone;
}

clone 复制了一份新的元素,使用了 super.clone() 得的一个新对象,而不是使用 new,这是为啥?然后把各个域值清空,然后使用 buildFromSorted 插入数据。之所以使用这个函数,是因为红黑树插入操作时间复杂度为 O(lgn),n个元素插入就是O(n*lgn),太不划算,更何况我们现在插入的是一个红黑树,所以用一个线性时间复杂度的算法来实现复制数据的操作。

/**
 * Linear time tree building algorithm from sorted data.  Can accept keys
 * and/or values from iterator or stream. This leads to too many
 * parameters, but seems better than alternatives.  The four formats
 * that this method accepts are:
 *
 *    1) An iterator of Map.Entries.  (it != null, defaultVal == null).
 *    2) An iterator of keys.         (it != null, defaultVal != null).
 *    3) A stream of alternating serialized keys and values.
 *                                   (it == null, defaultVal == null).
 *    4) A stream of serialized keys. (it == null, defaultVal != null).
 *
 * It is assumed that the comparator of the TreeMap is already set prior
 * to calling this method.
 *
 * @param size the number of keys (or key-value pairs) to be read from
 *        the iterator or stream
 * @param it If non-null, new entries are created from entries
 *        or keys read from this iterator.
 * @param str If non-null, new entries are created from keys and
 *        possibly values read from this stream in serialized form.
 *        Exactly one of it and str should be non-null.
 * @param defaultVal if non-null, this default value is used for
 *        each value in the map.  If null, each value is read from
 *        iterator or stream, as described above.
 * @throws IOException propagated from stream reads. This cannot
 *         occur if str is null.
 * @throws ClassNotFoundException propagated from readObject.
 *         This cannot occur if str is null.
 */
private void buildFromSorted(int size, Iterator it,
                             java.io.ObjectInputStream str,
                             V defaultVal)
    throws  java.io.IOException, ClassNotFoundException {
    this.size = size;
    root = buildFromSorted(0, 0, size-1, computeRedLevel(size),
                           it, str, defaultVal);
}

好吧,我们继续。

private final Entry<K,V> buildFromSorted(int level, int lo, int hi,
                                         int redLevel,
                                         Iterator it,
                                         java.io.ObjectInputStream str,
                                         V defaultVal)
    throws  java.io.IOException, ClassNotFoundException {
    /*
     * Strategy: The root is the middlemost element. To get to it, we
     * have to first recursively construct the entire left subtree,
     * so as to grab all of its elements. We can then proceed with right
     * subtree.
     *
     * The lo and hi arguments are the minimum and maximum
     * indices to pull out of the iterator or stream for current subtree.
     * They are not actually indexed, we just proceed sequentially,
     * ensuring that items are extracted in corresponding order.
     */

    if (hi < lo) return null;

    int mid = (lo + hi) >>> 1;

    Entry<K,V> left  = null;
    if (lo < mid)
        left = buildFromSorted(level+1, lo, mid - 1, redLevel,
                               it, str, defaultVal);

    // extract key and/or value from iterator or stream
    K key;
    V value;
    if (it != null) {
        if (defaultVal==null) {
            Map.Entry<K,V> entry = (Map.Entry<K,V>)it.next();
            key = entry.getKey();
            value = entry.getValue();
        } else {
            key = (K)it.next();
            value = defaultVal;
        }
    } else { // use stream
        key = (K) str.readObject();
        value = (defaultVal != null ? defaultVal : (V) str.readObject());
    }

    Entry<K,V> middle =  new Entry<>(key, value, null);

    // color nodes in non-full bottommost level red
    if (level == redLevel)
        middle.color = RED;

    if (left != null) {
        middle.left = left;
        left.parent = middle;
    }

    if (mid < hi) {
        Entry<K,V> right = buildFromSorted(level+1, mid+1, hi, redLevel,
                                           it, str, defaultVal);
        middle.right = right;
        right.parent = middle;
    }

    return middle;
}

可以看出这是一个递归的算法,找出中间结点,构造左右子树,并将他们连接在一起。时间复杂度分析可以根据递归式:

T(n) = 2 * T(n/2) + C

如果看不出来的话,可以使用《算法导论》第4章中的主定理,可以得到时间复杂度为 O(n) 。

OpenJDK 源代码阅读之 TreeMap

时间: 2024-07-30 22:19:51

OpenJDK 源代码阅读之 TreeMap的相关文章

OpenJDK 源代码阅读之 HashMap

概要 类继承关系 java.lang.Object java.util.AbstractMap<K,V> java.util.TreeMap<K,V> 定义 public class TreeMap<K,V> extends AbstractMap<K,V> implements NavigableMap<K,V>, Cloneable, Serializable 要点 1) 与 Hashtable 区别在于:非同步,允许 null 2) 不保证

OpenJDK 源代码阅读之 Collections

概要 类继承关系 java.lang.Object java.util.Collections 定义 public class Collections extends Object 实现 sort public static <T extends Comparable<? super T>> void sort(List<T> list) { Object[] a = list.toArray(); Arrays.sort(a); ListIterator<T&g

OpenJDK 源代码阅读之 String

概要 类继承关系 java.lang.Object java.lang.String 定义 public final class String extends Object implements Serializable, Comparable<String>, CharSequence 要点 一旦创建就不可改变 实现 storage /** The value is used for character storage. */ private final char value[]; 可以看出

开始OpenJDK源代码阅读

开始OpenJDK源代码阅读 在阅读了一周的 OpenJDK 源代码后,我才写这篇文章.因为除非你已经开始阅读,否则是不知道自己是不是应该读下去的.所以,不要贸然说自己要干嘛,先做一段时间,觉得感觉还好,再决定做下去. 这一周,主要是看 java.util 中和容器相关的几个文件,虽然还没看太多,但是已经有一些收获了.看到了以前学过的数据结构在Java的标准库中是如何被实现的.也明白了平时使用的一些类的原理是什么.另外,由于最近在看 <Java编程思想>,也能把书中讲的和标准库的源代码对应起来

OpenJDK 源代码阅读之 BitSet

概要 类继承关系 java.lang.Object java.util.BitSet 定义 public class BitSet extends Object implements Cloneable, Serializable 要点 BitSet 类用来支持位操作,给它一个 size ,就会返回一个对象,代表 size 个位.可以完成"与或非"操作. 实现 试想一下,long 最多也就 64 位,假如我们想对 1000 位进行一些运算,要如何实现呢?这个类就告诉我们怎么用一个数组,

Java 推荐读物与源代码阅读

1. Java语言基础     谈到Java语言基础学习的书籍,大家肯定会推荐Bruce Eckel的<Thinking in Java>.它是一本写的相当深刻的技术书籍,Java语言基础部分基本没有其它任何一本书可以超越它.该书的作者Bruce Eckel在网络上被称为天才的投机者,作者的<Thinking in C++>在1995年曾获SoftwareDevelopment Jolt Award最佳书籍大奖,<Thinking in Java>被评为1999年Jav

Notepad++源代码阅读——窗口元素组织与布局

1.1 前言 这两天在看notepad++ 1.0版本的源代码.看了许久终于把程序的窗口之间的关系搞清楚了现在把其组织的要点写于此,希望对大家有所帮助. 1.2 窗口元素之间的关系 Notepad++主要有以下窗口元素(见下图). 其中Notepad_plus 是程序的主要窗口,其他:工具栏.状态栏.主次编辑窗口.主次选项卡窗口以及对话框窗口均为主窗口的子窗口.     _mainDocTab 和 _subDocTab 为 类:DocTabView 其成员_pView 分别指向 _mainEdi

Linux-0.11源代码阅读一 加载操作系统

x86系列CPU可以在16位实模式和32位保护模式下运行,实模式的特点是地址总线只有20位,也就是只有1MB的寻址空间,为了兼容老的CPU,Intel x86系列CPU包括最新的CPU在上电时都运行在16位的实模式下,同时在硬件上强行将CS置成0xF000,IP置成0xFFF0,那么CS:IP就指向0xFFFF0这个地址,也就是上电瞬间代码从该处开始执行,而BIOS恰恰就存储在这个地方,可以想象一下,如果连BIOS都没有将会是一个什么结果. BIOS程序被存储在计算机主板上的一块ROM芯片里,首

linux0.11 源代码阅读记录

*/--> pre.src {background-color: Black; color: White;} pre.src {background-color: Black; color: White;} pre.src {background-color: Black; color: White;} pre.src {background-color: Black; color: White;} pre.src {background-color: Black; color: White;}