Java 集合系列12之 TreeMap详细介绍(源码解析)和使用示例

1,031 阅读16分钟

第1部分 TreeMap介绍 TreeMap 简介

TreeMap 是一个有序的key-value集合,它是通过红黑树实现的。 TreeMap 继承于AbstractMap,所以它是一个Map,即一个key-value集合。 TreeMap 实现了NavigableMap接口,意味着它支持一系列的导航方法。比如返回有序的key集合。 TreeMap 实现了Cloneable接口,意味着它能被克隆。 TreeMap 实现了java.io.Serializable接口,意味着它支持序列化。

TreeMap基于红黑树(Red-Black tree)实现。该映射根据其键的自然顺序进行排序,或者根据创建映射时提供的 Comparator 进行排序,具体取决于使用的构造方法。 TreeMap的基本操作 containsKey、get、put 和 remove 的时间复杂度是 log(n) 。 另外,TreeMap是非同步的。 它的iterator 方法返回的迭代器是fail-fastl的。

TreeMap的构造函数

复制代码 // 默认构造函数。使用该构造函数,TreeMap中的元素按照自然排序进行排列。 TreeMap()

// 创建的TreeMap包含Map TreeMap(Map<? extends K, ? extends V> copyFrom)

// 指定Tree的比较器 TreeMap(Comparator<? super K> comparator)

// 创建的TreeSet包含copyFrom TreeMap(SortedMap<K, ? extends V> copyFrom) 复制代码

TreeMap的API

复制代码 Entry<K, V> ceilingEntry(K key) K ceilingKey(K key) void clear() Object clone() Comparator<? super K> comparator() boolean containsKey(Object key) NavigableSet descendingKeySet() NavigableMap<K, V> descendingMap() Set<Entry<K, V>> entrySet() Entry<K, V> firstEntry() K firstKey() Entry<K, V> floorEntry(K key) K floorKey(K key) V get(Object key) NavigableMap<K, V> headMap(K to, boolean inclusive) SortedMap<K, V> headMap(K toExclusive) Entry<K, V> higherEntry(K key) K higherKey(K key) boolean isEmpty() Set keySet() Entry<K, V> lastEntry() K lastKey() Entry<K, V> lowerEntry(K key) K lowerKey(K key) NavigableSet navigableKeySet() Entry<K, V> pollFirstEntry() Entry<K, V> pollLastEntry() V put(K key, V value) V remove(Object key) int size() SortedMap<K, V> subMap(K fromInclusive, K toExclusive) NavigableMap<K, V> subMap(K from, boolean fromInclusive, K to, boolean toInclusive) NavigableMap<K, V> tailMap(K from, boolean inclusive) SortedMap<K, V> tailMap(K fromInclusive) 复制代码

第2部分 TreeMap数据结构 TreeMap的继承关系

复制代码 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, java.io.Serializable {} 复制代码

TreeMap与Map关系如下图:

从图中可以看出: (01) TreeMap实现继承于AbstractMap,并且实现了NavigableMap接口。 (02) 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是红黑数中节点的个数。

关于红黑数的具体算法,请参考"红黑树(一) 原理和算法详细介绍"。

第3部分 TreeMap源码解析(基于JDK1.6.0_45) 为了更了解TreeMap的原理,下面对TreeMap源码代码作出分析。我们先给出源码内容,后面再对源码进行详细说明,当然,源码内容中也包含了详细的代码注释。读者阅读的时候,建议先看后面的说明,先建立一个整体印象;之后再阅读源码。

View Code 说明:

在详细介绍TreeMap的代码之前,我们先建立一个整体概念。 TreeMap是通过红黑树实现的,TreeMap存储的是key-value键值对,TreeMap的排序是基于对key的排序。 TreeMap提供了操作“key”、“key-value”、“value”等方法,也提供了对TreeMap这颗树进行整体操作的方法,如获取子树、反向树。 后面的解说内容分为几部分, 首先,介绍TreeMap的核心,即红黑树相关部分; 然后,介绍TreeMap的主要函数; 再次,介绍TreeMap实现的几个接口; 最后,补充介绍TreeMap的其它内容。

TreeMap本质上是一颗红黑树。要彻底理解TreeMap,建议读者先理解红黑树。关于红黑树的原理,可以参考:红黑树(一) 原理和算法详细介绍

