1. 程式人生 > >30-集合--Map+keySet()/entrySet()獲取元素+Map.Entry+Map子類的特點+Hashtable+Properties+HashMap/TreeMap儲存自定義物件

30-集合--Map+keySet()/entrySet()獲取元素+Map.Entry+Map子類的特點+Hashtable+Properties+HashMap/TreeMap儲存自定義物件

一、Map

1、Map與Collection都是集合框架中的頂層介面。Map集合一次新增一對元素,也稱為雙列集合(Collection集合一次新增一個元素,也稱為單列集合)

2、interface Map<K, V>:將鍵對映到值的物件。一個對映不能包含重複的鍵,每個鍵最多隻能對映到一個值。即 Map中儲存的是鍵值對,且必須保證鍵的唯一性

3、Map介面提供三種collection檢視,允許以鍵集、值集或鍵-值對映關係集的形式檢視某個對映的內容

4、所有通用的對映實現類應該提供兩個“標準的”構造方法:一個void(無引數)構造方法,用於建立空對映;一個是帶有單個Map型別引數的構造方法,用於建立一個與其引數具有相同鍵-值對映關係的新對映。實際上,後一個構造方法允許使用者複製任意對映,生成所需類的一個等價對映

5、巢狀類

(1)static interface Map.Entry<K, V>:對映項(鍵-值對)

6、方法

(1)新增

        V put(K key, V value):將指定的值與此對映中的指定鍵關聯。如果此對映以前包含一個該鍵的對映關係,則用指定值替換舊值(當且僅當m.containsKey(k)返回true時,才能說對映m包含鍵k的對映關係)。否則,返回null

        void putAll(Map<? extends K, ? extends V> m):從指定對映中將所有對映關係複製到此對映中。對於指定對映中的每個鍵k到值v的對映關係,此呼叫等效於對此對映呼叫一次put(k, v)。如果正在進行此操作的同時修改了指定的對映,則此操作的行為是不確定的

(2)刪除

        void clear():從此對映中移除所有對映關係。此呼叫返回後,該對映將為空(清空Map集合)

        V remove(Object key):如果存在一個鍵的對映關係,則將其從此對映中移除(根據指定的key刪除這個鍵值對)。返回此對映中以前關聯該鍵的值,如果此對映不包含該鍵的對映關係,則返回null

(3)判斷

        boolean containsKey(Object key):如果此對映包含指定鍵的對映關係,則返回true

        boolean containsValue(Object value):如果此對映將一個或多個鍵對映到指定值,則返回true

        boolean isEmpty():如果此對映未包含鍵-值對映關係,則返回true

(4)獲取

        V get(Object key):返回指定鍵所對映的值。如果此對映不包含該鍵的對映關係,則返回null

注:如果此對映允許null值,則返回null值並不一定表示該對映不包含該鍵的對映關係。也可能該對映將該鍵顯示地對映到null。使用containsKey(key)操作可區分這兩種情況

        int size():返回此對映中的鍵-值對映關係數(鍵值對的個數)

(5)其他

        Set<K> keySet():返回此對映中包含的鍵的Set檢視

        Set<Map.Entry<K, V>> entrySet():返回此對映中包含的對映關係的Set檢視

        Collection<V> values():返回此對映中包含的值的Collection檢視。用Collection而不用Set是因為值不一定唯一

        //Map有泛型,Map<Integer, String>:學號和姓名
        Map<Integer, String> map = new HashMap<Integer, String>();
        map.put(8, "wangcai");  //null
        map.put(8, "xiaoqi");   //wangcai,存相同鍵,值會被覆蓋,返回舊值
        System.out.println(map);    //{8=xiaoqi},鍵和值,用等號(=)連線,表示二者有關係
        map.put(2, "zhangsan");
        map.put(7, "zhaoliu");
        System.out.println(map);    //{2=zhangsan, 7=zhaoliu, 8=xiaoqi},無序,因為new的是HashMap
        //刪除元素
        map.remove(2);  //zhangsan
        System.out.println(map);    //{7=zhaoliu, 8=xiaoqi}
        //判斷
        map.containsKey(8); //true
        //獲取
        map.get(8); //xiaoqi
        map.get(6); //null
        Map<Integer, String> map = new HashMap<Integer, String>();
        map.put(8, "wangwu");
        map.put(2, "zhaoliu");

        //鍵是唯一的,值不一定唯一,所以不能用Set,而是用Collection
        //拿到的全是值
        Collection<String> coll = map.values();
        for (Iterator<String> it = coll.iterator(); it.hasNext(); ) {
            String value = it.next();
            System.out.println(value);
        }

二、keySet()

1、獲取Map中的所有元素,方法之一:先拿到所有鍵,再遍歷所有鍵,每次遍歷通過get(key)方法拿到值

注:Map中沒有迭代器。使用Set而不用List,是為了保證元素的唯一性

2、Set<K> keySet():返回此對映中包含的鍵的Set檢視

3、Map<K, V> --> keySet() --> Set<K> --> iterator() --> key --> get(key) --> value

        Map<Integer, String> map = new HashMap<Integer, String>();
        map.put(8, "wangwu");
        map.put(2, "zhaoliu");
        /**
         * 獲取Map中的所有元素
         * 方法一:
         * (1)先通過keySet()方法獲取Map中的所有鍵的Set集合
         * (2)再通過Set迭代器獲取到每個鍵
         * (3)最後通過map集合的get(key)方法,獲取每個鍵對應的值
         */
        Set<Integer> keySet = map.keySet();
        //Set有迭代器,而Map沒有迭代器
        for (Iterator<Integer> it = keySet.iterator(); it.hasNext(); ) {
            //鍵
            Integer key = it.next();
            //值。get(key)方法每次只能獲取一個值
            String value = map.get(key);
            System.out.println(key + ": " + value);
        }

三、entrySet()

1、獲取Map中的所有元素,方法之二:通過將Map集合的鍵和值的對映關係作為物件儲存到Set集合中,從而實現將Map轉換成Set。遍歷取出每個對映關係,再獲取該對映關係中的鍵和值

注:Map中沒有迭代器。使用Set而不用List,是為了保證元素的唯一性

2、Set<Map.Entry<K, V>> entrySet():返回此對映中包含的對映關係的Set檢視

3、Map<K, V> --> entrySet() --> Set<Map.Entry<K, V>> --> iterator() --> Map.Entry<K, V> --> Map.Entry中的方法getKey()、getValue() --> key、value

        Map<Integer, String> map = new HashMap<Integer, String>();
        map.put(8, "wangwu");
        map.put(2, "zhaoliu");
        /**
         * 獲取Map中的所有元素
         * 方法二:
         * (1)先通過entrySet()方法將Map中的鍵和值的對映關係儲存到Set中
         * (2)再通過Set迭代器獲取每個對映關係
         * (3)最後通過Map.Entry的getKey()、getValue()方法獲取每個對映中的鍵、值
         */
        Set<Map.Entry<Integer, String>> entrySet = map.entrySet();
        //Set有迭代器,而Map沒有迭代器
        //迭代器中的泛型與Set中的一致
        for (Iterator<Map.Entry<Integer, String>> it = entrySet.iterator(); it.hasNext(); ) {
            //鍵-值對映關係(取出的是Set中的內容)
            Map.Entry<Integer, String> me = it.next();
            //使用Map.Entry的getKey()獲取鍵
            Integer key = me.getKey();
            //使用Map.Entry的getValue()獲取值
            String value = me.getValue();
            System.out.println(key + ": " + value);
        }

四、Map.Entry

1、public static interface Map.Entry<K, V>:對映項(鍵-值對)。Map.entrySet()方法返回對映的collection檢視,其中的元素屬於此類。獲得對映項引用的唯一方法是通過此collection檢視的迭代器來實現。這些Map.Entry物件僅在迭代期間有效。更確切地講,如果在迭代器返回項之後修改了底層對映,則某些對映項的行為是不確定的,除了通過setValue()在對映項上執行操作之外

2、public static interface Map.Entry<K, V>:內部介面(介面中的靜態介面),隨著外部介面的載入而載入

注:Entry是鍵和值的對映關係物件,只有先有Map對映,才存在關係Entry。而Entry把關係封裝成物件,該關係也在訪問Map中的鍵和值。即 外部規則中有內部規則,內部規則在直接訪問外部規則中的內容

3、方法

(1)K getKey():返回與此項對應的鍵

(2)V getValue():返回與此項對應的值。如果已經從底層對映中移除了對映關係(通過迭代器的remove()操作),則此呼叫的結果是不確定的

(3)V setValue(V value):用指定的值替換與此項對應的值(寫入該對映)。如果已經從對映中移除了對映關係(通過迭代器的remove()操作),則此呼叫的行為是不確定的

五、Map子類的特點

1、Hashtable:內部結構是雜湊表,是同步的(效率低)。不允許null作為鍵、null作為值

