通常來說,Map是一個由鍵值對組成的數(shù)據(jù)結(jié)構(gòu),并且在一個 map 中,每一個 key 都只能出現(xiàn)一次.這篇文章總結(jié)了九個關(guān)于如何使用 Java Map 和它的實(shí)現(xiàn)類的問題.后文中為了方便,Map的類型都使用的是泛型.因此,這里僅會使用Map而不是指定數(shù)據(jù)類型的 Map,但是你仍然可以假設(shè)K 和V 都是可排序的.
0.Convert a Map to a List
在 Java 中Map 接口提供了三種 collection views:key set,value set,和 key-value set.這些都可以通過使用構(gòu)造方法或者allAll()被轉(zhuǎn)換成List.下面這個代碼段展示了如何從一個 map 中構(gòu)造一個ArrayList
// key list
List keyList = new ArrayList(map.keySet());
// value list
List valueList = new ArrayList(map.values());
// key-value list
List entryList = new ArrayList(map.entrySet());
1.Iterate over a Map
迭代訪問鍵值對是一種常見的遍歷 map 的方式.在 Java 中,這樣的鍵值對是存在 mapp 的Map.Entry 內(nèi)部類中.這個內(nèi)部類返回一個 key-value set,因此遍歷一個 map 最有效的方式是
for(Entry entry: map.entrySet()) {
// get key
K key = entry.getKey();
// get value
V value = entry.getValue();
}
同樣可以使用迭代器(Iterator)
Iterator itr = map.entrySet().iterator();
while(itr.hasNext()) {
Entry entry = itr.next();
// get key
K key = entry.getKey();
// get value
V value = entry.getValue();
}
2.Sort a Map on the keys
如何給map 的 key 進(jìn)行排序也是一個被頻繁提起的問題.一個有效的方式是將 Map.Entry 放在 list 之中,然后用 comparaotr 接口對 list 進(jìn)行排序
List list = new ArrayList(map.entrySet());
Collections.sort(list, new Comparator() {
@Override
public int compare(Entry e1, Entry e2) {
return e1.getKey().compareTo(e2.getKey());
}
});
另外一個方式是使用SortedMap.如果要使用SortMap 就得要保證所有的 key 都得實(shí)現(xiàn)Comparable 接口,或者接收一個Comparator.
SortMap 的一個實(shí)現(xiàn)類是TreeMap.它的構(gòu)造方法可以接收一個Comparator.下面這段代碼展示了如何將一個普通的 Map 轉(zhuǎn)成一個可排序的 Map.
SortedMap sortedMap = new TreeMap(new Comparator() {
@Override
public int compare(K k1, K k2) {
return k1.compareTo(k2);
}
});
sortedMap.putAll(map);
3.Sort a Map on the values
將map 放在list 中排序同樣也適用于這種情況,不過這次調(diào)用的是Entry.getValue()方法,來看一下下面這段代碼:
List list = new ArrayList(map.entrySet());
Collections.sort(list, new Comparator() {
@Override
public int compare(Entry e1, Entry e2) {
return e1.getValue().compareTo(e2.getValue());
}
});
同樣的,仍然可以使用一個 sorted map 來解決這個問題,不過這僅僅適用于所有的 value 也都是唯一的情況.在這種情況下,你可以將鍵值對倒轉(zhuǎn)使用,就像 key=value 和 value=key.但是這種情形具有太強(qiáng)的限制性,所以這里并不推薦使用
4.Initialize a static/immutable Map
如果你希望你的 map 在代碼中保持不變,將它變成一個 immutable map 是一個很不錯的方法.這種防御性的代碼技術(shù)將為你創(chuàng)造一個線程安全并且不會改變的 map.
我們可以通過使用 static 代碼塊來創(chuàng)建一個 static/immutable map,就像下面這樣
public class Test {
private static final Map map;
static {
map = new HashMap();
map.put(1, "one");
map.put(2, "two");
}
}
這段代碼的問題是即使 map 被聲明成了一個靜態(tài)常量,仍然可以通過Test.map.put
這種方式對 map 進(jìn)行修改.因此這并不是真正的 immutable.為了能創(chuàng)建一個真的不可變的 map,我們需要一個額外的匿名類,并且將這個匿名類復(fù)制到一個不可修改的 map 中,就像下面這段代碼一樣
public class Test {
private static final Map map;
static {
Map aMap = new HashMap();
aMap.put(1, "one");
aMap.put(2, "two");
map = Collections.unmodifiableMap(aMap);
}
}
如果對這段代碼調(diào)用Test.map.put
方法,將會拋出一個UnsupportedOperationException 異常.
Guava 庫提供了幾種不同的方式來創(chuàng)建 static/immutable collection.這里就不展開講解,如果感興趣,可以查看他們的git.
5.Difference between HashMap,TreeMap,and Hashtable
Java 中的Map 有三個主要的實(shí)現(xiàn)類,它們分別是:HashMap,TreeMap,和Hashtable.下面列舉一下這幾個實(shí)現(xiàn)類最主要的區(qū)別:
1.迭代的順序.HashMap和Hashtable是不保證取值的順序就是存值的順序的,甚至無法保證每次遍歷時的順序(即分兩次遍歷,返回順序也可能不同).但是TreeMap的迭代順序是依據(jù) key 的自然排序或者是給 key 一個Comparator.
2.key-value權(quán)限.HashMap允許null key和 null value,Hashtable即不允許null key也不允許null value.而TreeMap則是分情況的,如果它使用的是自然排序或者它的comparator不允許null key,則使用空值會拋出異常.
3.Synchronized.只有Hashtable是同步的,其他的都不是.因此,如果不需要線程安全的話,推薦使用HashMap.
如果想了解更多,可以查看這篇文章.
6.A Map with reverse view/lookup
有些時候,我們需要一個key-key的set,這意味著 map 的 value 就像 key 一樣,是不重復(fù)的.這種約束創(chuàng)造了一種可反轉(zhuǎn)的 map.所以我們可以通過 value 來找 key.這種數(shù)據(jù)結(jié)構(gòu)被稱為雙向映射(bidirectional map),但是不幸的是,JDK 并不支持這種 map.
不過 Apache 和 Guava 提供的工具類都有這種雙向映射的實(shí)現(xiàn),分別是BidiMap和BiMap.它們都強(qiáng)制約束了 key 與 value 之間一對一的關(guān)系
7.Shallow copy of a Map
Java 提供的大多數(shù) Map 的實(shí)現(xiàn)類都提供了一個復(fù)制其他map的構(gòu)造方法.但是這個復(fù)制的過程不是線程安全的.這意味著當(dāng)一個線程在復(fù)制一個 map,另外一個可能在修改它的結(jié)構(gòu).為了避免這種不同步的意外發(fā)生,map 需要提前使用Collections.synchronizedMap方法
Map copiedMap = Collections.synchronizedMap(map);
另外一個淺拷貝的方法是使用clone().然鵝甚至 Java 集合框架的設(shè)計(jì)者都不建議使用這種方式
I often provide a public clone method on concrete classes because people expect it. ... It's a shame that Cloneable is broken, but it happens. ... Cloneable is a weak spot, and I think people should be aware of its limitations.
正是由于這個原因,這里就不講如何通過clone()方法來實(shí)現(xiàn)map 的淺復(fù)制了.
8.Create an empty Map
如果要創(chuàng)建一個 immutable map,可以使用
map = Collections.emptyMap();
否則,可以使用任一一個實(shí)現(xiàn)類,比如
map = new HashMap();