JAVA學習-TreeMap詳解

1.定義

TreeMap是基于紅黑樹的實現(xiàn),也是記錄了key-value的映射關(guān)系,該映射根據(jù)key的自然排序進行排序或者根據(jù)構(gòu)造方法中傳入的比較器進行排序,也就是說TreeMap是有序的key-value集合。
通過TreeMap的定義可以看出以下幾點:

1.TreeMap的內(nèi)部實現(xiàn)是紅黑樹實現(xiàn)的,關(guān)于紅黑樹的內(nèi)容可以參考JAVA學習-紅黑樹詳解

2.TreeMap是有序的key-value集合。

下面就通過源碼去了解下TreeMap的內(nèi)部實現(xiàn),本文源碼來自與JDK_1.8.0_131

2.結(jié)構(gòu)

2.1 類圖結(jié)構(gòu)

TreeMap

如上圖所示是TreeMap的類圖結(jié)構(gòu),其繼承、實現(xiàn)的接口如下所示:

  • 1.Map 接口: 定義將鍵值映射到值的對象,Map規(guī)定不能包含重復的鍵值,每個鍵最多可以映射一個值,這個接口是用來替換Dictionary類。
  • 2.AbstractMap 類: 提供了一個Map骨架的實現(xiàn),盡量減少了實現(xiàn)Map接口所需要的工作量
  • 3.SortedMap 接口: 定義按照key排序的Map結(jié)構(gòu),規(guī)定key-value是根據(jù)鍵key值的自然排序進行排序的,或者根據(jù)構(gòu)造key-value時設定的構(gòu)造器進行排序。
  • 4.NavigableMap 接口: 是SortedMap接口的子接口,在其基礎上擴展了針對搜索目標返回最近匹配項的導航方法,例如方法lowEntry、floorEntry、ceilingEntry等,如果不存在這樣的鍵,則返回null
  • 5.Cloneable 接口: 實現(xiàn)了該接口的類可以顯示的調(diào)用Object.clone()方法,合法的對該類實例進行字段復制,如果沒有實現(xiàn)Cloneable接口的實例上調(diào)用Obejct.clone()方法,會拋出CloneNotSupportException異常。正常情況下,實現(xiàn)了Cloneable接口的類會以公共方法重寫Object.clone()
  • 6.Serializable 接口: 實現(xiàn)了該接口標示了類可以被序列化和反序列化,具體的 查詢序列化詳解

2.2 構(gòu)造方法及基本屬性

2.2.1 構(gòu)造方法

如下TreeMap提供了如下的構(gòu)造方法:

  • public TreeMap() :空的構(gòu)造方法,默認容量為0
  • public TreeMap(Comparator<? super K> comparator):需要一個Comparator比較器作為參數(shù),其實這一點很好理解因為TreeMap是一個有序的key-value集合,所以可以自定義比較器進行設定元素的排序規(guī)則
  • public TreeMap(Map<? extends K, ? extends V> m):將一個Map構(gòu)建成一個TreeMap
  • public TreeMap(SortedMap<K, ? extends V> m):將一個SortedMap構(gòu)建成一個TreeMap
2.2.2 基本屬性

如下代碼所示,是TreeMap的基本屬性,其實TreeMap的屬性相對來說比較簡單如下所示,如下也有Entry節(jié)點的源碼。

//比較器
private final Comparator<? super K> comparator;
// Entry節(jié)點,這個表示紅黑樹的根節(jié)點
private transient Entry<K,V> root;
// TreeMap中元素的個數(shù)
private transient int size = 0;
// TreeMap修改次數(shù)
private transient int modCount = 0;

static final class Entry<K,V> implements Map.Entry<K,V> {
    K key;//對于key值
    V value;//對于value值
    Entry<K,V> left;//指向左子樹的引用
    Entry<K,V> right;//指向右子樹的引用
    Entry<K,V> parent;//指向父節(jié)點的引用
    boolean color = BLACK;//節(jié)點的顏色默認是黑色
    
    // 省略部分代碼
}

如下圖所示,是TreeMap(key:為[4,2,5,6,8,7,9])的一個內(nèi)部結(jié)構(gòu)示意圖,其中每個節(jié)點都是Entry類型的


TreeMap

3.實現(xiàn)