注:JDK1.0出現此類,單列集合是Vector,雙列集合是Hashtable

2、HashMap:內部結構是雜湊表,是不同步的(效率高)。允許null作為鍵、null作為值

注:HashSet:此類實現Set介面,由雜湊表(實際上是一個HashMap例項)支援。Set集合的底層程式碼由Map實現 -- 值統一,操作鍵

3、TreeMap:內部結構是二叉樹,是不同步的。可以對Map集合中的鍵進行排序

注:LinkedHashMap:有序(存、取順序一致)

六、Hashtable

1、此類實現一個雜湊表,該雜湊表將鍵對映到相應的值。任何非null物件都可以用作鍵或值。為了成功地在雜湊表中儲存和獲取物件,用作鍵的物件必須實現hashCode()和equals()方法

七、Properties

1、Properties:Hashtable的直接已知子類,用來儲存鍵值對型的配置檔案資訊(可以和IO技術相結合)

2、Properties類表示了一個持久的屬性集。Properties可儲存在流中或從流中載入。屬性列表中每個鍵及其對應值都是一個字串。一個屬性列表可包含另一個屬性列表作為它的“預設值”。如果未能在原有的屬性列表中搜索到屬性鍵,則搜尋第二個屬性列表

3、因為Properties繼承於Hashtable,所以可對Properties物件應用put()和putAll()方法。但不建議使用這兩個方法,因為它們允許呼叫者插入其鍵或值不是String的項。相反,應該使用setProperty()方法。如果在“不安全”的Properties物件(即包含非String的鍵或值)上呼叫store()或save()方法,則該呼叫將失敗。類似地,如果在“不安全”的Properties物件(即包含非String的鍵)上呼叫propertyNames()或list()方法,則該呼叫將失敗

4、Properties類是執行緒安全的:多個執行緒可以共享單個Properties物件而無需進行外部同步

5、欄位

(1)protected Properties defaults:一個屬性列表,包含屬性列表中所有未找到值的鍵的預設值

6、建構函式

(1)Properties():建立一個無預設值的空屬性列表

(2)Properties(Properties defaults):建立一個帶有指定預設值的空屬性列表

7、方法

(1)String getProperty(String key):用指定的鍵在此屬性列表中搜索屬性。如果在此屬性列表中未找到該鍵,則接著遞迴檢查預設屬性列表及其預設值。如果未找到屬性,則此方法返回null

(2)String getProperty(String key, String defaultValue):用指定的鍵在屬性列表中搜索屬性。如果在屬性列表中未找到該鍵,則接著遞迴檢查預設屬性列表及其預設值。如果未找到屬性,則此方法返回預設值變數defaultValue

(3)void list(PrintStream out):將屬性列表輸出到指定的輸出流。此方法對除錯很有用

(4)void list(PrintWriter out):將屬性列表輸出到指定的輸出流。此方法對除錯很有用

(5)void load(InputStream inStream) throws IOException:從輸入流中讀取屬性列表(鍵和元素對)。此方法返回後,指定的流仍保持開啟狀態

(6)void load(Reader reader) throws IOException:按簡單的面向行的格式從輸入字元流中讀取屬性列表(鍵和元素對)

(7)void loadFromXML(InputStream in) throws IOException, InvalidPropertiesFormatException:將指定輸入流中由XML文件所表示的所有屬性載入到此屬性表中。該XML文件必須具有以下DOCTYPE宣告:

<!DOCTYPE properties SYSTEM "http://java.sun.com/dtd/properties.dtd">

而且該文件還必須滿足上述屬性DTD的要求。此方法返回後,指定的流已關閉

(8)Enumeration<?> propertyNames():返回屬性列表中所有鍵的列舉,如果在主屬性列表中未找到同名的鍵,則包括預設屬性列表中不同的鍵

(9)@Deprecated public void save(OutputStream out, String comments):已過時。呼叫store(OutputStream out, String comments)方法並取消丟擲的IOExceptions。即 如果在儲存屬性列表時發生I/O錯誤,則此方法不丟擲IOException。儲存屬性列表的首選方法是通過store(OutputStream out, String comments)方法或storeToXML(OutputStream os, String comment)方法來進行

(10)Object setProperty(String key, String value):呼叫Hashtable的方法put()。使用getProperty()方法提供並行性。強制要求為屬性的鍵和值使用字串。返回值是Hashtable呼叫put()的結果(返回屬性列表中指定鍵的舊值,如果沒有值,則為null)

