Java集合(六)--TreeMap簡析

本篇分析TreeMap。

TreeMap的定義及說明

定義如下:

public class TreeMap<K,V>
    extends AbstractMap<K,V>
    implements NavigableMap<K,V>, Cloneable, java.io.Serializable{}p

1、繼承了AbstractMap及實現(xiàn)了NavigableMap接口。我們從集合框架那一篇文章中已經(jīng)知道NavigableMap是一個擴展了SortedMap接口的接口。而SortedMap接口主要提供有序的Map實現(xiàn)。所以TreeMap是一個有序的Map。

2、實現(xiàn)了Cloneable,即支持clone。

3、實現(xiàn)了java.io.Serializable,即支持序列化和反序列化。

TreeMap是基于紅黑樹的實現(xiàn)的。根據(jù)其鍵的可比較的自然順序,或者創(chuàng)建時提供的Comparator進行排序,具體取決于使用的構造函數(shù)。

接下來看構造方法:

//比較器,用于維護TreeMap中映射的順序,如果它使用其鍵的自然順序,則為null。
private final Comparator<? super K> comparator;

//構造一個根據(jù)鍵的自然順序排序的TreeMap
public TreeMap() {
        //比較器為null
        comparator = null;
    }
    
//構造空的,給定比較器的TreeMap
public TreeMap(Comparator<? super K> comparator) {
        //使用指定的比較器
        this.comparator = comparator;
    }

//構造一個與給定映射相同的TreeMap,根據(jù)其鍵的自然順序進行排序
public TreeMap(Map<? extends K, ? extends V> m) {
        comparator = null;
        //將指定映射中的所有映射復制到此TreeMap。這些映射將替換TreeMap中對當前位于指定映射中的任何鍵的任何映射
        putAll(m);
    }

//構造一個與m具有相同元素和元素順序的TreeMap
public TreeMap(SortedMap<K, ? extends V> m) {
        //返回用于對m中的鍵進行排序的比較器
        comparator = m.comparator();
        try {
           
             //使用線性時間算法來自排序數(shù)據(jù)
             buildFromSorted(m.size(),m.entrySet().iterator(), null, null);
        } catch (java.io.IOException cannotHappen) {
        } catch (ClassNotFoundException cannotHappen) {
        }
    }

從構造函數(shù)中我們知道,如果我們沒有指定比較器,則使用鍵的自然順序排序。

TreeMap源碼簡析

按照慣例,去看給TreeMap賦值的put()方法:

//比較器
private final Comparator<? super K> comparator;

//紅黑樹的根節(jié)點
private transient TreeMapEntry<K,V> root;

//TreeMap的大小
private transient int size = 0;

//TreeMap修改次數(shù)
private transient int modCount = 0;

public V put(K key, V value) {
        //將根節(jié)點的值賦予t
        TreeMapEntry<K,V> t = root;
        //判斷根節(jié)點是否為null
        if (t == null) {
            //根節(jié)點為null,判斷比較器是否為null
            if (comparator != null) {
                //比較器為null,判斷key是否為null
                if (key == null) {
                    //檢查輸入null值的副作用
                    comparator.compare(key, key);
                }
            } else {
                //如果比較器不為null,判斷key是否為null
                if (key == null) {
                    //key為null,拋出異常
                    throw new NullPointerException("key == null");
                } else if (!(key instanceof Comparable)) {
                //comparator為null,key不為null,但是key對象沒有實現(xiàn)Comparable接口,則拋出異常
                    throw new ClassCastException(
                            "Cannot cast" + key.getClass().getName() + " to Comparable.");
                }
            }
            //將此元素作為根節(jié)點
            root = new TreeMapEntry<>(key, value, null);
            
            size = 1;
            modCount++;
            return null;
        }
        
        //如果存在根節(jié)點,執(zhí)行以下操作
        //key與某個節(jié)點的key所比較的值
        int cmp;
        //與key相比較的某個節(jié)點
        TreeMapEntry<K,V> parent;
        //獲取比較器將其賦值與cpr 
        Comparator<? super K> cpr = comparator;
        //判斷比較器是否為null
        if (cpr != null) {
            //比較器不為null,執(zhí)行循環(huán)
            do {
                //將t的值賦予parent(第一次循環(huán)為root)
                parent = t;
                //使用比較器比較要插入的key跟當前節(jié)點的key
                cmp = cpr.compare(key, t.key);
                //如果key小則將t的值置為當前節(jié)點左孩子的值
                if (cmp < 0)
                    t = t.left;
                //如果key大則將t的值置為當前節(jié)點右孩子的值
                else if (cmp > 0)
                    t = t.right;
                else
                    //如果key值相同,則覆蓋當前節(jié)點的值
                    return t.setValue(value);
            } while (t != null);
        }
        else {
            //如果比較器為null,判斷key是否為null
            if (key == null)
                //key為null,則拋出異常
                throw new NullPointerException();
            @SuppressWarnings("unchecked")
            //此處注意,key需為實現(xiàn)了Comparable的對象,不然會拋出ClassCastException。
                Comparable<? super K> k = (Comparable<? super K>) key;
            //執(zhí)行循環(huán)    
            do {
                //將t的值賦予parent(第一次循環(huán)為root)
                parent = t;
                //將key與當前節(jié)點的key做比較
                cmp = k.compareTo(t.key);
                if (cmp < 0)
                //如果key小則將t的值置為當前節(jié)點左孩子的值
                    t = t.left;
                else if (cmp > 0)
                //如果key大則將t的值置為當前節(jié)點右孩子的值
                    t = t.right;
                else
                //如果key值相同,則覆蓋當前節(jié)點的值
                    return t.setValue(value);
            } while (t != null);
        }
        //設置一個新的節(jié)點
        TreeMapEntry<K,V> e = new TreeMapEntry<>(key, value, parent);
        //如果key比當前節(jié)點的key值小
        if (cmp < 0)
            //將新元素作為當前節(jié)點的左孩子
            parent.left = e;
        else
            //否則,將新元素作為當前節(jié)點的右孩子
            parent.right = e;
        //插入修正,以保證插入新元素后還是紅黑樹結構    
        fixAfterInsertion(e);
        size++;
        modCount++;
        return null;
    }

