TreeMap原理(淺談)

一. 回顧

前面瞭解了LinkedHashMap原理(淺談),今天瞭解一下TreeMap。

二. 儲備知識

在瞭解TreeMap前,瞭解以下兩個知識比較容易理解:Comparable和Comparator;一致性hash

2.1 Comparable和Comparator

詳情見Comparable和Comparator的知識點以及兩者的區別

2.2 一致性hash

參考MemCache詳細解讀

三. TreeMap

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

可以看到TreeMap是繼承AbstractMap,不像LinkedHashMap繼承HashMap。而HashMap也是繼承AbstractMap。


點進去看看NavigableMap,如下:

public interface NavigableMap<K,V> extends SortedMap<K,V> 

NavigableMap繼承SortedMap,是對SortedMap的擴展,提供很多導航方法。(分析Comparable時,瞭解到了Comparable被運用於SortMap、SortSet中)


繼續點進去看SortedMap,如下:

public interface SortedMap<K,V> extends Map<K,V> {

    /**
     * Returns the comparator used to order the keys in this map, or
     * {@code null} if this map uses the {@linkplain Comparable
     * natural ordering} of its keys.
     *
     * @return the comparator used to order the keys in this map,
     *         or {@code null} if this map uses the natural ordering
     *         of its keys
     */
    Comparator<? super K> comparator();

它繼承Map。並且可以看到它有一個 成員方法方法是Comparator<? super K> comparator()。這就是用到Comparator的地方(如果沒有Comparator,可以實現Comparable。後面詳細講述)。


SortedMap中還有兩個很重要的方法,如下:
在這裏插入圖片描述


在TreeMap的implements notes註釋中有一段,如下:

 * A Red-Black tree based {@link NavigableMap} implementation.
 * The map is sorted according to the {@linkplain Comparable natural
 * ordering} of its keys, or by a {@link Comparator} provided at map
 * creation time, depending on which constructor is used.

大概意思:這是基於紅黑樹實現的。TreeMap根據key的Comparable或者構造TreeMap時提供的Comparator進行排序。

總結:TreeMap的key對象必須實現Comparable接口或者構造TreeMap時提供一個Comparator

爲了證明確實如此,作以下測試:

Person.java

package com.atguigu;

public class Person {
    private String name;
    private Integer age;

    public Person() {
    }

    public Person(String name, Integer age) {
        this.name = name;
        this.age = age;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public Integer getAge() {
        return age;
    }

    public void setAge(Integer age) {
        this.age = age;
    }

    @Override
    public String toString() {
        return "Person{" +
                "name='" + name + '\'' +
                ", age=" + age +
                '}';
    }
}

  @Test
    public void test7() {
        TreeMap<Person, Integer> treemap = new TreeMap<>();
        treemap.put(new Person("tianqi", 7), 7);
        treemap.put(new Person("wangwu", 5), 5);
        treemap.put(new Person("zhaoliu", 6), 6);
        treemap.put(new Person("zhangsan", 3), 3);
        System.out.println(treemap);
    }

測試結果:
在這裏插入圖片描述
源碼中的成員變量comparator也證實了要如此,如下:

/**
     * The comparator used to maintain order in this tree map, or
     * null if it uses the natural ordering of its keys.
     *
     * @serial
     */
    private final Comparator<? super K> comparator;

註釋的大概意思:comparator用來保持一個tree map中的排序。如果tree map的key有自然排序(即key實現了Comparable接口),那麼compartor可以爲null。

3.1 成員變量

comparator: comparator用來保持一個tree map中的排序。如果tree map的key有自然排序(即key實現了Comparable接口),那麼compartor可以爲null。

/**
     * The comparator used to maintain order in this tree map, or
     * null if it uses the natural ordering of its keys.
     *
     * @serial
     */
    private final Comparator<? super K> comparator;

root: 根節點,其類型是TreeMap的靜態內部類,後面會介紹到

private transient Entry<K,V> root;

size: tree中鍵值對的個數

/**
     * The number of entries in the tree
     */
    private transient int size = 0;

modCount : 修改次數(因爲TreeMap是非同步的,即線程不安全的)

 /*Note that this implementation is not synchronized.*/

/**
     * The number of structural modifications to the tree.
     */
    private transient int modCount = 0;

entrySet、navigableKeySet、 descendingMap: 關於鍵值對的集合