(11)void store(OutputStream out, String comments) throws IOException:以適合使用load(InputStream)方法載入到Properties表中的格式,將此Properties表中的屬性列表(鍵和元素對)寫入輸出流。寫入各個項後,重新整理輸出流。此方法返回後,輸出流仍保持開啟狀態

(12)void store(Writer writer, String comments) throws IOException:以適合使用load(Reader)方法的格式,將此Properties表中的屬性列表(鍵和元素對)寫入輸出字元。寫入各個項後,重新整理輸出流。此方法返回後,輸出流仍保持開啟狀態

(13)void storeToXML(OutputStream os, String comment) throws IOException:發出一個表示此表中包含的所有屬性的XML文件。以props.storeToXML(os, comment)的形式呼叫此方法的行為與呼叫props.storeToXML(os, comment, "UTF-8");完全相同

(14)void storeToXML(OutputStream os, String comment, String encoding) throws IOException:使用指定的編碼發出一個表示此表中包含的所有屬性的XML文件。該XML文件要具有以下DOCTYPE宣告:

 <!DOCTYPE properties SYSTEM "http://java.sun.com/dtd/properties.dtd">

如果指定的註釋comment為null,則沒有註釋儲存在該文件中。此方法返回後,指定的流仍保持開啟狀態

(15)Set<String> stringPropertyNames():返回此屬性列表中的鍵集,其中該鍵及其對應值是字串,如果在主屬性列表中未找到同名的鍵,則還包括預設屬性列表中不同的鍵。其鍵或值不是String型別的屬性被忽略。返回的set不受Properties物件支援。對此Properties的改變不能在該set中反映出來,反之亦然

八、HashMap儲存自定義物件

1、HashMap:基於雜湊表的Map介面的實現。此實現提供所有可選的對映操作,並允許使用null值和null鍵(除了非同步和允許使用null之外,HashMap類與Hashtable大致相同)。此類不保證對映的順序(無序),特別是它不保證該順序恆久不變

2、此實現不是同步的。如果多個執行緒同時訪問一個雜湊對映,而其中至少一個執行緒從結構上修改了該對映,則它必須保持外部同步。一般通過對自然封裝該對映的物件進行同步操作來完成。如果不存在這樣的物件,則應該使用Collections.synchronizedMap()方法來“包裝”該對映。最好在建立時完成這一操作,以防止對對映進行意外的非同步訪問

 Map m = Collections.synchronizedMap(new HashMap(...));

3、構造方法

(1)HashMap():構造一個具有預設初始容量(16)和預設載入因子(0.75)的空HashMap

(2)HashMap(int initialCapacity):構造一個帶指定初始容量和預設載入因子(0.75)的空HashMap

(3)HashMap(int initialCapacity, float loadFactor):構造一個帶指定初始容量和載入因子的空HashMap

(4)HashMap(Map<? extends K, ? extends V> m):構造一個對映關係與指定Map相同的新HashMap。所建立的HashMap具有預設載入因子(0.75)和足以容納指定Map中對映關係的初始容量

4、HashMap若想保證鍵相同,必須要提供鍵判斷相同的依據。雜湊表要覆蓋hashCode()和equals()兩個方法。所以,鍵物件要覆蓋hashCode()和equals()方法,建立自己的比較相同的依據

九、TreeMap儲存自定義物件

1、TreeMap:基於紅黑樹的NavigableMap實現。該對映根據其鍵的自然順序(Comparable)進行排序,或者根據建立對映時提供的Comparator進行排序,具體取決於使用的構造方法

2、此實現不是同步的。如果多個執行緒同時訪問一個對映,並且其中至少一個執行緒從結構上修改了該對映,則其必須外部同步。一般是通過對自然封裝該對映的物件執行同步操作來完成的。如果不存在這樣的物件,則應該使用Collections.synchronizedSortedMap()方法來“包裝”該對映。最好在建立時完成這一操作,以防止對對映進行意外的不同步訪問

SortedMap m = Collections.synchronizedSortedMap(new TreeMap(...));

3、建構函式

(1)TreeMap():使用鍵的自然順序構造一個新的、空的樹對映。插入該對映的所有鍵都必須實現Comparable介面。另外,所有這些鍵都必須是可互相比較的:對於對映中的任意兩個鍵k1和k2,執行k1.compareTo(k2);都不得丟擲ClassCastException

