TreeMap源码

原博

1.介绍

所有已实现的接口:

Serializable, Cloneable, Map<K,V>, NavigableMap<K,V>, SortedMap<K,V>

public class TreeMap<K,V> extends AbstractMap<K,V> implements NavigableMap<K,V>, Cloneable, Serializable
  • TreeMap 是一个有序的key-value集合,它是通过红黑树实现的。
    • 该映射根据其键的自然顺序进行排序,或者根据创建映射时提供的 Comparator 进行排序,具体取决于使用的构造方法。
  • TreeMap 继承于AbstractMap,所以它是一个Map,即一个key-value集合。
  • TreeMap 实现了NavigableMap接口,意味着它支持一系列的导航方法。比如返回有序的key集合。
  • TreeMap 实现了Cloneable接口,意味着它能被克隆
  • TreeMap 实现了java.io.Serializable接口,意味着它支持序列化

此实现为 containsKey、get、put 和 remove 操作提供受保证的 log(n) 时间开销。

此实现不是同步的。

2.构造方法

TreeMap():使用键的自然顺序构造一个新的、空的树映射。

TreeMap(Comparator<? super K> comparator):构造一个新的、空的树映射,该映射根据给定比较器进行排序。

TreeMap(Map<? extends K,? extends V> m):  构造一个与给定映射具有相同映射关系的新的树映射,该映射根据其键的自然顺序 进行排序。

TreeMap(SortedMap<K,? extends V> m):构造一个与指定有序映射具有相同映射关系和相同排序顺序的新的树映射。

3.TreeMap和Map的关系

TreeMap的本质是R-B Tree(红黑树),它包含几个重要的成员变量: root, size, comparator。

  • root 是红黑数的根节点。它是Entry类型。
    • Entry是红黑数的节点,它包含了红黑数的6个基本组成成分:key(键)、value(值)、left(左孩子)、right(右孩子)、parent(父节点)、color(颜色)。
    • Entry节点根据key进行排序,Entry节点包含的内容为value。
    • 红黑数排序时,根据Entry中的key进行排序。
    • Entry中的key比较大小是根据比较器comparator来进行判断的。
  • size是红黑数中节点的个数。

4.源码分析

4.1TreeMap的红黑树相关内容

4.1.1数据结构

//红黑树的节点颜色--红色
private static final boolean RED = false;

//红黑树的节点颜色--黑色
private static final boolean BLACK = true;

// “红黑树的节点”对应的类。
static final class Entry<K,V> implements Map.Entry<K,V> { ... }

Entry包含了6个部分内容:

  • key(键)
  • value(值)
  • left(左孩子)
  • right(右孩子)
  • parent(父节点)
  • color(颜色)

Entry节点根据key进行排序,Entry节点包含的内容为value。

4.1.2 相关操作

//左旋
private void rotateLeft(Entry<K,V> p) { ... }

//右旋
private void rotateRight(Entry<K,V> p) { ... }

//插入操作
public V put(K key, V value) { ... }

/*插入修正操作
红黑树执行插入操作之后,要执行“插入修正操作”。
目的是:保红黑树在进行插入节点之后,仍然是一颗红黑树*/
private void fixAfterInsertion(Entry<K,V> x) { ... }

//删除操作
private void deleteEntry(Entry<K,V> p) { ... }

/*删除修正操作
红黑树执行删除之后,要执行“删除修正操作”。
目的是保证:红黑树删除节点之后,仍然是一颗红黑树*/
private void fixAfterDeletion(Entry<K,V> x) { ... }

4.2构造函数

4.2.1 默认构造函数

使用默认构造函数构造TreeMap时,使用java的默认的比较器比较Key的大小,从而对TreeMap进行排序。

public TreeMap() {
    comparator = null;
}

4.2.2 带比较器的构造函数

public TreeMap(Comparator<? super K> comparator) {
    this.comparator = comparator;
}

4.2.3 带Map的构造函数,Map会成为TreeMap的子集