第3.1部分 TreeMap的红黑树相关内容

TreeMap中于红黑树相关的主要函数有: 1 数据结构 1.1 红黑树的节点颜色--红色

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

private static final boolean BLACK = true; 1.3 “红黑树的节点”对应的类。

static final class Entry<K,V> implements Map.Entry<K,V> { ... } Entry包含了6个部分内容:key(键)、value(值)、left(左孩子)、right(右孩子)、parent(父节点)、color(颜色) Entry节点根据key进行排序,Entry节点包含的内容为value。

2 相关操作

2.1 左旋

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

private void rotateRight(Entry<K,V> p) { ... } 2.3 插入操作

public V put(K key, V value) { ... } 2.4 插入修正操作 红黑树执行插入操作之后,要执行“插入修正操作”。 目的是:保红黑树在进行插入节点之后,仍然是一颗红黑树

private void fixAfterInsertion(Entry<K,V> x) { ... } 2.5 删除操作

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

红黑树执行删除之后,要执行“删除修正操作”。 目的是保证:红黑树删除节点之后,仍然是一颗红黑树

private void fixAfterDeletion(Entry<K,V> x) { ... } 关于红黑树部分,这里主要是指出了TreeMap中那些是红黑树的主要相关内容。具体的红黑树相关操作API,这里没有详细说明,因为它们仅仅只是将算法翻译成代码。读者可以参考“红黑树(一) 原理和算法详细介绍”进行了解。

第3.2部分 TreeMap的构造函数

1 默认构造函数

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

public TreeMap() { comparator = null; } 2 带比较器的构造函数

public TreeMap(Comparator<? super K> comparator) { this.comparator = comparator; } 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 带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) { } } 复制代码 该构造函数不同于上一个构造函数,在上一个构造函数中传入的参数是Map,Map不是有序的,所以要逐个添加。 而该构造函数的参数是SortedMap是一个有序的Map,我们通过buildFromSorted()来创建对应的Map。 buildFromSorted涉及到的代码如下:

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

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

第3.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值。下面我们看看到底是如何实现的。 (01) 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的值。

(02) 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是如何实现的,代码如下:

View Code 从中,我们可以看出SimpleImmutableEntry实际上是简化的key-value节点。 它只提供了getKey()、getValue()方法类获取节点的值;但不能修改value的值,因为调用 setValue() 会抛出异常UnsupportedOperationException();

再回到我们之前的问题:那为什么外界不能直接调用 getFirstEntry(),而需要多此一举的调用 firstEntry() 呢? 现在我们清晰的了解到: (01) firstEntry()是对外接口,而getFirstEntry()是内部接口。 (02) 对firstEntry()返回的Entry对象只能进行getKey()、getValue()等读取操作;而对getFirstEntry()返回的对象除了可以进行读取操作之后,还可以通过setValue()修改值。

第3.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”。它的实现代码如下:

View Code

第3.5部分 TreeMap的values()函数

values() 返回“TreeMap中值的集合”

values()的实现代码如下:

public Collection values() { Collection vs = values; return (vs != null) ? vs : (values = new Values()); } 说明:从中,我们可以发现values()是通过 new Values() 来实现 “返回TreeMap中值的集合”。

那么Values()是如何实现的呢? 没错!由于返回的是值的集合,那么Values()肯定返回一个集合;而Values()正好是集合类Value的构造函数。Values继承于AbstractCollection,它的代码如下:

View Code 说明:从中,我们可以知道Values类就是一个集合。而 AbstractCollection 实现了除 size() 和 iterator() 之外的其它函数,因此只需要在Values类中实现这两个函数即可。 size() 的实现非常简单,Values集合中元素的个数=该TreeMap的元素个数。(TreeMap每一个元素都有一个值嘛!) iterator() 则返回一个迭代器,用于遍历Values。下面,我们一起可以看看iterator()的实现:

public Iterator iterator() { return new ValueIterator(getFirstEntry()); } 说明: iterator() 是通过ValueIterator() 返回迭代器的,ValueIterator是一个类。代码如下:

复制代码 final class ValueIterator extends PrivateEntryIterator { ValueIterator(Entry<K,V> first) { super(first); } public V next() { return nextEntry().value; } } 复制代码 说明:ValueIterator的代码很简单,它的主要实现应该在它的父类PrivateEntryIterator中。下面我们一起看看PrivateEntryIterator的代码:

View Code 说明:PrivateEntryIterator是一个抽象类,它的实现很简单,只只实现了Iterator的remove()和hasNext()接口,没有实现next()接口。 而我们在ValueIterator中已经实现的next()接口。 至此,我们就了解了iterator()的完整实现了。

第3.6部分 TreeMap的entrySet()函数

entrySet() 返回“键值对集合”。顾名思义,它返回的是一个集合,集合的元素是“键值对”。

下面,我们看看它是如何实现的?entrySet() 的实现代码如下:

public Set<Map.Entry<K,V>> entrySet() { EntrySet es = entrySet; return (es != null) ? es : (entrySet = new EntrySet()); } 说明:entrySet()返回的是一个EntrySet对象。

下面我们看看EntrySet的代码:

View Code 说明: EntrySet是“TreeMap的所有键值对组成的集合”,而且它单位是单个“键值对”。 EntrySet是一个集合,它继承于AbstractSet。而AbstractSet实现了除size() 和 iterator() 之外的其它函数,因此,我们重点了解一下EntrySet的size() 和 iterator() 函数

size() 的实现非常简单,AbstractSet集合中元素的个数=该TreeMap的元素个数。 iterator() 则返回一个迭代器,用于遍历AbstractSet。从上面的源码中,我们可以发现iterator() 是通过EntryIterator实现的;下面我们看看EntryIterator的源码:

复制代码 final class EntryIterator extends PrivateEntryIterator<Map.Entry<K,V>> { EntryIterator(Entry<K,V> first) { super(first); } public Map.Entry<K,V> next() { return nextEntry(); } } 复制代码 说明:和Values类一样,EntryIterator也继承于PrivateEntryIterator类。

第3.7部分 TreeMap实现的Cloneable接口

TreeMap实现了Cloneable接口,即实现了clone()方法。 clone()方法的作用很简单,就是克隆一个TreeMap对象并返回。

View Code

第3.8部分 TreeMap实现的Serializable接口

TreeMap实现java.io.Serializable,分别实现了串行读取、写入功能。 串行写入函数是writeObject(),它的作用是将TreeMap的“容量,所有的Entry”都写入到输出流中。 而串行读取函数是readObject(),它的作用是将TreeMap的“容量、所有的Entry”依次读出。 readObject() 和 writeObject() 正好是一对,通过它们,我能实现TreeMap的串行传输。

View Code 说到这里,就顺便说一下“关键字transient”的作用

transient是Java语言的关键字,它被用来表示一个域不是该对象串行化的一部分。 Java的serialization提供了一种持久化对象实例的机制。当持久化对象时,可能有一个特殊的对象数据成员,我们不想用serialization机制来保存它。为了在一个特定对象的一个域上关闭serialization,可以在这个域前加上关键字transient。 当一个对象被串行化的时候,transient型变量的值不包括在串行化的表示中,然而非transient型的变量是被包括进去的。

第3.9部分 TreeMap实现的NavigableMap接口

firstKey()、lastKey()、lowerKey()、higherKey()、ceilingKey()、floorKey(); firstEntry()、 lastEntry()、 lowerEntry()、 higherEntry()、 floorEntry()、 ceilingEntry()、 pollFirstEntry() 、 pollLastEntry(); 上面已经讲解过这些API了,下面对其它的API进行说明。

1 反向TreeMap descendingMap() 的作用是返回当前TreeMap的反向的TreeMap。所谓反向,就是排序顺序和原始的顺序相反。

我们已经知道TreeMap是一颗红黑树,而红黑树是有序的。 TreeMap的排序方式是通过比较器,在创建TreeMap的时候,若指定了比较器,则使用该比较器;否则,就使用Java的默认比较器。 而获取TreeMap的反向TreeMap的原理就是将比较器反向即可!

理解了descendingMap()的反向原理之后,再讲解一下descendingMap()的代码。

复制代码 // 获取TreeMap的降序Map public NavigableMap<K, V> descendingMap() { NavigableMap<K, V> km = descendingMap; return (km != null) ? km : (descendingMap = new DescendingSubMap(this, true, null, true, true, null, true)); } 复制代码 从中,我们看出descendingMap()实际上是返回DescendingSubMap类的对象。下面,看看DescendingSubMap的源码:

View Code 从中,我们看出DescendingSubMap是降序的SubMap,它的实现机制是将“SubMap的比较器反转”。

它继承于NavigableSubMap。而NavigableSubMap是一个继承于AbstractMap的抽象类;它包括2个子类——"(升序)AscendingSubMap"和"(降序)DescendingSubMap"。NavigableSubMap为它的两个子类实现了许多公共API。 下面看看NavigableSubMap的源码。

View Code NavigableSubMap源码很多,但不难理解;读者可以通过源码和注释进行理解。

其实,读完NavigableSubMap的源码后,我们可以得出它的核心思想是:它是一个抽象集合类,为2个子类——"(升序)AscendingSubMap"和"(降序)DescendingSubMap"而服务;因为NavigableSubMap实现了许多公共API。它的最终目的是实现下面的一系列函数:

复制代码 headMap(K toKey, boolean inclusive) headMap(K toKey) subMap(K fromKey, K toKey) subMap(K fromKey, boolean fromInclusive, K toKey, boolean toInclusive) tailMap(K fromKey) tailMap(K fromKey, boolean inclusive) navigableKeySet() descendingKeySet() 复制代码

第3.10部分 TreeMap其它函数

1 顺序遍历和逆序遍历

TreeMap的顺序遍历和逆序遍历原理非常简单。 由于TreeMap中的元素是从小到大的顺序排列的。因此,顺序遍历,就是从第一个元素开始,逐个向后遍历;而倒序遍历则恰恰相反,它是从最后一个元素开始,逐个往前遍历。

我们可以通过 keyIterator() 和 descendingKeyIterator()来说明! keyIterator()的作用是返回顺序的KEY的集合, descendingKeyIterator()的作用是返回逆序的KEY的集合。

keyIterator() 的代码如下:

Iterator keyIterator() { return new KeyIterator(getFirstEntry()); } 说明:从中我们可以看出keyIterator() 是返回以“第一个节点(getFirstEntry)” 为其实元素的迭代器。 KeyIterator的代码如下:

复制代码 final class KeyIterator extends PrivateEntryIterator { KeyIterator(Entry<K,V> first) { super(first); } public K next() { return nextEntry().key; } } 复制代码 说明:KeyIterator继承于PrivateEntryIterator。当我们通过next()不断获取下一个元素的时候,就是执行的顺序遍历了。

descendingKeyIterator()的代码如下:

Iterator descendingKeyIterator() { return new DescendingKeyIterator(getLastEntry()); } 说明:从中我们可以看出descendingKeyIterator() 是返回以“最后一个节点(getLastEntry)” 为其实元素的迭代器。 再看看DescendingKeyIterator的代码:

复制代码 final class DescendingKeyIterator extends PrivateEntryIterator { DescendingKeyIterator(Entry<K,V> first) { super(first); } public K next() { return prevEntry().key; } } 复制代码 说明:DescendingKeyIterator继承于PrivateEntryIterator。当我们通过next()不断获取下一个元素的时候,实际上调用的是prevEntry()获取的上一个节点,这样它实际上执行的是逆序遍历了。

至此,TreeMap的相关内容就全部介绍完毕了。若有错误或纰漏的地方,欢迎指正!

第4部分 TreeMap遍历方式 4.1 遍历TreeMap的键值对

第一步:根据entrySet()获取TreeMap的“键值对”的Set集合。 第二步:通过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(); } 复制代码

4.2 遍历TreeMap的键

第一步:根据keySet()获取TreeMap的“键”的Set集合。 第二步:通过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); } 复制代码

4.3 遍历TreeMap的值

第一步:根据value()获取TreeMap的“值”的集合。 第二步:通过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(); } 复制代码 TreeMap遍历测试程序如下:

View Code

第5部分 TreeMap示例 下面通过实例来学习如何使用TreeMap

View Code 运行结果:

复制代码 {one=8, three=4, two=2} next : one - 8 next : three - 4 next : two - 2 size: 3 contains key two : true contains key five : false contains value 0 : false tmap:{one=8, two=2} tmap is empty 复制代码