Java集合(十) —— TreeMap源碼分析

Java集合(一) —— Collection源碼分析
Java集合(二) —— ArrayList源碼分析
Java集合(三) —— LinkedList源碼分析
Java集合(四) —— PriorityQueue源碼分析
Java集合(五) —— HashSet源碼分析
Java集合(六) —— LinkedHashSet源碼分析
Java集合(七) —— TreeSet源碼分析
Java集合(八) —— HashMap源碼分析
Java集合(九) —— LinkedHashMap源碼分析
Java集合(十) —— TreeMap源碼分析

1.總結(jié)

1.TreeMap數(shù)據(jù)結(jié)構(gòu):紅黑樹
2.有序的key-value映射颤芬,key必須可以自然排序的站蝠,否則就要自定義比較器Comparator作為參數(shù)傳入
3.默認(rèn)key不可以為null卓鹿,因?yàn)橐ㄟ^key排序

2.繼承關(guān)系圖

TreeMap.png

3.源碼分析

3.1成員變量分析

// key的比較器
private final Comparator<? super K> comparator;
// 紅黑樹的根節(jié)點(diǎn)
private transient Entry<K,V> root;
// treeMap的大小
private transient int size = 0;
// 樹修改的次數(shù)
private transient int modCount = 0;
// 鍵值對(duì)集合
private transient EntrySet entrySet;
// 鍵集合
private transient KeySet<K> navigableKeySet;
// 降序的NavigableMap
private transient NavigableMap<K,V> descendingMap;

3.2構(gòu)造方法分析

public TreeMap() {
    comparator = null;
}

/**
 * 自定義比較器comparator
 */
public TreeMap(Comparator<? super K> comparator) {
    this.comparator = comparator;
}

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

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

沒啥好深究的澜倦,一般都是用默認(rèn)的構(gòu)造方法

3.3常用方法分析

1.put方法

public V put(K key, V value) {
    // 將根節(jié)點(diǎn)賦值給t
    Entry<K,V> t = root;
    if (t == null) {
        compare(key, key); // 默認(rèn)要通過比較key進(jìn)行排序,所以key不能為null碘勉,否則空指針異常

        // 如果根節(jié)點(diǎn)為null桩卵,則新增元素節(jié)點(diǎn)就是根節(jié)點(diǎn)
        // 此時(shí)不用調(diào)整紅黑樹雏节,新增結(jié)束
        root = new Entry<>(key, value, null);
        size = 1;
        modCount++;
        return null;
    }
    int cmp;
    Entry<K,V> parent;
    // 使用自定義比較器進(jìn)行比較
    Comparator<? super K> cpr = comparator;
    if (cpr != null) {
        do {
            // 根節(jié)點(diǎn)作為父節(jié)點(diǎn)
            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);
            // k < t.key
            if (cmp < 0)
                t = t.left;
            // k > t.key
            else if (cmp > 0)
                t = t.right;
            // k = t.key钩乍,由于key不能重復(fù),所以這里更新節(jié)點(diǎn)數(shù)據(jù)
            else
                return t.setValue(value);
        } while (t != null); // 直到找到的子節(jié)點(diǎn)為null孙技,結(jié)束循環(huán)
    }
    // 創(chuàng)建一個(gè)新的節(jié)點(diǎn)
    Entry<K,V> e = new Entry<>(key, value, parent);
    // 根據(jù)比較結(jié)果判斷新增節(jié)點(diǎn)應(yīng)該是左孩子還是右孩子
    if (cmp < 0)
        parent.left = e;
    else
        parent.right = e;
    // 調(diào)整紅黑樹的結(jié)構(gòu)
    fixAfterInsertion(e);
    size++;
    modCount++;
    return null;
}
  • 如果紅黑樹為null牵啦,則之間新建一個(gè)Entry節(jié)點(diǎn)妄痪,并設(shè)置為根節(jié)點(diǎn)root
  • 檢查comparator是否為null衫生,不為null則使用comparator比較key的大小,否則使用k.compareTo(t.key)比較彭羹;遍歷紅黑樹泪酱,找到對(duì)應(yīng)的key墓阀,更新value
  • 如果沒有找到,則新建一個(gè)Entry節(jié)點(diǎn)添加到樹上经伙,然后執(zhí)行fixAfterInsertion(e)勿锅,確保新增節(jié)點(diǎn)后仍是紅黑樹
  • 關(guān)于fixAfterInsertion方法調(diào)整紅黑樹比較復(fù)雜枣氧,這里不再展開作瞄,我會(huì)在數(shù)據(jù)結(jié)構(gòu)中更新紅黑樹的分析
    2.putAll方法
