TreeMap源码解析

参考:
Java编程的逻辑

1 前导知识

排序二叉树找到一个节点的后继节点的算法:

1.如果该节点有右孩子,则后继为右子树中最小的节点。

2.如果该节点没有右孩子,则后继为父节点或某个祖先节点,从当前节点往上找,如果它是父亲节点的右孩子,则继续找父节点,直到它不是右孩子或父节点为空,第一个非右孩子节点的父亲节点就是后继节点,如果找不到这样的祖先节点,则后继为空,遍历结束。

例子:绿色箭头代表当前节点的后继

[外链图片转存失败(img-WxLWPFLo-1564659512194)(…/markdownPicture/assets/1564651826057.png)]

插入

在排序二叉树中,插入元素首先要找插入位置,即新节点的父节点,与查找元素类似,从根节点开始往下找,其步骤为:

  1. 与当前节点比较,如果相同,表示已经存在了,不能再插入。
  2. 如果小于当前节点,则到左子树中寻找,如果左子树为空,则当前节点即为要找的父节点。
  3. 如果大于当前节点,则到右子树中寻找,如果右子树为空,则当前节点即为要找的父节点。

找到父节点后,即可插入,如果插入元素小于父节点,则作为左孩子插入,否则作为右孩子插入:
[外链图片转存失败(img-EJGz5wvh-1564659512197)(…/markdownPicture/assets/1564651802138.png)]

删除

从排序二叉树中删除一个节点要复杂一些,有三种情况:

  1. 节点为叶子节点
  2. 节点只有一个孩子
  3. 节点有两个孩子

如果节点为叶子节点,则很简单,可以直接删掉,修改父节点的对应孩子为空即可。

如果节点只有一个孩子节点,则替换待删节点为孩子节点,或者说,在孩子节点和父节点之间直接建立链接:
[外链图片转存失败(img-p5SxzJel-1564659512198)(…/markdownPicture/assets/1564651766777.png)]

如果节点有两个孩子,则首先找该节点的后继,找到后继后,替换待删节点为后继的内容,然后再删除后继节点。后继节点没有左孩子,这就将两个孩子的情况转换为了叶子节点或只有一个孩子的情况:
[外链图片转存失败(img-eBxkjOIs-1564659512199)(…/markdownPicture/assets/1564651776307.png)]

2 实现原理

2.1 构造方法

TreeMap有两个基本构造方法:

public TreeMap()
public TreeMap(Comparator<? super K> comparator)

第一个为默认构造方法,如果使用默认构造方法,要求Map中的键实现Comparabe接口,TreeMap内部进行各种比较时会调用键的Comparable接口中的compareTo方法。

第二个接受一个比较器对象comparator,如果comparator不为null,在TreeMap内部进行比较时会调用这个comparator的compare方法,而不再调用键的compareTo方法,也不再要求键实现Comparable接口。

应该用哪一个呢?第一个更为简单,但要求键实现Comparable接口,且期望的排序和键的比较结果是一致的,第二个更为灵活,不要求键实现Comparable接口,比较器可以用灵活复杂的方式进行实现。

需要强调的是,TreeMap是按键而不是按值有序,无论哪一种,都是对键而非值进行比较。

除了这两个基本构造方法,TreeMap还有如下构造方法:

public TreeMap(Map<? extends K, ? extends V> m)
public TreeMap(SortedMap<K, ? extends V> m) 

SortedMap接口:它扩展了Map接口,表示有序的Map,它有一个comparator()方法,返回其比较器

这两个构造方法都是接受一个已有的Map,将其所有键值对添加到当前TreeMap中来,区别在于,第一个构造方法中,比较器会设为null,而第二个,比较器会设为和参数SortedMap中的一样。

2.2 SortedMap接口

public interface SortedMap<K,V> extends Map<K,V> {
    Comparator<? super K> comparator();
    SortedMap<K,V> subMap(K fromKey, K toKey);
    SortedMap<K,V> headMap(K toKey);
    SortedMap<K,V> tailMap(K fromKey);
    K firstKey();
    K lastKey();
}

firstKey返回第一个键,而lastKey返回最后一个键。

headMap/tailMap/subMap都返回一个视图,视图中包括一部分键值对,它们的区别在于键的取值范围:

  • headMap:为小于toKey的所有键
  • tailMap:为大于等于fromKey的所有键
  • subMap:为大于等于fromKey且小于toKey的所有键。

2.3 NavigableMap接口

NavigableMap扩展了SortedMap,主要增加了一些查找邻近键的方法,比如:

Map.Entry<K,V> floorEntry(K key);
Map.Entry<K,V> lowerEntry(K key);
Map.Entry<K,V> ceilingEntry(K key);
Map.Entry<K,V> higherEntry(K key);

参数key对应的键不一定存在,但这些方法可能都有返回值,它们都返回一个邻近键值对,它们的区别在于,这个邻近键与参数key的关系。

  • floorEntry:邻近键是小于等于key的键中最大的
  • lowerEntry:邻近键是严格小于key的键中最大的
  • ceilingEntry:邻近键是大于等于key的键中最小的
  • higherEntry:邻近键是严格大于key的键中最小的

