集是一個集合,它可以快速地查找現(xiàn)有的元素廊佩。但是囚聚,要查看一個元素,需要有要查找元素的精確副本标锄。這不是一種非常通用的查找方式。通常茁计,我們知道某些鍵的信息料皇,并想要查找與之對應的元素。映射(map)數(shù)據(jù)結構就是為此設計的星压。映射用來存放鍵/值對践剂。如果提供了鍵,就能夠查找到值娜膘。
基本映射操作
Java類庫為映射提供了兩個通用的實現(xiàn):HashMap和TreeMap逊脯。這兩個類都實現(xiàn)了Map接口。
散列映射對鍵進行散列竣贪,樹映射用鍵的整體順序?qū)υ剡M行排序军洼,并將其組織成搜索樹。散列或比較函數(shù)職能作用于鍵演怎。于鍵關聯(lián)的值不能進行散列或比較匕争。
散列稍微快一些,如果不需要按照排序順序訪問鍵爷耀,就最好選擇散列甘桑。
下列代碼將為存儲的員工信息建立一個散列映射:
Map<String, Employee> staff = new HashMap<>(); // HashMap implements Map
Employee harry = new Employee("Harry Hacker");
staff.put("987-98-9996", harry);
每當往映射中添加對象時,必須同時提供一個鍵。在這里跑杭,鍵是一個字符串铆帽,對應的值是Employee對象。
要想檢索一個對象德谅,必須使用(因而锄贼,必須記住)一個鍵女阀。
String id = "987-98-9996";
e = staff.get(id);
如果在映射中沒有與定鍵對應的消息宅荤,get將返回null。null返回值可能并不方便浸策。有時可以有一個好的默認值冯键,用作為映射中不存在的鍵。然后使用getOrDefault方法庸汗。
Map<String, Integer> scores = ...;
int score = scores.get(id,0); // Gets 0 if the id is not present
鍵必須是唯一的惫确。不能對同一個鍵存放兩個值。如果對同一個鍵兩次調(diào)用put方法蚯舱,第二個值就會取代第一個值改化。實際上,put將返回用這個鍵參數(shù)存儲的上一個值枉昏。
remove方法用于從映射中刪除給定鍵對應的元素陈肛。size方法用于返回映射中的元素數(shù)。
要迭代處理映射的鍵和值兄裂,最容易的方法是使用forEach方法句旱。
更新映射項
處理映射時的一個難點就是更新映射項。正常情況下晰奖,可以得到與一個鍵關聯(lián)的原值谈撒,完成更新,再放回更新后的值匾南。不過啃匿,必須考慮一個特殊情況,即鍵第一次出現(xiàn)蛆楞。下面來看一個例子溯乒,使用一個映射統(tǒng)計一個單詞再文件中出現(xiàn)的頻度‰叮看到單詞(word)時橙数,我們將計數(shù)器增1,如下所示:
counts.put(word, counts.get(word) + 1)
這是可以的帅戒,不過一種情況除外:就是第一次看到word時灯帮。在這種情況下崖技,get會返回null,因此會出現(xiàn)NullPointerException異常钟哥。
作為一個簡單的補救迎献,可以使用getOrDefault方法:
counts.put(word, counts.getOrDefault(word, 0) + 1);
另一個方法是首先調(diào)用putIfAbsent方法。只有當鍵原先存在時才會放入一個值腻贰。
counts.putInfAbsent(word, 0 );
counts.put(word,counts.get(word) + 1);
不過還可以做得更好吁恍。merge方法可以簡化這個常見的操作。如果鍵原先不存在播演,下面的調(diào)用:
counts.merge(word, 1,Integer::sum);
將把word與1關聯(lián)冀瓦,否則使用Integer::sum函數(shù)組合原值和1(也就是將原值與1求和)。
映射視圖
集合框架不認為映射本身是一個集合写烤。(其他數(shù)據(jù)結構框架認為映射是一個鍵/值對集合翼闽,或者是由鍵索引的值集合。)
弱散列映射
設計WeakHashMap類是為了解決一個有趣的問題洲炊。如果有一個值感局,對應的鍵已經(jīng)不再使用了,將會出現(xiàn)什么情況呢暂衡?
首先询微,垃圾回收器跟蹤活動的對象。只要映射對象是活動的狂巢,其中的所有桶也是活動的撑毛,它們不能被回收。
WeakHashMap使用弱引用(weak references)保存鍵隧膘。WeakReference對象將引用保存到另外一個對象中代态,在這里,就是散列鍵疹吃。對于這種類型的對象,垃圾回收器用一種特有的方式進行處理西雀。通常萨驶,如果垃圾回收器發(fā)現(xiàn)某個特定的對象已經(jīng)沒有他人引用了,就將其回收艇肴。然而腔呜,如果某個對象只能由WeakReference引用,垃圾回收器仍然回收它再悼,但要將引用這個對象的弱引用放入隊列中核畴。WeakHashMap將周期性地檢查隊列,以便找出新添加地弱引用冲九。一個弱引用進入隊列意味著這個鍵不再被他人使用谤草,并且已經(jīng)被收集起來跟束。于是,WeakHashMap將刪除對應的條目丑孩。
鏈接散列集與映射
鏈接散列映射將用訪問順序冀宴,而不是插入順序,對映射條目進行迭代温学。每次調(diào)用get或put略贮,受到影響的條目將從當前的位置刪除,并放到條目鏈表的尾部(只有條目在鏈表中的位置會受影響仗岖,而散列表中的桶不會受影響逃延。一個條目總位于與鍵散列碼對應的桶中)。
訪問順序?qū)τ趯崿F(xiàn)高速緩存的“最近最少使用”原則十分重要轧拄。
枚舉集與映射
EnumSet是一個枚舉類型元素集的高效實現(xiàn)揽祥。由于枚舉類型只有有限個實例,所以EnumSet內(nèi)部用位序列實現(xiàn)紧帕。如果對應的值在集中盔然,則相應的位被置為1。
EnumMap是一個鍵類型為枚舉類型的映射是嗜。他可以直接且高效地用一個值數(shù)組實現(xiàn)愈案。
標識散列映射
類IdentityHashMap有特殊的作用。在這個類中鹅搪,鍵的散列值不是用hashCode函數(shù)計算的站绪,而是用System.identityHashCode方法計算的。這是Object.hashCode方法根據(jù)對象的內(nèi)存地址類計算散列碼所使用的方式丽柿。而且恢准,在對兩個對象進行比較時,IdentityHashMap類使用==甫题,而不是用equals馁筐。
也就是說,不同的鍵對象坠非,即使內(nèi)容相同敏沉,也被視為是不同的對象。在實現(xiàn)對象遍歷算法(如對象串行化)時炎码,這個類非常有用盟迟,可以用來跟蹤每個對象的遍歷狀況。