public void putAll(Map<? extends K, ? extends V> map) {
    int mapSize = map.size();
    // 針對(duì)將一個(gè)非空的SortedMap加入空的TreeMap且比較器相同的情況做了優(yōu)化
    if (size==0 && mapSize!=0 && map instanceof SortedMap) {
        Comparator<?> c = ((SortedMap<?,?>)map).comparator();
        if (c == comparator || (c != null && c.equals(comparator))) {
            ++modCount;
            try {
                buildFromSorted(mapSize, map.entrySet().iterator(),
                                null, null);
            } catch (java.io.IOException cannotHappen) {
            } catch (ClassNotFoundException cannotHappen) {
            }
            return;
        }
    }
    // 否則,直接遍歷給定map种蝶,將每一個(gè)節(jié)點(diǎn)添加到TreeMap上
    super.putAll(map); // 會(huì)循環(huán)調(diào)用put方法
}

3.get(key)方法

public V get(Object key) {
    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)
        // 使用指定比較器遍歷紅黑樹
        return getEntryUsingComparator(key);
    // key為null時(shí)瞒大,拋出空指針異常
    if (key == null)
        throw new NullPointerException();
    @SuppressWarnings("unchecked")
    Comparable<? super K> k = (Comparable<? super K>) key;
    Entry<K,V> p = root;
    // 從根節(jié)點(diǎn)開始遍歷紅黑樹
    while (p != null) {
        // 將給定k與樹的節(jié)點(diǎn)的key比較
        int cmp = k.compareTo(p.key);
        if (cmp < 0) // < 0則遍歷左子樹
            p = p.left;
        else if (cmp > 0)
            p = p.right; // > 0則遍歷右子樹
        else
            return p; // 相等表示找到節(jié)點(diǎn)
    }
    return null; // 否則沒有該節(jié)點(diǎn)透敌,返回null
}

4.remove()方法

public V remove(Object key) {
    Entry<K,V> p = getEntry(key); // 根據(jù)key查找節(jié)點(diǎn)
    if (p == null)
        return null;

    V oldValue = p.value;
    // 刪除節(jié)點(diǎn)
    deleteEntry(p);
    return oldValue;
}

/**
 * p:待刪除節(jié)點(diǎn)
 */
private void deleteEntry(Entry<K,V> p) {
    modCount++;
    size--;

    if (p.left != null && p.right != null) { // p有左右節(jié)點(diǎn)
        // 得到p的后繼節(jié)點(diǎn)酗电;得到的是p的右子樹的最左節(jié)點(diǎn)(大于當(dāng)前節(jié)點(diǎn)的最小節(jié)點(diǎn))
        Entry<K,V> s = successor(p);
        // 修改p的key和value,并將p指向s
        p.key = s.key;
        p.value = s.value;
        p = s; // 經(jīng)過上述操作其實(shí)已經(jīng)將p刪除背率,p節(jié)點(diǎn)的內(nèi)容已經(jīng)變?yōu)閟節(jié)點(diǎn)的內(nèi)容
    } // p has 2 children

    // Start fixup at replacement node, if it exists.
    // p == s寝姿,replacement為p(s)的左孩子或右孩子划滋;如果上面進(jìn)入了if代碼塊,這里得到的一定是右孩子
    Entry<K,V> replacement = (p.left != null ? p.left : p.right);

    // 1.具有孩子節(jié)點(diǎn)的情況
    if (replacement != null) {
        // 修改replacement的父節(jié)點(diǎn)指向p的父節(jié)點(diǎn)
        replacement.parent = p.parent;
        if (p.parent == null) // 表示刪除的根節(jié)點(diǎn)
            // 孩子節(jié)點(diǎn)成為根節(jié)點(diǎn)
            root = replacement;
        else if (p == p.parent.left)
            // 只有進(jìn)入上面的if代碼塊才可能進(jìn)入這個(gè)代碼塊
            // 此時(shí)replacement為p的右孩子根资,將p的父節(jié)點(diǎn)的左孩子指向replacement
            p.parent.left  = replacement;
        else
            // 否則嫂冻,p的父節(jié)點(diǎn)的右孩子指向replacement
            p.parent.right = replacement;

        // 將p節(jié)點(diǎn)刪除
        p.left = p.right = p.parent = null;

        if (p.color == BLACK)
            // 如果p的顏色為黑色塞椎,需要調(diào)整紅黑樹的平衡
            fixAfterDeletion(replacement);
    } else if (p.parent == null) { // 刪除的是根節(jié)點(diǎn)且只有一個(gè)節(jié)點(diǎn)
        root = null;
    } else { // 刪除的節(jié)點(diǎn)沒有孩子節(jié)點(diǎn)
        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;
        }
    }
}