(2)TreeMap(Comparator<? super K> comparator):構造一個新的、空的樹對映,該對映根據給定比較器進行排序。插入該對映的所有鍵都必須由給定比較器進行相互比較:對於對映中的任意兩個鍵k1和k2,執行comparator.compare(k1, k2);都不得丟擲ClassCastException

(3)TreeMap(Map<? extends K, ? extends V> m):構造一個與給定對映具有相同對映關係的新的樹對映,該對映根據其鍵的自然順序(Comparable)進行排序。插入此新對映的所有鍵都必須實現Comparable介面。另外,所有這些鍵都必須是可互相比較的:對於對映中的任意兩個鍵k1和k2,執行k1.compareTo(k2);都不得丟擲ClassCastException

(4)TreeMap(SortedMap<K, ? extends V> m):構造一個與指定有序對映具有相同對映關係和相同排序順序的新的樹對映。引數m是有序對映,其對映關係將存放在此對映中,並且其比較器將用來對此對映進行排序

4、TreeMap對元素進行排序的兩種方式 -- 針對鍵 :

(1)讓元素自身具備比較功能。元素需要實現Comparable介面,覆蓋CompareTo()方法 -- 元素的自然排序

(2)讓集合自身具備比較功能。定義一個類,實現Comparator介面,覆蓋compare()方法。將該類物件(介面Comparator的子類物件)作為引數傳遞給TreeMap集合的建構函式 -- 比較器

十、練習

1、獲取字串中每一個字母出現的次數。eg:“fdgavcbsacdfs” --> a(2)b(1)c(2)...

    /**
     * 獲取字串中每一個字母出現的次數。eg:“fdgavcbsacdfs” --> a(2)b(1)c(2)...
     * 思路:
     * (1)每個字母對應次數,字母和次數之間存在著對映關係
     * (2)當對映關係的一方是有序編號時,使用陣列。當對映關係的任何一方都不是有序編號時,使用Map集合
     * (3)需要保證唯一性的一方(字母)具備著順序,使用TreeMap
     */
    public static String getCharCount(String str) {
        //將字串變成字元陣列
        char[] chars = str.toCharArray();
        //定義Map集合。結果需要按照字母順序排序,使用TreeMap
        Map<Character, Integer> map = new TreeMap<Character, Integer>();
        //遍歷字元陣列
        for (int i = 0; i < chars.length; i++) {
            //如果只要字母,而指定的字串中可能包含其他字元,需要做判斷
            if (!((chars[i] >= 'a' && chars[i] <= 'z') || (chars[i] >= 'A' && chars[i] <= 'Z'))) {
                continue;
            }

            //將陣列中的字母作為鍵查詢map
            //也可使用containsKey()方法判斷是否包含key,再對val進行操作
            Integer val = map.get(chars[i]);
            //寫法一:
//            if(val == null){
//                //如果該字母不存在,就將該字母作為鍵,1作為值儲存到map中
//                map.put(chars[i], 1);
//            }else {
//                //如果該字母存在,就將該字母鍵對應的值取出後加1,再將該字母和加1後的值儲存到map中
//                map.put(chars[i],val+1);
//            }
            //寫法二:
//            int count = 1;
//            if (val != null) {
//                count = val + 1;
//            }
//            map.put(chars[i], count);
            //寫法三:
            int count = 0;
            if (val != null) {
                count = val;
            }
            count++;
            map.put(chars[i], count);
        }
        //遍歷結束,map中記錄著所有字母出現的次數
        //根據指定格式輸出
        return mapToString(map);
    }

    /**
     * 將map按照指定格式轉成字串
     *
     * @param map
     * @return
     */
    private static String mapToString(Map<Character, Integer> map) {
        //使用StringBuffer,而不是直接定義String進行拼接(節省記憶體空間)
        StringBuffer sb = new StringBuffer();
        Set<Character> ketSet = map.keySet();
        for (Iterator<Character> it = ketSet.iterator(); it.hasNext(); ) {
            Character key = it.next();
            Integer value = map.get(key);
            sb.append(key + "(" + value + ")");
        }
        return sb.toString();
    }

2、Map集合在有對映關係時,可以優先考慮。在查表法中的應用較為多見

(1)對映關係的一方是有序的編號時,使用陣列

(2)對映關係的任何一方都不是有序編號時,使用Map集合

3、Map不一定需要有序編號,旨在建立物件之間的關係。一個鍵,其對應的值可以是List/Set集合,集合裡面存的是物件

eg:每一個班級,裡面都有好多同學  map.put("班級", Set<Student>);