private static final boolean RED   = false;
private static final boolean BLACK = true;

//紅黑樹結構
static final class TreeMapEntry<K,V> implements Map.Entry<K,V> {
       
        //key值
        K key;
        
        //value值
        V value;
        
        //左孩子
        TreeMapEntry<K,V> left;
        
        //右孩子
        TreeMapEntry<K,V> right;
        
        //父節(jié)點
        TreeMapEntry<K,V> parent;
        
        //節(jié)點顏色
        boolean color = BLACK;

        //初始化
        TreeMapEntry(K key, V value, TreeMapEntry<K,V> parent) {
            this.key = key;
            this.value = value;
            this.parent = parent;
        }

        //其他方法
        ..........
    }

通過上面的分析,我們發(fā)現(xiàn)TreeMap其實是通過操作TreeMapEntry類型的root屬性,以實現(xiàn)對數(shù)據(jù)的插入。也就是說,對TreeMap的操作其實是通過對紅黑樹的操作來完成的。

接下來,我們再分析一些其他的方法:

對TreeMapEntry的操作

對TreeMap的操作的方法有firstEntry()、lastEntry()、pollFirstEntry()、pollLastEntry()、lowerEntry()、floorEntry()、ceilingEntry()、higherEntry()。它們的都是通過對樹的遍歷找到并操作指定的節(jié)點。比如firstEntry():

//獲取第一個節(jié)點的值
public Map.Entry<K,V> firstEntry() {
        return exportEntry(getFirstEntry());
    }

//獲取第一個節(jié)點的值     
final TreeMapEntry<K,V> getFirstEntry() {
        //遍歷樹的左節(jié)點,獲取第一個節(jié)點的值
        TreeMapEntry<K,V> p = root;
        if (p != null)
            while (p.left != null)
                p = p.left;
        return p;
    }

//返回一個AbstractMap.SimpleImmutableEntry
static <K,V> Map.Entry<K,V> exportEntry(TreeMapEntry<K,V> e) {
        return (e == null) ? null :
            new AbstractMap.SimpleImmutableEntry<>(e);
    }

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

在上面的分析中,我們發(fā)現(xiàn)一個陌生的類,AbstractMap.SimpleImmutableEntry。這個類是為了使鍵和值不可變,該類不支持setValue()方法。這么做是為了防止我們修改返回的Entry。

對key操作的相關方法

對key操作的方法有firstKey()、lastKey()、lowerKey()、floorKey()、ceilingKey()、higherKey(),我們看一下firstKey()方法:

public K firstKey() {
        return key(getFirstEntry());
    }
    
 static <K> K key(TreeMapEntry<K,?> e) {
        if (e==null)
            throw new NoSuchElementException();
        return e.key;
    }   

即通過獲取節(jié)點的TreeMapEntry對象來獲取其對應的key。

對value操作的相關方法

對value操作的方法有containsValue()、get()、values()等,我們看一下get()方法:

 public V get(Object key) {
        //獲取擁有指定的key的節(jié)點
        TreeMapEntry<K,V> p = getEntry(key); 
         //返回值
        return (p==null ? null : p.value);
    }
    
final TreeMapEntry<K,V> getEntry(Object key) {
        //判斷比較器是否為空
        if (comparator != null)
            return getEntryUsingComparator(key);
        //判斷key是否為null    
        if (key == null)
            throw new NullPointerException();
        @SuppressWarnings("unchecked")
        //強制轉換獲取key的比較器
            Comparable<? super K> k = (Comparable<? super K>) key;
        //將root的值賦予t    
        TreeMapEntry<K,V> p = root;
        //遍歷樹并獲取擁有指定key的節(jié)點,沒有返回null
        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;
    }    

我們可以看到,其實還是通過對樹的遍歷獲取指定的節(jié)點,再獲取指定的值。

與樹相關的操作

與操作樹相關的方法有左旋(rotateLeft())、右旋(rotateRight())、插入修正(fixAfterInsertion())、刪除修正(fixAfterDeletion())等等。我們看一下左旋:

private void rotateLeft(TreeMapEntry<K,V> p) {
        if (p != null) {
            TreeMapEntry<K,V> r = p.right;
            p.right = r.left;
            if (r.left != null)
                r.left.parent = p;
            r.parent = p.parent;
            if (p.parent == null)
                root = r;
            else if (p.parent.left == p)
                p.parent.left = r;
            else
                p.parent.right = r;
            r.left = p;
            p.parent = r;
        }
    }

好了,本篇文章就分析到這,后期看看是否有補充。

最后編輯于
?著作權歸作者所有,轉載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

相關閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容