TreeMap的紅黑樹結(jié)構(gòu)操作比較復(fù)雜案狠,沒有紅黑樹基礎(chǔ)建議先看看紅黑樹的知識(shí)。
如果不深究的話吹零,知道TreeMap的存儲(chǔ)結(jié)構(gòu)灿椅,并且key不可以為null,默認(rèn)以key的升序排序就足夠了操刀。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末骨坑,一起剝皮案震驚了整個(gè)濱河市柬采,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌礁遣,老刑警劉巖亡脸,帶你破解...
    沈念sama閱讀 212,454評(píng)論 6 493
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件树酪,死亡現(xiàn)場離奇詭異续语,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)滥朱,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,553評(píng)論 3 385
  • 文/潘曉璐 我一進(jìn)店門徙邻,熙熙樓的掌柜王于貴愁眉苦臉地迎上來畸裳,“玉大人,你說我怎么就攤上這事帅容〔⑴牵” “怎么了?”我有些...
    開封第一講書人閱讀 157,921評(píng)論 0 348
  • 文/不壞的土叔 我叫張陵蕴茴,是天一觀的道長荐开。 經(jīng)常有香客問我简肴,道長百侧,這世上最難降的妖魔是什么佣渴? 我笑而不...
    開封第一講書人閱讀 56,648評(píng)論 1 284
  • 正文 為了忘掉前任辛润,我火速辦了婚禮,結(jié)果婚禮上真椿,老公的妹妹穿的比我還像新娘乎澄。我一直安慰自己,他們只是感情好解恰,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,770評(píng)論 6 386
  • 文/花漫 我一把揭開白布护盈。 她就那樣靜靜地躺著羞酗,像睡著了一般。 火紅的嫁衣襯著肌膚如雪围苫。 梳的紋絲不亂的頭發(fā)上撤师,一...
    開封第一講書人閱讀 49,950評(píng)論 1 291
  • 那天剃盾,我揣著相機(jī)與錄音腺占,去河邊找鬼。 笑死痒谴,一個(gè)胖子當(dāng)著我的面吹牛衰伯,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播积蔚,決...
    沈念sama閱讀 39,090評(píng)論 3 410
  • 文/蒼蘭香墨 我猛地睜開眼意鲸,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了尽爆?” 一聲冷哼從身側(cè)響起怎顾,我...
    開封第一講書人閱讀 37,817評(píng)論 0 268
  • 序言:老撾萬榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎漱贱,沒想到半個(gè)月后槐雾,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體幅狮,經(jīng)...
    沈念sama閱讀 44,275評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡募强,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,592評(píng)論 2 327
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了崇摄。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片擎值。...
    茶點(diǎn)故事閱讀 38,724評(píng)論 1 341
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖配猫,靈堂內(nèi)的尸體忽然破棺而出幅恋,到底是詐尸還是另有隱情,我是刑警寧澤泵肄,帶...
    沈念sama閱讀 34,409評(píng)論 4 333
  • 正文 年R本政府宣布捆交,位于F島的核電站,受9級(jí)特大地震影響腐巢,放射性物質(zhì)發(fā)生泄漏品追。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 40,052評(píng)論 3 316
  • 文/蒙蒙 一冯丙、第九天 我趴在偏房一處隱蔽的房頂上張望肉瓦。 院中可真熱鬧遭京,春花似錦、人聲如沸泞莉。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,815評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽鲫趁。三九已至斯嚎,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間挨厚,已是汗流浹背堡僻。 一陣腳步聲響...
    開封第一講書人閱讀 32,043評(píng)論 1 266
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留疫剃,地道東北人钉疫。 一個(gè)月前我還...
    沈念sama閱讀 46,503評(píng)論 2 361
  • 正文 我出身青樓,卻偏偏與公主長得像巢价,于是被迫代替她去往敵國和親牲阁。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,627評(píng)論 2 350

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