下面會詳細介紹TreeMap中的put(K key,V value)方法及get(Object key)方法

3.1 put方法

public V put(K key, V value) {
        Entry<K,V> t = root;
        //根節(jié)點為空直接新增節(jié)點,否則繼續(xù)下面的流程
        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;
        //有特殊指定的比較器則使用比較器進行比較,否則使用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);
        }
        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;
    }

如上所示為put方法的源碼,可能看起來相對來說比較長,但是我們來詳細分析下如以下步驟

  • 1.校驗根節(jié)點:校驗根節(jié)點是否為空,若為空則根據(jù)傳入的key-value的值創(chuàng)建一個新的節(jié)點,若根節(jié)點不為空則繼續(xù)第二步
  • 2.尋找插入位置:其實這點很好理解,由于TreeMap內(nèi)部是紅黑樹實現(xiàn)的則插入元素時,實際上是會去遍歷左子樹,或者右子樹(具體遍歷哪顆子樹是根據(jù)當前插入key-value與根節(jié)點的比較判定的,這部在代碼里面其實分為兩步來體現(xiàn)是否指定比較器,若指定了則使用指定的比較器比較,否則使用默認key的比較器進行比較(這里有一點需要注意是TreeMap是不允許key-value為NULL)
  • 3.新建并恢復:在第二步中實際上是需要確定當前插入節(jié)點的位置,而這一步是實際的插入操作,而插入之后為啥還需要調(diào)用fixAfterInsertion方法,這里是因為紅黑樹插入一個節(jié)點后可能會破壞紅黑樹的性質(zhì),則通過修改的代碼使得紅黑樹從新達到平衡,具體可以參考JAVA學習-紅黑樹詳解

其實從上面的分析來講TreeMap插入一個節(jié)點的流程不是太復雜,只要深刻的理解紅黑樹的原理,這個流程就相對簡單很多,其實這里本人存在一點疑問(為什么要引入紅黑樹來做TreeMap的結(jié)構(gòu),需要看到本文的大神指導指導),

3.2 get方法

如下所示是get方法的源碼,其實際是調(diào)用了getEntry方法

public V get(Object key) {
    //獲取元素,若為空則返回null否則返回其值
    Entry<K,V> p = getEntry(key);
    return (p==null ? null : p.value);
}

final Entry<K,V> getEntry(Object key) {
        // Offload comparator-based version for sake of performance
    if (comparator != null)
    //構(gòu)造器不為空則調(diào)用以下方法
        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;
    }

getEntry方法主要流程如下:

  • 1.構(gòu)造器校驗:判斷是否指定構(gòu)造器,若指定則調(diào)用getEntryUsingComparator,若沒有則進行第二步
  • 2.空值校驗:key若為空直接拋出NullPointerException,從這點可以看出TreeMap是不允許Key-value為空的
  • 3.遍歷返回:遍歷整個紅黑樹若找到對應的值則返回,否則返回null值
    以上就是getEntry方法的主要流程,在步驟一中提到了getEntryUsingComparator方法,其實該方法與步驟三中的操作并無太大差異,存在的區(qū)別就是使用了構(gòu)造器進行比較。

3.總結(jié)

本文主要介紹了TreeMap的實現(xiàn)及其特點,還有一些方法及代碼沒有詳解介紹,若想深入了解可以查看源碼做更深入的探究。若有問題,請指正。

  • 1.TreeMap的內(nèi)部實現(xiàn)是紅黑樹,關(guān)于紅黑樹的詳解請查看JAVA學習-紅黑樹詳解
  • 2.TreeMap特點有些是源于紅黑樹的一些特點,比如TreeMap是有序的,TreeMap不允許key-value為空等
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 228,345評論 6 531
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 98,494評論 3 416
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 176,283評論 0 374
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經(jīng)常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 62,953評論 1 309
  • 正文 為了忘掉前任,我火速辦了婚禮,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,714評論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 55,186評論 1 324
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,255評論 3 441
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 42,410評論 0 288
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 48,940評論 1 335
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 40,776評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 42,976評論 1 369
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,518評論 5 359
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 44,210評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,642評論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,878評論 1 286
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,654評論 3 391
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 47,958評論 2 373

推薦閱讀更多精彩內(nèi)容