public TreeMap(Map<? extends K, ? extends V> m) {
    comparator = null;
    putAll(m);
}

该构造函数会调用putAll()将m中的所有元素添加到TreeMap中。

putAll()源码如下:

public void putAll(Map<? extends K, ? extends V> m) {
    for (Map.Entry<? extends K, ? extends V> e : m.entrySet())
        put(e.getKey(), e.getValue());
}

从中,我们可以看出putAll()就是将m中的key-value逐个的添加到TreeMap中。

4.2.4 带SortedMap的构造函数,SortedMap会成为TreeMap的子集

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

该构造函数不同于上一个构造函数,在上一个构造函数中传入的参数是MapMap不是有序的,所以要逐个添加

而该构造函数的参数是SortedMap是一个有序的Map,我们通过buildFromSorted()来创建对应的Map。

buildFromSorted涉及到的代码如下:

// 根据已经一个排好序的map创建一个TreeMap
    // 将map中的元素逐个添加到TreeMap中,并返回map的中间元素作为根节点。
    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 {

        if (hi < lo) return null;

      
        // 获取中间元素
        int mid = (lo + hi) / 2;

        Entry<K,V> left  = null;
        // 若lo小于mid,则递归调用获取(middel的)左孩子。
        if (lo < mid)
            left = buildFromSorted(level+1, lo, mid - 1, redLevel,
                   it, str, defaultVal);

        // 获取middle节点对应的key和value
        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());
        }

        // 创建middle节点
        Entry<K,V> middle =  new Entry<K,V>(key, value, null);

        // 若当前节点的深度=红色节点的深度,则将节点着色为红色。
        if (level == redLevel)
            middle.color = RED;

        // 设置middle为left的父亲,left为middle的左孩子
        if (left != null) {
            middle.left = left;
            left.parent = middle;
        }

        if (mid < hi) {
            // 递归调用获取(middel的)右孩子。
            Entry<K,V> right = buildFromSorted(level+1, mid+1, hi, redLevel,
                           it, str, defaultVal);
            // 设置middle为left的父亲,left为middle的左孩子
            middle.right = right;
            right.parent = middle;
        }

        return middle;
    }

要理解buildFromSorted,重点说明以下几点:

  • buildFromSorted是通过递归将SortedMap中的元素逐个关联
  • buildFromSorted返回middle节点(中间节点)作为root。
  • buildFromSorted添加到红黑树中时,只将level == redLevel的节点设为红色。
    • 第level级节点,实际上是buildFromSorted转换成红黑树后的最底端(假设根节点在最上方)的节点
    • 只将红黑树最底端的阶段着色为红色,其余都是黑色。

4.3 TreeMap的Entry相关函数

TreeMap的 firstEntry()、 lastEntry()、 lowerEntry()、 higherEntry()、 floorEntry()、 ceilingEntry()、 pollFirstEntry() 、 pollLastEntry() 原理都是类似的;

下面以firstEntry()来进行详细说明

我们先看看firstEntry()和getFirstEntry()的代码:

public Map.Entry<K,V> firstEntry() {
    return exportEntry(getFirstEntry());
}

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

从中,我们可以看出 firstEntry() 和 getFirstEntry() 都是用于获取第一个节点。

  • firstEntry() 是对外接口
  • getFirstEntry() 是内部接口
  • firstEntry() 是通过 getFirstEntry() 来实现的。

那为什么外界不能直接调用 getFirstEntry(),而需要多此一举的调用 firstEntry() 呢?

这么做的目的是:防止用户修改返回的Entry。

  • getFirstEntry()返回的Entry是可以被修改的,
  • 但是经过firstEntry()返回的Entry不能被修改,只可以读取Entry的key值和value值。

下面我们看看到底是如何实现的。

4.3.1 getFirstEntry()

getFirstEntry()返回的是Entry节点,而Entry是红黑树的节点,它的源码如下:

// 返回“红黑树的第一个节点”
final Entry<K,V> getFirstEntry() {
    Entry<K,V> p = root;
    if (p != null)
    while (p.left != null)
            p = p.left;
    return p;
}

从中,我们可以调用Entry的getKey()、getValue()来获取key和value值,以及调用setValue()来修改value的值。

4.3.2 firstEntry()返回的是exportEntry(getFirstEntry())

下面我们看看exportEntry()干了些什么?

static <K,V> Map.Entry<K,V> exportEntry(TreeMap.Entry<K,V> e) {
    return e == null? null :
        new AbstractMap.SimpleImmutableEntry<K,V>(e);
}

实际上,exportEntry() 是新建一个AbstractMap.SimpleImmutableEntry类型的对象,并返回。

SimpleImmutableEntry的实现在AbstractMap.java中.

下面我们看看AbstractMap.SimpleImmutableEntry是如何实现的,代码如下:

public static class SimpleImmutableEntry<K,V>
implements Entry<K,V>, java.io.Serializable
{
    private static final long serialVersionUID = 7138329143949025153L;

    private final K key;
    private final V value;

    public SimpleImmutableEntry(K key, V value) {
        this.key   = key;
        this.value = value;
    }

    public SimpleImmutableEntry(Entry<? extends K, ? extends V> entry) {
        this.key   = entry.getKey();
        this.value = entry.getValue();
    }

    public K getKey() {
        return key;
    }

    public V getValue() {
        return value;
    }

    public V setValue(V value) {
        throw new UnsupportedOperationException();
    }

    public boolean equals(Object o) {
        if (!(o instanceof Map.Entry))
        return false;
        Map.Entry e = (Map.Entry)o;
        return eq(key, e.getKey()) && eq(value, e.getValue());
    }

    public int hashCode() {
        return (key   == null ? 0 :   key.hashCode()) ^
           (value == null ? 0 : value.hashCode());
    }

    public String toString() {
        return key + "=" + value;
    }
}

从中,我们可以看出SimpleImmutableEntry实际上是简化的key-value节点

它只提供了getKey()、getValue()方法类获取节点的值;但不能修改value的值,因为调用 setValue() 会抛出异常UnsupportedOperationException();

之前的问题:

为什么外界不能直接调用 getFirstEntry(),而需要多此一举的调用 firstEntry() 呢?

  1. firstEntry()是对外接口,而getFirstEntry()是内部接口
  2. 对firstEntry()返回的Entry对象只能进行getKey()、getValue()等读取操作;而对getFirstEntry()返回的对象除了可以进行读取操作之后,还可以通过setValue()修改值。

4.4 TreeMap的key相关函数

TreeMap的firstKey()、lastKey()、lowerKey()、higherKey()、floorKey()、ceilingKey()原理都是类似的;

下面以ceilingKey()来进行详细说明。

ceilingKey(K key)的作用:返回大于/等于key的最小的键值对所对应的KEY,没有的话返回null。

它的代码如下:

public K ceilingKey(K key) {
    return keyOrNull(getCeilingEntry(key));
}

ceilingKey()是通过getCeilingEntry()实现的。keyOrNull()的代码很简单,它是获取节点的key,没有的话,返回null。

static <K,V> K keyOrNull(TreeMap.Entry<K,V> e) {
    return e == null? null : e.key;
}

getCeilingEntry(K key):获取TreeMap中大于/等于key的最小的节点,若不存在(即TreeMap中所有节点的键都比key大),就返回null。

它的实现代码如下:

final Entry<K,V> getCeilingEntry(K key) {
    Entry<K,V> p = root;
    while (p != null) {
        int cmp = compare(key, p.key);
        // 情况一:若“p的key” > key。
        // 若 p 存在左孩子,则设 p=“p的左孩子”;
        // 否则,返回p
        if (cmp < 0) {
            if (p.left != null)
                p = p.left;
            else
                return p;
        // 情况二:若“p的key” < key。
        } else if (cmp > 0) {
            // 若 p 存在右孩子,则设 p=“p的右孩子”
            if (p.right != null) {
                p = p.right;
            } else {
                // 若 p 不存在右孩子,则找出 p 的后继节点,并返回
                // 注意:这里返回的 “p的后继节点”有2种可能性:第一,null;第二,TreeMap中大于key的最小的节点。
                //   理解这一点的核心是,getCeilingEntry是从root开始遍历的。
                //   若getCeilingEntry能走到这一步,那么,它之前“已经遍历过的节点的key”都 > key。
                //   能理解上面所说的,那么就很容易明白,为什么“p的后继节点”有2种可能性了。
                Entry<K,V> parent = p.parent;
                Entry<K,V> ch = p;
                while (parent != null && ch == parent.right) {
                    ch = parent;
                    parent = parent.parent;
                }
                return parent;
            }
        // 情况三:若“p的key” = key。
        } else
            return p;
    }
    return null;
}

4.5 TreeMap其它函数

4.5.1  顺序遍历和逆序遍历

TreeMap的顺序遍历和逆序历原理非常简单。

由于TreeMap中的元素是从小到大的顺序排列的因此:

  • 顺序遍历,就是从第一个元素开始,逐个向后遍历;
  • 而倒序遍历则恰恰相反,它是从最后一个元素开始,逐个往前遍历。

我们可以通过 keyIterator() 和 descendingKeyIterator()来说明!

  • keyIterator():返回顺序的KEY的集合,
  • descendingKeyIterator():返回逆序的KEY的集合。

keyIterator() 的代码如下:

Iterator<K> keyIterator() {
    return new KeyIterator(getFirstEntry());
}

说明:从中我们可以看出keyIterator() 是返回以“第一个节点(getFirstEntry)” 为其实元素的迭代器

KeyIterator的代码如下:

final class KeyIterator extends PrivateEntryIterator<K> {
    KeyIterator(Entry<K,V> first) {
        super(first);
    }
    public K next() {
        return nextEntry().key;
    }
}

说明:KeyIterator继承于PrivateEntryIterator。当我们通过next()不断获取下一个元素的时候,就是执行的顺序遍历了。

descendingKeyIterator()的代码如下:

Iterator<K> descendingKeyIterator() {
    return new DescendingKeyIterator(getLastEntry());
}

说明:从中我们可以看出descendingKeyIterator() 是返回以“最后一个节点(getLastEntry)” 为其实元素的迭代器

再看看DescendingKeyIterator的代码:

5.TreeMap遍历方式

5.1 遍历TreeMap的键值对

  1. 根据entrySet()获取TreeMap的“键值对”的Set集合。
  2. 通过Iterator迭代器遍历“第一步”得到的集合。
// 假设map是TreeMap对象
// map中的key是String类型,value是Integer类型
Integer integ = null;
Iterator iter = map.entrySet().iterator();
while(iter.hasNext()) {
    Map.Entry entry = (Map.Entry)iter.next();
    // 获取key
    key = (String)entry.getKey();
        // 获取value
    integ = (Integer)entry.getValue();
}

5.2 遍历TreeMap的键

  1. 根据keySet()获取TreeMap的“键”的Set集合。
  2. 通过Iterator迭代器遍历“第一步”得到的集合。
// 假设map是TreeMap对象
// map中的key是String类型,value是Integer类型
String key = null;
Integer integ = null;
Iterator iter = map.keySet().iterator();
while (iter.hasNext()) {
        // 获取key
    key = (String)iter.next();
        // 根据key,获取value
    integ = (Integer)map.get(key);
}

5.3 遍历TreeMap的值

  1. 根据value()获取TreeMap的“值”的集合。
  2. 通过Iterator迭代器遍历“第一步”得到的集合。
// 假设map是TreeMap对象
// map中的key是String类型,value是Integer类型
Integer value = null;
Collection c = map.values();
Iterator iter= c.iterator();
while (iter.hasNext()) {
    value = (Integer)iter.next();
}

 

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值