1. 程式人生 > >Collection介面和Map介面-02

Collection介面和Map介面-02

Map介面

Map介面並不是Collection介面的子介面,但是它仍然被看作是Collection框架的一部分。

Map是一種把鍵物件和值物件進行關聯的容器,而一個值物件又可以是一個Map,依次類推,這樣就可形成一個多級對映。

程式碼如下:

package java.util;
public interface Map<K,V> {
  ...
}

類圖:

在這裡插入圖片描述

Map 
├Hashtable 
├HashMap 
└WeakHashMap

對於鍵物件 來說,像Set一樣,一個Map容器中的鍵物件不允許重複,這是為了保持查詢結果的一致性;

如果有兩個鍵物件一樣,那你想得到那個鍵物件所對應的值物件時就有問題了,可能你得到的並不是你想的那個值物件,結果會造成混亂,所以鍵的唯一性很重要,也是符合集合的性質的。


當然在使用過程中,某個鍵所對應的值物件可能會發生變化,這時會按照最後一次修改的值物件與鍵對應。對於值物件則沒有唯一性的要求。
你可以將任意多個鍵都對映到一個值物件上,這不會發生任何問題(不過對你的使用卻可能會造成不便,你不知道你得到的到底是那一個鍵所對應的值物件)。

1.HashMap類

HashMap是最常用的Map類,根據鍵的Hash值計算儲存位置,儲存鍵值對,可以根據鍵獲取對應值。具有很快的訪問速度,但是是無序的、執行緒不安全的。且HashMap不同步,如果需要執行緒同步,則需要使用ConcurrentHashMap,也可以使用Collections.synchronizedMap(HashMap map)方法讓HashMap具有同步的能力。其實是否同步,就看有沒有synchronized關鍵字。 HashMap的key有且只能允許一個null。

注:執行緒不安全(多個執行緒訪問同一個物件或實現進行更新操作時,造成資料混亂)

2.Hashtable類

Hashtable繼承自Dictionary類 ,它也是無序的,但是Hashtable是執行緒安全的,同步的,即任一時刻只有一個執行緒能寫Hashtable
由此我們比較一下HashMap和Hashtable的執行效率
測試插入效率如下:

        long runCount=1000000;
        Map<Integer,Integer> hashMap = new HashMap<Integer, Integer>();
        Date dateBegin =
new Date(); for (int i = 0; i < runCount; i++) { hashMap.put(i, i); } Date dateEnd = new Date(); System.out.println("HashMap插入用時為:" + (dateEnd.getTime() - dateBegin.getTime())); Map<Integer,Integer> hashtable = new Hashtable<Integer, Integer>(); Date dateBegin1 = new Date(); for (int i = 0; i < runCount; i++) { hashtable.put(i, i); } Date dateEnd1 = new Date(); System.out.println("Hashtable插入用時為:" + (dateEnd1.getTime() - dateBegin1.getTime()));

執行結果為:

HashMap插入用時為:223
Hashtable插入用時為:674

如果我們將執行次數提高到20000000次,則執行時間分別為:

HashMap插入用時為:36779
Hashtable插入用時為:22632

由此可見,在資料量較小時,HashMap效率較高,但是當資料量增大,HashMap需要進行更多次的resize,這個操作會極大的降低HashMap的執行效率,因此在資料量大之後,Hashtable的執行效率更高。
而反過來重新測試讀取效率,程式碼如下:

        long runCount=1000000;
        Map<Integer,Integer> hashMap = new HashMap<Integer, Integer>();
        for (int i = 0; i < runCount; i++) {
            hashMap.put(i, i);
        }
        Date dateBegin = new Date();
        for (Integer key : hashMap.keySet()) {
            hashMap.get(key);
        }
        Date dateEnd = new Date();
        System.out.println("HashMap讀取用時為:" + (dateEnd.getTime() - dateBegin.getTime()));

        Map<Integer,Integer> hashtable = new Hashtable<Integer, Integer>();
        for (int i = 0; i < runCount; i++) {
            hashtable.put(i, i);
        }
        Date dateBegin1 = new Date();
        for (Integer key : hashtable.keySet()) {
            hashtable.get(key);
        }
        Date dateEnd1 = new Date();
        System.out.println("Hashtable讀取用時為:" + (dateEnd1.getTime() - dateBegin1.getTime()));

執行結果為:

HashMap讀取用時為:54
Hashtable讀取用時為:65

如果將數量增加到20000000,則執行結果為:

HashMap讀取用時為:336
Hashtable讀取用時為:526

由此可見,HashMap的讀取效率更高。

3.LinkedHashMap

LinkedHashMap是Map中常用的有序的兩種實現之一, 它儲存了記錄的插入順序,先進先出。
對於LinkedHashMap而言,它繼承與HashMap,底層使用雜湊表與雙向連結串列來儲存所有元素。其基本操作與父類HashMap相似,它通過重寫父類相關的方法,來實現自己的連結列表特性。LinkedHashMap採用的hash演算法和HashMap相同,但是它重新定義了陣列中儲存的元素Entry,該Entry除了儲存當前物件的引用外,還儲存了其上一個元素before和下一個元素after的引用,從而在雜湊表的基礎上又構成了雙向連結列表,效果圖如下:
在這裡插入圖片描述

示例程式碼如下:

        Map<Integer,Integer> linkedHashMap = new LinkedHashMap<Integer, Integer>();
        linkedHashMap.put(1, 2);
        linkedHashMap.put(3, 4);
        linkedHashMap.put(5, 6);
        linkedHashMap.put(7, 8);
        linkedHashMap.put(9, 0);
        System.out.println("linkedHashMap的值為:" + linkedHashMap);

輸出結果為:

linkedHashMap的值為:{1=2, 3=4, 5=6, 7=8, 9=0}

注:LinkedHashMap在遍歷的時候會比HashMap慢,不過有種情況例外,當HashMap容量很大,實際資料較少時,遍歷起來可能會 比LinkedHashMap慢,因為LinkedHashMap的遍歷速度只和實際資料有關,和容量無關,而HashMap的遍歷速度和他的容量有關

4. TreeMap

TreeMap實現SortMap介面,能夠把它儲存的記錄根據鍵排序,預設是按鍵值的升序排序,也可以指定排序的比較器,當用Iterator 遍歷TreeMap時,得到的記錄是排過序的。

TreeMap的排序原理是:紅黑樹演算法的實現 。

它的主要實現是Comparator架構,通過比較的方式,進行一個排序,以下是TreeMap的原始碼,

比較的原始碼為:

    /**
     * Compares two keys using the correct comparison method for this TreeMap.
     */
    @SuppressWarnings("unchecked")
    final int compare(Object k1, Object k2) {
        return comparator==null ? ((Comparable<? super K>)k1).compareTo((K)k2)
            : comparator.compare((K)k1, (K)k2);
    }

我們也可以自定義Comparator, 對TreeMap資料的排序規則進行修改,這點是LinkedHashMap不能實現的
具體程式碼如下:

        Map<String,Integer> treeMap = new TreeMap<String, Integer>();
        treeMap.put("aa", 888);
        treeMap.put("ee", 55);
        treeMap.put("dd", 777);
        treeMap.put("cc", 88);
        treeMap.put("bb", 999);
        System.out.println("使用預設排序規則,生成的結果為:" + treeMap);

        Map<String, Integer> treeMap2 = new TreeMap<String, Integer>(new Comparator<String>() {
            public int compare(String o1, String o2) {
                return o2.compareTo(o1);
            }
        });
        treeMap2.put("aa", 888);
        treeMap2.put("ee", 55);
        treeMap2.put("dd", 777);
        treeMap2.put("cc", 88);
        treeMap2.put("bb", 999);
        System.out.println("使用自定義排序規則,生成的結果為:" + treeMap2);

執行結果為:

使用預設排序規則,生成的結果為:{aa=888, bb=999, cc=88, dd=777, ee=55}
使用自定義排序規則,生成的結果為:{ee=55, dd=777, cc=88, bb=999, aa=888}

這邊可以檢視一下compareTo()的方法原始碼,內容為:

    public int compareTo(String anotherString) {
        //先得到比較值的字串長度
        int len1 = value.length;
        int len2 = anotherString.value.length;
        //得到最小長度
        int lim = Math.min(len1, len2);
        char v1[] = value;
        char v2[] = anotherString.value;

        int k = 0;
        //逐個比較字串中字元大小
        while (k < lim) {
            char c1 = v1[k];
            char c2 = v2[k];
            if (c1 != c2) {
                return c1 - c2;
            }
            k++;
        }
        //如果在兩個字串的最小長度內,字元均相同,則比較長度
        return len1 - len2;
    }

由此可見,當key值中儲存了Integer型別的數字時,將預設無法根據數字大小來進行排序,處理方式如下:

        Map<String,Integer> treeMap = new TreeMap<String, Integer>();
        treeMap.put("1", 888);
        treeMap.put("9", 55);
        treeMap.put("31", 777);
        treeMap.put("239", 88);
        treeMap.put("177", 999);
        System.out.println("使用預設排序規則,生成的結果為:" + treeMap);

        Map<String, Integer> treeMap2 = new TreeMap<String, Integer>(new Comparator<String>() {
            public int compare(String o1, String o2) {
                //修改比較規則,按照數字大小升序排列
                return Integer.parseInt(o1) - Integer.parseInt(o2);
            }
        });
        treeMap2.put("1", 888);
        treeMap2.put("9", 55);
        treeMap2.put("31", 777);
        treeMap2.put("239", 88);
        treeMap2.put("177", 999);
        System.out.println("使用自定義排序規則,生成的結果為:" + treeMap2);

執行結果為:

使用預設排序規則,生成的結果為:{1=888, 177=999, 239=88, 31=777, 9=55}
使用自定義排序規則,生成的結果為:{1=888, 9=55, 31=777, 177=999, 239=88}

總結

Map中,HashMap具有超高的訪問速度,如果我們只是在Map 中插入、刪除和定位元素,而無關執行緒安全或者同步問題,HashMap 是最好的選擇。
如果考慮執行緒安全或者寫入速度的話,可以使用HashTable
如果想要按照存入資料先入先出的進行讀取。 那麼使用LinkedHashMap
如果需要讓Map按照key進行升序或者降序排序,那就用TreeMap

補充

WeakHashMap類

WeakHashMap是一種改進的HashMap,它對key實行“弱引用”,如果一個key不再被外部所引用,那麼該key可以被GC回收。

Hashtable類和HashMap類的區別

Hashtable和HashMap類有三個重要的不同之處。

1.Hashtable是基於陳舊的Dictionary類的,HashMap是Java 1.2引進的Map介面的一個實現。

2.Hashtable的方法是同步的,而HashMap的方法不是。這就意味著,雖然你可以不用採取任何特殊的行為就可以在一個多執行緒的應用程式中用一個Hashtable,但你必須同樣地為一個HashMap提供外同步。一個方便的方法就是利用Collections類的靜態的synchronizedMap()方法,它建立一個執行緒安全的Map物件,並把它作為一個封裝的物件來返回。這個物件的方法可以讓你同步訪問潛在的HashMap。這麼做的結果就是當你不需要同步時,你不能切斷Hashtable中的同步(比如在一個單執行緒的應用程式中),而且同步增加了很多處理費用。

3.只有HashMap可以讓你將空值作為一個表的條目的key或value。HashMap中只有一條記錄可以是一個空的key,但任意數量的條目可以是空的value。這就是說,如果在表中沒有發現搜尋鍵,或者如果發現了搜尋鍵,但它是一個空的值,那麼get()將返回null。如果有必要,用containKey()方法來區別這兩種情況。