 /**
     * Fields initialized to contain an instance of the entry set view
     * the first time this view is requested.  Views are stateless, so
     * there's no reason to create more than one.
     */
    private transient EntrySet entrySet;
    private transient KeySet<K> navigableKeySet;
    private transient NavigableMap<K,V> descendingMap;

3.2 靜態常量

RED 、BLACK : 代表紅黑結點

// Red-black mechanics

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

3.3 構造方法

有四個構造方法:
在這裏插入圖片描述

TreeMap() : 空參構造。註釋中大概意思: 使用key的自然排序構造一個新的、空的tree map。tree map中所有的key必須實現Comparable接口。 所有的key能互相比較,否則拋出ClassCastException類型轉換異常,比如tree map中全是Integer類型的key,現put進去一個String類型的key,put方法會拋出ClassCastException類型轉換異常。

/**
     * Constructs a new, empty tree map, using the natural ordering of its
     * keys.  All keys inserted into the map must implement the {@link
     * Comparable} interface.  Furthermore, all such keys must be
     * <em>mutually comparable</em>: {@code k1.compareTo(k2)} must not throw
     * a {@code ClassCastException} for any keys {@code k1} and
     * {@code k2} in the map.  If the user attempts to put a key into the
     * map that violates this constraint (for example, the user attempts to
     * put a string key into a map whose keys are integers), the
     * {@code put(Object key, Object value)} call will throw a
     * {@code ClassCastException}.
     */
    public TreeMap() {
        comparator = null;
    }

TreeMap(Comparator<? super K> comparator) : 指定comparator構造。若有指定compartor,可以沒有comparable。put進去的鍵值對會根據comparator的排序規則對key進行排序從而進行存儲。

/**
     * Constructs a new, empty tree map, ordered according to the given
     * comparator.  All keys inserted into the map must be <em>mutually
     * comparable</em> by the given comparator: {@code comparator.compare(k1,
     * k2)} must not throw a {@code ClassCastException} for any keys
     * {@code k1} and {@code k2} in the map.  If the user attempts to put
     * a key into the map that violates this constraint, the {@code put(Object
     * key, Object value)} call will throw a
     * {@code ClassCastException}.
     *
     * @param comparator the comparator that will be used to order this map.
     *        If {@code null}, the {@linkplain Comparable natural
     *        ordering} of the keys will be used.
     */
    public TreeMap(Comparator<? super K> comparator) {
        this.comparator = comparator;
    }

TreeMap(Map<? extends K, ? extends V> m): 入參爲map,將map轉爲treemap。沒有提供comparator,則默認爲null,則使用key的自然排序(即key要實現Comparable接口)

 /**
     * Constructs a new tree map containing the same mappings as the given
     * map, ordered according to the <em>natural ordering</em> of its keys.
     * All keys inserted into the new map must implement the {@link
     * Comparable} interface.  Furthermore, all such keys must be
     * <em>mutually comparable</em>: {@code k1.compareTo(k2)} must not throw
     * a {@code ClassCastException} for any keys {@code k1} and
     * {@code k2} in the map.  This method runs in n*log(n) time.
     *
     * @param  m the map whose mappings are to be placed in this map
     * @throws ClassCastException if the keys in m are not {@link Comparable},
     *         or are not mutually comparable
     * @throws NullPointerException if the specified map is null
     */
    public TreeMap(Map<? extends K, ? extends V> m) {
        comparator = null;
        putAll(m);
    }

TreeMap(SortedMap<K, ? extends V> m) : 入參爲SortedMap,將SortedMap轉爲treemap。使用SortedMap的comparator。

 /**
     * Constructs a new tree map containing the same mappings and
     * using the same ordering as the specified sorted map.  This
     * method runs in linear time.
     *
     * @param  m the sorted map whose mappings are to be placed in this map,
     *         and whose comparator is to be used to sort this map
     * @throws NullPointerException if the specified map is null
     */
    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) {
        }
    }

總結:從上面四個構造方法瞭解到,如果不提供comparator,那麼key就必須實現Comparable接口(即使用自然排序)。不瞭解這個自然排序的可以到Comparable和Comparator的知識點以及兩者的區別瞭解

例子:

Person.java

package com.atguigu;

public class Person {
    private String name;
    private Integer age;

    public Person() {
    }

    public Person(String name, Integer age) {
        this.name = name;
        this.age = age;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public Integer getAge() {
        return age;
    }

    public void setAge(Integer age) {
        this.age = age;
    }

    @Override
    public String toString() {
        return "Person{" +
                "name='" + name + '\'' +
                ", age=" + age +
                '}';
    }
}

Main.java

  @Test
    public void test7() {
        TreeMap<Person, Integer> treemap = new TreeMap<>();
        treemap.put(new Person("tianqi", 7), 7);
        treemap.put(new Person("wangwu", 5), 5);
        treemap.put(new Person("zhaoliu", 6), 6);
        treemap.put(new Person("zhangsan", 3), 3);
        System.out.println(treemap);
    }

測試結果:
在這裏插入圖片描述

到這裏,提了關於要實現Comparable或者Comparator超過2次了。那爲什麼要實現這個東西?我們還是從源碼去分析,不能單憑猜測,得用事實說話。

3.4 靜態內部類

Entry<K,V>: 其構造方法是有一個入參parent,是父節點的意思

/**
     * Node in the Tree.  Doubles as a means to pass key-value pairs back to
     * user (see Map.Entry).
     */

    static final class Entry<K,V> implements Map.Entry<K,V> {
        K key;
        V value;
        Entry<K,V> left;
        Entry<K,V> right;
        Entry<K,V> parent;
        boolean color = BLACK;

        /**
         * Make a new cell with given key, value, and parent, and with
         * {@code null} child links, and BLACK color.
         */
        Entry(K key, V value, Entry<K,V> parent) {
            this.key = key;
            this.value = value;
            this.parent = parent;
        }

3.5 爲什麼要實現Comparable或者提供Comparator呢?

上面的報錯信息來自tree map實例將元素put進去的時候,那麼能說明是在調用put方法的時候報錯了。我們點進去put方法看看。如下:

方法很長,我們一步步分析,首先看一下注釋,大概意思是:將確定的值(即入參value)與確定的鍵(即入參key)關聯起來(就是存儲鍵值對的意思)。如果map中已經存在put進去的key,那麼舊值會被新值覆蓋。

再看到@throws給出的註釋,大概意思是:如果入參key是null的,並且 這個map使用自然排序或者map的comparator不允許null的key,會拋出NullPointerException空指針異常。也就是說comparator可以允許null的key進行排序,comparable不允許null的key進行排序(詳細原因見Comparable和Comparator的知識點以及兩者的區別)。

/**
     * Associates the specified value with the specified key in this map.
     * If the map previously contained a mapping for the key, the old
     * value is replaced.
     *
     * @param key key with which the specified value is to be associated
     * @param value value to be associated with the specified key
     *
     * @return the previous value associated with {@code key}, or
     *         {@code null} if there was no mapping for {@code key}.
     *         (A {@code null} return can also indicate that the map
     *         previously associated {@code null} with {@code key}.)
     * @throws ClassCastException if the specified key cannot be compared
     *         with the keys currently in the map
     * @throws NullPointerException if the specified key is null
     *         and this map uses natural ordering, or its comparator
     *         does not permit null keys
     */
    public V put(K key, V value) {
      
      ...
    }

使用上面的例子進行源碼分析:
在這裏插入圖片描述
構造完就進行put元素,所以去看put方法是怎麼實現的,如下:
在這裏插入圖片描述

調用compare()方法,如下:

在這裏插入圖片描述
假如構造的時候提供了comparator,那麼三元運算符將會進行冒號後面的語句,如下:
在這裏插入圖片描述
總結:綜上所述,這就爲什麼treemap的key要實現Comparable。因爲剛開始構造treemap,裏面無節點,所以無根節點,所以第一個put進去的元素是會作爲根節點。也就在put進去時進行了key類型檢查。key若沒有實現Comparable會報錯

繼續看put方法,如下:
在這裏插入圖片描述
假如沒有報錯,put順利完成,則會put進第二個元素,如下:

在這裏插入圖片描述
進入put()方法,如下:
在這裏插入圖片描述

在這裏插入圖片描述

四. 例子

4.1 需求:實現Comparable接口,使用treemap存值

User.java

package com.atguigu;

public class User implements Comparable<User>{
    private String name;
    private Integer age;

    public User() {
    }

    public User(String name, Integer age) {
        this.name = name;
        this.age = age;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public Integer getAge() {
        return age;
    }

    public void setAge(Integer age) {
        this.age = age;
    }

    @Override
    public String toString() {
        return "User{" +
                "name='" + name + '\'' +
                ", age=" + age +
                '}';
    }

    @Override
    public int compareTo(User o) {
        return (o.age-this.age);//按年齡的倒敘排
    }
}

Main.java

  @Test
    public void test7() {
        TreeMap<User, Integer> treemap = new TreeMap<>();
        treemap.put(new User("tianqi", 7), 7);
        treemap.put(new User("wangwu", 5), 5);
        treemap.put(new User("zhaoliu", 6), 6);
        treemap.put(new User("zhangsan", 3), 3);
        System.out.println(treemap);
    }

測試結果:存進去就是按照排序規則存的,排序規則是降序
在這裏插入圖片描述

4.2 需求:提供Comparator,使用treemap存值

Main.java

 @Test
    public void test8() {
        TreeMap<User, Integer> treemap = new TreeMap<>(new Comparator<User>() {
            @Override
            public int compare(User o1, User o2) {
                if(o1.getAge() != null && o2.getAge() != null) {
                    return o1.getAge()-o2.getAge();//按年齡升序排序
                }else{
                    return o1.getAge()==null? 1: -1;//處理age爲null的鍵值對,放在map的後面
                }
            }
        });
        treemap.put(new User("tianqi", 7), 7);
        treemap.put(new User("wangwu", null), null);
        treemap.put(new User("zhaoliu", 6), 6);
        treemap.put(new User("zhangsan", null), null);
        System.out.println(treemap);
    }

在這裏插入圖片描述
測試結果:Comparable是降序排序,Comparator是升序,測試結果是升序,說明Comparator比Comparable的優先級高
在這裏插入圖片描述

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章