天天看點

Java集合架構(五)—— Map、HashMap、Hashtable、Properties、SortedMap、TreeMap、WeakHashMap、IdentityHashMap、EnumMap

  • Map

  Map用于儲存具有映射關系的資料,是以Map集合裡儲存着兩組值,一組值用于儲存Map裡的key,另一組值用于儲存Map裡的value,key和value都可以是任何引用類型的資料。Map的key不容許重複,即同一個Map對象的任何兩個key通過equals方法比較總是傳回false。

  key和value之間存在單向一對一關系,即通過指定的key,總能找到唯一的、确定的value。從Map中取出資料時,隻要給出指定的key,就可以取出對應的value。

  如果把Map裡的所有key放在一起看,它們就是一個Set集合,實際上Map确實包含一個keySet()方法,用于傳回Map所有key組成的Set集合。如下:

public class Test {
    public static void main(String[] args){
        Map<String, String> mapVal = new HashMap<String, String>();
        mapVal.put("spring", "春");
        mapVal.put("summer", "夏");
        mapVal.put("autumn", "秋");
        mapVal.put("winter", "冬");
        //擷取mapVal集合的所有key值
        Set<String> set = mapVal.keySet();
        Iterator<String> it = set.iterator();
        while(it.hasNext()){
            String season = it.next();
            System.out.println(season);
        }
    }
}      

  不僅如此,Map裡key集合和Set集合裡元素的存儲形式也很像,Map子類和Set子類在名字上也驚人的相似:如Set接口下有HashSet、LinkedHashSet、SortedSet(接口)、TreeSet、EnumSet等實作類和子接口,而Map接口下則有HashMap、LinkedHashMap、SortedMap(接口)、TreeMap、EnumMap等實作類和子接口。正如它們名字所暗示的,Map的這些實作類和子接口中key集存儲形式和對應Set集合中元素的存儲形式完全相同。

  如果把Map所有value放在一起看,它們又非常類似于一個List:元素與元素之間可以重複,每個元素可以根據索引來查找,隻是map中的索引不再使用整數值,而是以另一個對象做為索引。如果需要從List集合中取元素,需要提供該元素的數字索引;如果需要從Map中取出元素,需要提供該元素的key索引。是以,Map有時也被稱為字典,或關聯數組。Map接口中定義了如下常用方法:

  1. void clear();  //删除該Map對象中所有key-value對。
  2. boolean containsKey(Object key);        //查詢Map中是否包含指定key,如果包含則傳回true。
  3. boolean containsValue(Object value);   //查詢Map中是否包含一個或多個value,如果包含則傳回true。
  4. Set entrySet();        //傳回Map中所包含的key-value對所組成的Set集合,每個集合元素都是Map.Entry(Entry是Map的内部類)對象。
  5. Object get(Object key);        //傳回指定key所對應的value,如果此Map不包含該key,則傳回null。
  6. boolean isEmpty();            //查詢該Map集合是否為空(即不包含任何key-value對),如果為空則傳回true。
  7. Set keySet();      //傳回該Map中所有key所組成的Set集合。
  8. Object put(Object key, Object value);       //添加一個key-value對,如果目前Map中已有一個與key相等的key-value對,則新的key-value對會覆寫原來的key-value對。
  9. void putAll(Map m);        //将指定Map中的key-value對複制到本Map中。
  10. Object remove(Object key);        //删除指定key所對應的key-value對,傳回被删除key所關聯的value,如果key不存在,傳回nul
  11. int size();            //傳回該Map裡的key-value對的個數。
  12. Collection values();     //傳回該Map裡所有value組成的Collection。

  Map中包含一個内部類:Entry。該類封裝了一個key-value對,Entry包含三個方法:

  1. Object  getKey();  //傳回該Entry裡包含的key值。
  2. Object  getValue();   //傳回該Entry裡包含的value值。
  3. Object  setValue(V value);  //設定該Entry裡包含的value值,并傳回新設定的value值。

 我們可以把Map了解成一個特殊的Set,隻是該Set裡包含的集合元素是Entry對象,而不是普通對象。

如下為Entry示例:

