關於java集合類TreeMap的理解(轉)


概要

這一章,我們對TreeMap進行學習。

轉載請注明出處:http://www.cnblogs.com/skywang12345/admin/EditPosts.aspx?postid=3310928

 

第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<K>            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<K>                     keySet()
Entry<K, V>                lastEntry()
K                          lastKey()
Entry<K, V>                lowerEntry(K key)
K                          lowerKey(K key)
NavigableSet<K>            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<V> values() {
    Collection<V> 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<V> iterator() {
    return new ValueIterator(getFirstEntry());
}

說明: iterator() 是通過ValueIterator() 返回迭代器的,ValueIterator是一個類。代碼如下:

復制代碼
final class ValueIterator extends PrivateEntryIterator<V> {
    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<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的代碼:

復制代碼
final class DescendingKeyIterator extends PrivateEntryIterator<K> {
    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
復制代碼

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM