不可變集合類
public static final ImmutableSet<String> COLOR_NAMES = ImmutableSet.of(
"red",
"orange",
"yellow",
"green",
"blue",
"purple");
class Foo {
Set<Bar> bars;
Foo(Set<Bar> bars) {
this.bars = ImmutableSet.copyOf(bars); // defensive copy!
}
}
為什么要使用不可變集合
不可變對象有很多優(yōu)點廓握,包括:
當(dāng)對象被不可信的庫調(diào)用時过椎,不可變形式是安全的;
不可變對象被多個線程調(diào)用時浙芙,不存在競態(tài)條件問題
不可變集合不需要考慮變化捐迫,因此可以節(jié)省時間和空間乾翔。所有不可變的集合都比它們的可變形式有更好的內(nèi)存利用率(分析和測試細節(jié));
不可變對象因為有固定不變弓乙,可以作為常量來安全使用末融。
創(chuàng)建對象的不可變拷貝是一項很好的防御性編程技巧。Guava為所有JDK標(biāo)準(zhǔn)集合類型和Guava新集合類型都提供了簡單易用的不可變版本暇韧。
JDK也提供了Collections.unmodifiableXXX方法把集合包裝為不可變形式勾习,但我們認為不夠好:
笨重而且累贅:不能舒適地用在所有想做防御性拷貝的場景;
不安全:要保證沒人通過原集合的引用進行修改懈玻,返回的集合才是事實上不可變的巧婶;
低效:包裝過的集合仍然保有可變集合的開銷,比如并發(fā)修改的檢查、散列表的額外空間艺栈,等等英岭。
如果你沒有修改某個集合的需求,或者希望某個集合保持不變時湿右,把它防御性地拷貝到不可變集合是個很好的實踐诅妹。
重要提示:所有Guava不可變集合的實現(xiàn)都不接受null值。我們對Google內(nèi)部的代碼庫做過詳細研究毅人,發(fā)現(xiàn)只有5%的情況需要在集合中允許null元素吭狡,剩下的95%場景都是遇到null值就快速失敗。如果你需要在不可變集合中使用null丈莺,請使用JDK中的Collections.unmodifiableXXX方法划煮。更多細節(jié)建議請參考“使用和避免null”。
怎么使用不可變集合
不可變集合可以用如下多種方式創(chuàng)建:
1. copyOf方法缔俄,如ImmutableSet.copyOf(set);
of方法弛秋,如ImmutableSet.of(“a”, “b”, “c”)或 ImmutableMap.of(“a”, 1, “b”, 2);
2. Builder工具,如
public static final ImmutableSet<Color> GOOGLE_COLORS =
ImmutableSet.<Color>builder()
.addAll(WEBSAFE_COLORS)
.add(new Color(0, 191, 255))
.build();
此外俐载,對有序不可變集合來說蟹略,排序是在構(gòu)造集合的時候完成的,如:
3. ImmutableSortedSet.of("a", "b", "c", "a", "d", "b");
會在構(gòu)造時就把元素排序為a, b, c, d瞎疼。
我測試發(fā)現(xiàn): of方法得到的不可變集合科乎,如果原集合增加,它也隨之增加贼急,其他的不受影響。
比想象中更智能的copyOf
請注意捏萍,ImmutableXXX.copyOf方法會嘗試在安全的時候避免做拷貝——實際的實現(xiàn)細節(jié)不詳太抓,但通常來說是很智能的,比如:
ImmutableSet<String> foobar = ImmutableSet.of("foo", "bar", "baz");
thingamajig(foobar);
void thingamajig(Collection<String> collection) {
ImmutableList<String> defensiveCopy = ImmutableList.copyOf(collection);
...
}
在這段代碼中令杈,ImmutableList.copyOf(foobar)會智能地直接返回foobar.asList(),它是一個ImmutableSet的常量時間復(fù)雜度的List視圖走敌。
作為一種探索,ImmutableXXX.copyOf(ImmutableCollection)會試圖對如下情況避免線性時間拷貝:
- 在常量時間內(nèi)使用底層數(shù)據(jù)結(jié)構(gòu)是可能的——例如逗噩,ImmutableSet.copyOf(ImmutableList)就不能在常量時間內(nèi)完成掉丽。
- 不會造成內(nèi)存泄露——例如,你有個很大的不可變集合ImmutableList<String>
hugeList异雁, ImmutableList.copyOf(hugeList.subList(0, 10))就會顯式地拷貝捶障,以免不必要地持有hugeList的引用。 - 不改變語義——所以ImmutableSet.copyOf(myImmutableSortedSet)會顯式地拷貝纲刀,因為和基于比較器的ImmutableSortedSet相比项炼,ImmutableSet對hashCode()和equals有不同語義。
- 在可能的情況下避免線性拷貝,可以最大限度地減少防御性編程風(fēng)格所帶來的性能開銷锭部。
asList視圖
所有不可變集合都有一個asList()方法提供ImmutableList視圖暂论,來幫助你用列表形式方便地讀取集合元素。例如拌禾,你可以使用sortedSet.asList().get(k)從ImmutableSortedSet中讀取第k個最小元素取胎。
asList()返回的ImmutableList通常是——并不總是——開銷穩(wěn)定的視圖實現(xiàn),而不是簡單地把元素拷貝進List湃窍。也就是說扼菠,asList返回的列表視圖通常比一般的列表平均性能更好,比如坝咐,在底層集合支持的情況下循榆,它總是使用高效的contains方法。
新集合類型
Multiset
統(tǒng)計一個詞在文檔中出現(xiàn)了多少次墨坚,傳統(tǒng)的做法是這樣的:
Map<String, Integer> counts = new HashMap<String, Integer>();
for (String word : words) {
Integer count = counts.get(word);
if (count == null) {
counts.put(word, 1);
} else {
counts.put(word, count + 1);
}
}
這種寫法很笨拙秧饮,也容易出錯,并且不支持同時收集多種統(tǒng)計信息泽篮,如總詞數(shù)盗尸。我們可以做的更好。
Guava提供了一個新集合類型 Multiset帽撑,它可以多次添加相等的元素泼各。維基百科從數(shù)學(xué)角度這樣定義Multiset:”集合[set]概念的延伸,它的元素可以重復(fù)出現(xiàn)…與集合[set]相同而與元組[tuple]相反的是亏拉,Multiset元素的順序是無關(guān)緊要的:Multiset {a, a, b}和{a, b, a}是相等的”扣蜻。——譯者注:這里所說的集合[set]是數(shù)學(xué)上的概念及塘,Multiset繼承自JDK中的Collection接口莽使,而不是Set接口,所以包含重復(fù)元素并沒有違反原有的接口契約笙僚。
可以用兩種方式看待Multiset:
- 沒有元素順序限制的ArrayList<E>
- Map<E, Integer>芳肌,鍵為元素,值為計數(shù)
Guava的Multiset API也結(jié)合考慮了這兩種方式:
當(dāng)把Multiset看成普通的Collection時肋层,它表現(xiàn)得就像無序的ArrayList:
- add(E)添加單個給定元素
- iterator()返回一個迭代器亿笤,包含Multiset的所有元素(包括重復(fù)的元素)
- size()返回所有元素的總個數(shù)(包括重復(fù)的元素)
當(dāng)把Multiset看作Map<E, Integer>時,它也提供了符合性能期望的查詢操作:
- count(Object)返回給定元素的計數(shù)栋猖。HashMultiset.count的復(fù)雜度為O(1)净薛,TreeMultiset.count的復(fù)雜度為O(log n)。
- entrySet()返回Set<Multiset.Entry<E>>掂铐,和Map的entrySet類似罕拂。
- elementSet()返回所有不重復(fù)元素的Set<E>揍异,和Map的keySet()類似。
- 所有Multiset實現(xiàn)的內(nèi)存消耗隨著不重復(fù)元素的個數(shù)線性增長爆班。
值得注意的是衷掷,除了極少數(shù)情況,Multiset和JDK中原有的Collection接口契約完全一致——具體來說柿菩,TreeMultiset在判斷元素是否相等時戚嗅,與TreeSet一樣用compare,而不是Object.equals枢舶。另外特別注意懦胞,Multiset.addAll(Collection)可以添加Collection中的所有元素并進行計數(shù),這比用for循環(huán)往Map添加元素和計數(shù)方便多了凉泄。
SortedMultiset
SortedMultiset 是Multiset 接口的變種躏尉,它支持高效地獲取指定范圍的子集。比方說后众,你可以用 latencies.subMultiset(0,BoundType.CLOSED, 100, BoundType.OPEN).size()來統(tǒng)計你的站點中延遲在100毫秒以內(nèi)的訪問胀糜,然后把這個值和latencies.size()相比,以獲取這個延遲水平在總體訪問中的比例蒂誉。
TreeMultiset實現(xiàn)SortedMultiset接口教藻。
Multimap
每個有經(jīng)驗的Java程序員都在某處實現(xiàn)過Map<K, List<V>>或Map<K, Set<V>>,并且要忍受這個結(jié)構(gòu)的笨拙右锨。例如括堤,Map<K, Set<V>>通常用來表示非標(biāo)定有向圖。Guava的 Multimap可以很容易地把一個鍵映射到多個值绍移。換句話說悄窃,Multimap是把鍵映射到任意多個值的一般方式。
可以用兩種方式思考Multimap的概念:”鍵-單個值映射”的集合:
a -> 1 a -> 2 a ->4 b -> 3 c -> 5
或者”鍵-值集合映射”的映射:
a -> [1, 2, 4] b -> 3 c -> 5
一般來說登夫,Multimap接口應(yīng)該用第一種方式看待广匙,但asMap()視圖返回Map<K, Collection<V>>,讓你可以按另一種方式看待Multimap恼策。重要的是,不會有任何鍵映射到空集合:一個鍵要么至少到一個值潮剪,要么根本就不在Multimap中涣楷。
很少會直接使用Multimap接口,更多時候你會用ListMultimap或SetMultimap接口抗碰,它們分別把鍵映射到List或Set狮斗。
修改Multimap
Multimap.get(key) 以集合形式返回鍵所對應(yīng)的值視圖,即使沒有任何對應(yīng)的值弧蝇,也會返回空集合碳褒。ListMultimap.get(key)返回List折砸,SetMultimap.get(key)返回Set。
對值視圖集合進行的修改最終都會反映到底層的Multimap沙峻。
Multimap的視圖
Multimap還支持若干強大的視圖:
- asMap為Multimap<K, V>提供Map<K,Collection<V>>形式的視圖睦授。返回的Map支持remove操作,并且會反映到底層的Multimap摔寨,但它不支持put或putAll操作去枷。更重要的是,如果你想為Multimap中沒有的鍵返回null是复,而不是一個新的删顶、可寫的空集合,你就可以使用asMap().get(key)淑廊。(你可以并且應(yīng)當(dāng)把asMap.get(key)返回的結(jié)果轉(zhuǎn)化為適當(dāng)?shù)募项愋汀鏢etMultimap.asMap.get(key)的結(jié)果轉(zhuǎn)為Set逗余,ListMultimap.asMap.get(key)的結(jié)果轉(zhuǎn)為List——Java類型系統(tǒng)不允許ListMultimap直接為asMap.get(key)返回List——譯者注:也可以用Multimaps中的asMap靜態(tài)方法幫你完成類型轉(zhuǎn)換)
- entries用Collection<Map.Entry<K, V>>返回Multimap中所有”鍵-單個值映射”——包括重復(fù)鍵。(對SetMultimap季惩,返回的是Set)
- keySet用Set表示Multimap中所有不同的鍵录粱。
- keys用Multiset表示Multimap中的所有鍵,每個鍵重復(fù)出現(xiàn)的次數(shù)等于它映射的值的個數(shù)蜀备」匾。可以從這個Multiset中移除元素,但不能做添加操作碾阁;移除操作會反映到底層的Multimap输虱。
- values()用一個”扁平”的Collection<V>包含Multimap中的所有值。這有一點類似于Iterables.concat(multimap.asMap().values())脂凶,但它直接返回了單個Collection宪睹,而不像multimap.asMap().values()那樣是按鍵區(qū)分開的Collection。
BiMap
1.雙向映射
2.value唯一蚕钦。 插入相同value報錯亭病,forcePut()強制插入會更改key
3.inverse()方法翻轉(zhuǎn)key與value。不返回新對象嘶居,保持一種視圖關(guān)聯(lián)罪帖,操作會互相影響。
Table
多個索引的數(shù)據(jù)結(jié)構(gòu)的邮屁,通常情況下整袁,我們只能用這種丑陋的Map<FirstName, Map<LastName, Person>>來實現(xiàn)。guava的table提供更方便的實現(xiàn)佑吝。類似一個表的結(jié)構(gòu)坐昙,并且可以切換視圖。
public void test(){
Table<String, Integer, String> aTable = HashBasedTable.create();
for (char a = 'A'; a <= 'C'; ++a) {
for (Integer b = 1; b <= 3; ++b) {
aTable.put(Character.toString(a), b, String.format("%c%d", a, b));
}
}
System.out.println(aTable);
System.out.println(aTable.rowKeySet());
System.out.println(aTable.columnKeySet());
System.out.println(aTable.row("A"));
System.out.println(aTable.get("B",2));
System.out.println(aTable.contains("A",4));
System.out.println(aTable.columnMap());
System.out.println(aTable.rowMap());
System.out.println(aTable.size());
System.out.println(aTable.remove("A",1));
System.out.println(aTable);
}