public class Test {
    public static void main(String[] args){
        Map<String, String> mapVal = new HashMap<String, String>();
        mapVal.put("spring", "春");
        mapVal.put("summer", "夏");
        mapVal.put("autumn", "秋");
        mapVal.put("winter", "冬");
        Set<Entry<String, String>> entrySet = mapVal.entrySet();
        Iterator<Entry<String, String>> iterator = entrySet.iterator();
        while(iterator.hasNext()){
            Entry<String, String> next = iterator.next();
            String key = next.getKey();
            String value = next.getValue();
            System.out.println("key:"+ key +"-------value:" + value);
        }
    }
}      
  •  HashMap和Hashtable實作類

  HashMap和Hashtable都是Map接口的典型實作類,他們之間的關系完全類似于ArrayList和Vector的關系:Hashtable是一個古老的Map實作類,它從JDK1.0起就已經出現了,當它出現時,Java沒有提供Map接口,是以它包含了兩個繁瑣的方法:elements()(類似于Map接口定義的values()方法)和keys(類似于Map接口定義的keySet()方法),現在很少使用這兩個方法。

  HashMap和Hashtable的兩點典型差別:

  Hashtable是一個線程安全的Map實作,但HashMap是線程不安全的實作,是以HashMap比Hashtable性能要高一點;但如果有多條線程通路同一個Map對象時,使用Hashtable實作類會更好。

  Hashtable不容許使用null作為key和value,如果試圖把null放進Hashtable中,将會引發NullPointerException異常;但HashMap可以使用null做為key和value。

  注意:與Vector類似,盡量少用Hashtable實作類,即使需要建立線程安全的Map實作類,也可以通過Collections工具類把HashMap變成線程安全的,無須使用Hashtable實作類。

  為了成功地在HashMap、Hashtable中存儲、擷取對象,用作key的對象必須實作hashCode方法和equals方法。

  與HashSet不能保證元素的順序一樣,HashMap和Hashtable也不能保證key-value對的順序。類似于HashSet的是,HashMap、Hashtable判斷兩個key相等的标準也是:兩個key通過equals方法比較傳回true,兩個key的hashCode值也相等。

  除此之外,HashMap、Hashtable中還包含一個containsValue方法用于判斷是否包含指定的value,那麼HashMap、Hashtable如何判斷兩個value相等呢?HashMap、Hashtable判斷兩個value相等的标準更簡單:隻要兩個對象通過equals比較傳回true即可。 

  • LinkedHashMap類  

  HashMap有一個子類:LinkedHashMap;LinkedHashMap也使用雙向連結清單來維護key-value對的次序,該連結清單定義了疊代順序,該疊代順序與key-value對的插入順序保持一緻。LinkedHashMap可以避免需要對HashMap、Hashtable裡的key-value對進行排序(隻要插入key-value對時保持順序即可)。同時又避免使用TreeMap所增加的成本。

  LinkedHashMap需要維護元素的插入順序,是以性能略低于HashMap的性能,但在疊代通路Map裡的全部元素時将有很好的性能,因為它以連結清單來維護内部順序。

public class Test {
    public static void main(String[] args){
        LinkedHashMap<String, String> mapVal = new LinkedHashMap<String, String>();
        mapVal.put("spring", "春");
        mapVal.put("summer", "夏");
        mapVal.put("autumn", "秋");
        mapVal.put("winter", "冬");
        System.out.println(mapVal);
        for(String str : mapVal.keySet()){
            System.out.println("key:" + str);
            System.out.println("value:" + mapVal.get(str));
        }
    }
}      
  • Properties類

  Properties類是Hashtable類的子類,正如它的名字所暗示的,該檔案在處理屬性檔案。Properties類可以把Map對象和屬性檔案關聯起來,進而可以把Map對象中的key-value對寫入屬性檔案,也可以把屬性檔案中的屬性名=屬性值加載到Map對象中。由于屬性檔案裡的屬性名、屬性值隻能是字元串類型,是以Properties裡的key、value都是字元串類型,該類提供了如下三個方法來修改Properties裡的key、value值。

  1. String  getProperty(String key);   //擷取properties中指定屬性名對應的屬性值,類似于Map的get(Object key)方法。
  2. String  getProperty(String key, String defaultValue);    //該方法與前一個方法基本相似,該方法多個功能,如果Properties中不存在指定key時,該方法傳回預設值。
  3. Object setProperty(String  key, String value);    //設定屬性值,類似Hashtable的put方法。
  4. void load(InputStream inStream);      //從屬性檔案(以輸出流表示)中加載屬性名=屬性值,把加載到的屬性名=屬性值對追加到Properties裡(由于Properties是Hashtable的之類,它不保證key-value對之間的次序)。
  5. void store(OutputStream out, String comments);    //将Properties中的key-value對寫入指定屬性檔案(以輸出流表示),comments是要寫的注解。

  如下代碼所示:

public class Test {
    public static void main(String[] args){
        Properties readProperties = new Properties();  //用來寫的Properties對象
        readProperties.setProperty("username", "zhangsan");
        readProperties.setProperty("password", "123456");
        Properties writeProperties = new Properties(); //用來讀的Properties對象
        FileInputStream fileInputStream = null;
        FileOutputStream fileOutputStream = null;
        try {
            //1.1通過類裝載器擷取要存儲的路徑
            String path = Test.class.getResource("/").getPath();
                path = path +  "config.properties";
            System.out.println(path);
            fileOutputStream = new FileOutputStream(new File(path));
            //1.2将配置檔案資訊寫到硬碟上
            readProperties.store(fileOutputStream, "This is config with database!");
            
            //2.1讀取硬碟上的配置檔案
            fileInputStream = new FileInputStream(new File(path));
            writeProperties.load(fileInputStream);
            String username = writeProperties.getProperty("username");
            String username2 = writeProperties.getProperty("username2" , "沒有找到username2的key");
            //列印結果"sername:zhangsan"
            System.out.println("username:" + username);
            //列印結果"username2:沒有找到username2的key"
            System.out.println("username2:" + username2);
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }finally{
            if(fileOutputStream != null){
                try {
                    fileOutputStream.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
        
    }
}      
  •  SortedMap接口和TreeMap實作類

  正如Set接口派生出了SortedSet子接口,SortedSet接口有一個TreeSet實作類,Map接口也派生了一個SortedMap子接口,SortedMap也有一個TreeMap實作類。

  與TreeSet類似的是,TreeMap也是基于紅黑樹對TreeMap中所有key進行排序,進而保證TreeMap中所有key-value對處于有序狀态。TreeMap也有兩種排序方式:

    自然排序:TreeMap的所有key必須實作那Comparable接口,而且所有key應該是同一個類的對象,否則将會抛出ClassCaseException。

    定制排序:建立TreeMap時,傳入一個Comparator對象,該對象負責對TreeMap中所有key進行排序。采用定制排序時不要求Map的key實作Comparable接口。

  *可以參考TreeSet的代碼示範。

修飾符和類型 方法和描述

Map.Entry<K,V>

ceilingEntry(K key)

傳回大于或等于給定鍵相關聯的與最小鍵 - 值映射,或者

null

如果不存在這樣的鍵。

K

ceilingKey(K key)

傳回大于或等于給定鍵的最小鍵,或者

null

void

clear()

從此映射中删除所有映射。

Object

clone()

傳回此

TreeMap

執行個體的淺表副本。

Comparator<? super K>

comparator()

傳回用于對此映射中的鍵進行排序的比較器,或者 

null

此映射使用其鍵的自然排序。

boolean

containsKey(Object key)

true

如果此映射包含指定鍵的映射,則傳回。

boolean

containsValue(Object value)

傳回

true

如果此映射将一個或多個鍵映射到指定值。

NavigableSet<K>

descendingKeySet()

NavigableSet

此映射中包含的鍵的逆序視圖。

NavigableMap<K,V>

descendingMap()

傳回此映射中包含的映射的逆序視圖。

Set<Map.Entry<K,V>>

entrySet()

Set

此映射中包含的映射的視圖。

Map.Entry<K,V>

firstEntry()

傳回與此地圖中最小鍵相關的鍵值映射,或者

null

地圖為空。

K

firstKey()

傳回此地圖中目前第一個(最低)的鍵。

Map.Entry<K,V>

floorEntry(K key)

傳回與最大鍵小于或等于給定鍵相關聯的鍵 - 值映射,如果不存在這樣的鍵,則傳回null。

K

floorKey(K key)

傳回小于或等于給定鍵的最大鍵,如果不存在這樣的鍵傳回null。

void

forEach(BiConsumer<? super K,? super V> action)

對此映射中的每個條目執行給定操作,直到處理完所有條目或操作抛出異常為止。

V

get(Object key)

傳回指定鍵映射到的值,或者

null

此映射不包含鍵的映射。

SortedMap<K,V>

headMap(K toKey)

傳回此映射的關鍵字嚴格小于的部分的視圖

toKey

NavigableMap<K,V>

headMap(K toKey, boolean inclusive)

傳回此映射關鍵字小于(或等于,如果

inclusive

為真)的部分的視圖

toKey

Map.Entry<K,V>

higherEntry(K key)

傳回與最小鍵相關的鍵 - 值映射嚴格大于給定鍵,或者

null

K

higherKey(K key)

傳回嚴格大于給定鍵的最小鍵,或者 

null

Set<K>

keySet()

Set

此映射中包含的鍵的視圖。

Map.Entry<K,V>

lastEntry()

傳回與此地圖中最大鍵關聯的鍵值映射,或者

null

映射為空。

K

lastKey()

傳回此地圖中目前最後一個(最高)的鍵。

Map.Entry<K,V>

lowerEntry(K key)

傳回與最大鍵相關的鍵 - 值映射嚴格小于給定鍵,或者

null

如果沒有這樣的鍵。

K

lowerKey(K key)

傳回最大的密鑰嚴格小于給定的密鑰,或者 

null

如果沒有這樣的密鑰。

NavigableSet<K>

navigableKeySet()

NavigableSet

Map.Entry<K,V>

pollFirstEntry()

移除并傳回與此地圖中的最小鍵相關聯的鍵值映射,或者

null

Map.Entry<K,V>

pollLastEntry()

移除并傳回與此地圖中最大鍵關聯的鍵值映射,或者

null

V

put(K key, V value)

将指定的值與此映射中指定的鍵關聯。

void

putAll(Map<? extends K,? extends V> map)

将指定地圖中的所有映射複制到此地圖。

V

remove(Object key)

如果存在,則從此TreeMap中移除此鍵的映射。

V

replace(K key, V value)

僅當指定鍵的條目映射到某個值時才替換該條目。

boolean

replace(K key, V oldValue, V newValue)

僅當目前映射到指定值時才替換指定鍵的條目。

void

replaceAll(BiFunction<? super K,? super V,? extends V> function)

用對該條目調用給定函數的結果替換每個條目的值,直到處理完所有條目或者該函數抛出異常。

int

size()

傳回此映射中鍵值映射的數量。

NavigableMap<K,V>

subMap(K fromKey, boolean fromInclusive, K toKey, boolean toInclusive)

傳回此鍵映射範圍從0 

fromKey

到的部分視圖 

toKey

SortedMap<K,V>

subMap(K fromKey, K toKey)

傳回此映射部分的視圖,其鍵範圍從 

fromKey

(包含)到

toKey

獨占。

SortedMap<K,V>

tailMap(K fromKey)

傳回此映射的鍵大于或等于的部分的視圖

fromKey

NavigableMap<K,V>

tailMap(K fromKey, boolean inclusive)

傳回此映射關鍵字大于(或等于,如果

inclusive

fromKey

Collection<V>

values()

Collection

此映射中包含的值的視圖。
  • WeakHashMap實作類

  WeakHashMap與HashMap的用法基本相似。但與HashMap的差別在于,HashMap的key保留對象的強引用,這意味着隻要該HashMap對象不被銷毀,該HashMap對象所有key所引用的對象不會被垃圾回收,HashMap也不會自動删除這些key所對應的key-value對象;但WeakHashMap的key隻保留對實際對象的弱引用,這意味着當垃圾回收了該key所對應的實際對象後,WeakHashMap會自動删除該key對應的key-value對。

public class Test {
    public static void main(String[] args){
        WeakHashMap<String, String> map = new WeakHashMap<String, String>();
            //将WeakHashMap中添加三個key-value對,
            //三個key都是匿名字元串對象(沒有其他引用)
            map.put(new String("國文"), new String("優"));
            map.put(new String("數學"), new String("良"));
            map.put(new String("英語"), new String("中"));
            //将WeakHashMap中添加一個key-value對,
            //該key是一個系統緩存的字元串對象。
            map.put("java", new String("不及格"));
            //輸出map對象,将看到4個key-value對{java=不及格, 數學=良, 英語=中, 國文=優}
            System.out.println(map);
            //通知系統立即進行垃圾回收
            System.gc();
            System.runFinalization();
            //通常情況下,将隻看到一個key-value對{java=不及格}
            System.out.println(map);
    }
}      

  從上面運作結果可以看出,當系統進行垃圾回收時,删除了WeakHashMap對象的前三個key-value對。這是因為添加前三個key-value對時,這三個key都是匿名字元串對象,隻有WeakHashMap保留了對它們的弱引用。WeakHashMap對象中的第四組key-value對的key是一個字元串的直接量,系統會緩沖這個字元串直接量(即系統保留了對該字元串對象的強引用),是以垃圾回收時不會回收它。

  •  IdentityHashMap實作類

  IdentityHashMap實作類的實作機制與HashMap基本相似,但它在處理兩個key相等時,比較獨特:在IdentityHashMap中,當且僅當兩個key嚴格相等時(key1 = key2)時,IdentityHashMap才認為兩個key相等,對于普通HashMap而言,隻要key1和key2通過equals比較傳回true,且它們的hashCode值相等即可。

   IdentityHashMap提供了與HashMap基本相似的方法,也允許使用null做為key和value。與HashMap類似的是,IdentityHashMap不保證任何key-value對之間的順序,更不能保證它們的順序随時間的推移保持不變。

public class Test {
    public static void main(String[] args){
        IdentityHashMap<String, String> map = new IdentityHashMap<String, String>();
            //下面兩行代碼會向map中添加兩條key-value對
            map.put(new String("國文"), "99");
            map.put(new String("國文"), "100");
            //下面兩行代碼會向map中添加一條key-value對
            map.put("java", "89");
            map.put("java", "69");
            //列印結果為{java=69, 國文=100, 國文=99}
            System.out.println(map);
    }
}      
  •  EnumMap實作類

  EnumMap是一個與枚舉類一起使用的Map實作,EnumMap中所有key都必須是單個枚舉類的枚舉值。建立EnumMap時必須顯示或隐式指定它對應的枚舉類。

  EnumMap不允許使用null作為key值,但容許使用null值做為value。如果試圖使用null做為key将抛出NullPointerException異常。如果僅僅隻是查詢是否包含值為null的key,或者僅僅隻是使用删除值為null的key,都不會抛出異常。

enum Season{
    SPRING,SUMMER,AUTUMN,WINTER
}
public class Test {
    public static void main(String[] args){
        EnumMap<Season, String> map = new EnumMap<Season, String>(Season.class);
        //列印結果為{}
        System.out.println(map);
        map.put(Season.SPRING, "春");
        map.put(Season.SUMMER, "夏");
        map.put(Season.AUTUMN, "秋");
        map.put(Season.WINTER, "冬");
        //列印結果為{SPRING=春, SUMMER=夏, AUTUMN=秋, WINTER=冬}
        System.out.println(map);
    }
}      

   上面程式中建立了一個EnumMap對象,建立該EnumMap對象時指定它的key隻能是Season枚舉類的枚舉值。如果向該EnumMap中添加四個key-value對後,這四個key-value對将會以Season枚舉值的自然順序排序。

  對于Map的常用實作類而言,HashMap和Hashtable的效率大緻相同,因為它們的實作機制幾乎完全一樣,但HashMap通常比Hashtable要快一點,因為Hashtable額外實作同步操作。

  TreeMap通常比HashMap、Hashtable要慢(尤其在插入、删除key-value對的時候更慢),因為TreeMap需要額外的紅黑樹操作來維護key之間的次序。但使用TreeMap有一個好處:TreeMap中的key-value對總是處于有序狀态,無須專門進行排序操作。

作者:郭耀華

出處:http://www.guoyaohua.com

微信:guoyaohua167

郵箱:[email protected]

本文版權歸作者和部落格園所有,歡迎轉載,轉載請标明出處。

【如果你覺得本文還不錯,對你的學習帶來了些許幫助,請幫忙點選右下角的推薦】

繼續閱讀