如果没有对应的邻近键,返回值为null。这些方法也都有对应的只返回键的方法:

K floorKey(K key);
K lowerKey(K key);
K ceilingKey(K key);
K higherKey(K key);

相比SortedMap中的方法headMap/tailMap/subMap,NavigableMap也增加了一些方法,以更为明确的方式指定返回值中是否包含边界值,如:

NavigableMap<K,V> headMap(K toKey, boolean inclusive);
NavigableMap<K,V> tailMap(K fromKey, boolean inclusive);
NavigableMap<K,V> subMap(K fromKey, boolean fromInclusive,
                             K toKey,   boolean toInclusive);

相比SortedMap中对头尾键的基本操作,NavigableMap增加了如下方法:

Map.Entry<K,V> firstEntry();
Map.Entry<K,V> lastEntry();
Map.Entry<K,V> pollFirstEntry();
Map.Entry<K,V> pollLastEntry();

firstEntry返回第一个键值对,lastEntry返回最后一个。pollFirstEntry删除并返回第一个键值对,pollLastEntry删除并返回最后一个。

此外,NavigableMap有如下方法,可以方便的逆序访问:

NavigableMap<K,V> descendingMap();
NavigableSet<K> descendingKeySet();

2.4 内部组成

TreeMap内部主要成员:

private final Comparator<? super K> comparator;
private transient Entry<K,V> root = null;
private transient int size = 0;//当前键值对个数

Entry是TreeMap的一个内部类,其内部成员和构造方法为:

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;

    /**
     * Make a new cell with given key, value, and parent, and with
     * {@code null} child links, and BLACK color.
     */
    Entry(K key, V value, Entry<K,V> parent) {
        this.key = key;
        this.value = value;
        this.parent = parent;
    }
}    

每个节点除了键(key)和值(value)之外,还有三个引用,分别指向其左孩子(left)、右孩子(right)和父节点(parent);
对于根节点,父节点为null,对于叶子节点,孩子节点都为null;
还有一个成员color表示颜色,TreeMap是用红黑树实现的,每个节点都有一个颜色,非黑即红。

2.5 put

public V put(K key, V value) {
    Entry<K,V> t = root;
    //添加第一个节点的情况
    if (t == null) {
        //检查key的类型和null,如果类型不匹配或为null,compare方法会抛出异常。
        compare(key, key); 

        root = new Entry<>(key, value, null);
        size = 1;
        modCount++;//用于迭代过程中检测结构性变化,failFast机制
        return null;
    }
    
    int cmp;
    Entry<K,V> parent;
    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的父节点情况
    //如果没有设置comparator,则假设key一定实现了Comparable接口,使用Comparable接口的compareTo方法进行比较
    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;
}
final int compare(Object k1, Object k2) {
    return comparator==null ? ((Comparable<? super K>)k1).compareTo((K)k2)
        : comparator.compare((K)k1, (K)k2);
}

2.6 get

//根据key找对应节点p,找到节点后获取值p.value
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;
}
//有比较器的情况
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;
}

2.7 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;
}
//返回第一个节点,即最左边的节点
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;
    }
}

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

private void deleteEntry(Entry<K,V> p) {
    modCount++;
    size--;
	//处理两个孩子的情况:s为后继,当前节点p的key和value设置为了s的key和value,然后将待删节点p指向了s,这样就转换为了一个孩子或叶子节点的情况
    if (p.left != null && p.right != null) {
        Entry<K,V> s = successor(p);
        p.key = s.key;
        p.value = s.value;
        p = s;
    } 
    //要替换p的孩子节点
    Entry<K,V> replacement = (p.left != null ? p.left : p.right);
	//一个孩子的情况
    //p的父节点p.parent和replacement之间建立链接,以替换p.parent和p原来的链接;如果p.parent为null,则修改root以指向新的根;fixAfterDeletion重新平衡树。
    if (replacement != null) {
        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;

        p.left = p.right = p.parent = null;

        if (p.color == BLACK)
            fixAfterDeletion(replacement);
    //叶子节点的情况:两种
    } else if (p.parent == null) { //如果删除的是最后一个节点
        root = null;
    } else { //如果不是,则根据待删节点是父节点的左孩子还是右孩子,相应的设置孩子节点为null。
        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;
        }
    }
}

3 特点分析

与HashMap相比,TreeMap同样实现了Map接口,但内部使用红黑树实现,红黑树是统计效率比较高的大致平衡的排序二叉树,这决定了它有如下特点:

  • 按键有序,TreeMap同样实现了SortedMap和NavigableMap接口,可以方便的根据键的顺序进行查找,如第一个、最后一个、某一范围的键、邻近键等。
  • 为了按键有序,TreeMap要求键实现Comparable接口或通过构造方法提供一个Comparator对象。
  • 根据键保存、查找、删除的效率比较高,为O(h),h为树的高度,在树平衡的情况下,h为log2(N),N为节点数。

不要求排序,优先考虑HashMap,要求排序,考虑TreeMap。

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值