guava引入了很多jdk沒有的、但我們發現明顯有用的新集合類型。這些新類型是為了和jdk集合架構共存,而沒有往jdk集合抽象中硬塞其他概念。作為一般規則,guava集合非常精準地遵循了jdk接口契約。
統計一個詞在文檔中出現了多少次,傳統的做法是這樣的:
這種寫法很笨拙,也容易出錯,并且不支援同時收集多種統計資訊,如總詞數。我們可以做的更好。
可以用兩種方式看待multiset:
沒有元素順序限制的arraylist<e>
map<e, integer>,鍵為元素,值為計數
guava的multiset api也結合考慮了這兩種方式:
當把multiset看成普通的collection時,它表現得就像無序的arraylist:
add(e)添加單個給定元素
iterator()傳回一個疊代器,包含multiset的所有元素(包括重複的元素)
size()傳回所有元素的總個數(包括重複的元素)
當把multiset看作map<e, integer>時,它也提供了符合性能期望的查詢操作:
count(object)傳回給定元素的計數。hashmultiset.count的複雜度為o(1),treemultiset.count的複雜度為o(log n)。
entryset()傳回set<multiset.entry<e>>,和map的entryset類似。
elementset()傳回所有不重複元素的set<e>,和map的keyset()類似。
所有multiset實作的記憶體消耗随着不重複元素的個數線性增長。
值得注意的是,除了極少數情況,multiset和jdk中原有的collection接口契約完全一緻——具體來說,treemultiset在判斷元素是否相等時,與treeset一樣用compare,而不是object.equals。另外特别注意,multiset.addall(collection)可以添加collection中的所有元素并進行計數,這比用for循環往map添加元素和計數友善多了。
<b>方法</b><b></b>
<b>描述</b><b></b>
給定元素在multiset中的計數
<a href="http://docs.guava-libraries.googlecode.com/git/javadoc/com/google/common/collect/multiset.html#elementset()">elementset()</a>
multiset中不重複元素的集合,類型為set<e>
<a href="http://docs.guava-libraries.googlecode.com/git/javadoc/com/google/common/collect/multiset.html#entryset()">entryset()</a>
和map的entryset類似,傳回set<multiset.entry<e>>,其中包含的entry支援getelement()和getcount()方法
<a href="http://docs.guava-libraries.googlecode.com/git/javadoc/com/google/common/collect/multiset.html#add(java.lang.object,int)">add(e, int)</a>
增加給定元素在multiset中的計數
<a href="http://docs.guava-libraries.googlecode.com/git-history/release/javadoc/com/google/common/collect/multiset.html#remove(java.lang.object,%20int)">remove(e, int)</a>
減少給定元素在multiset中的計數
<a href="http://docs.guava-libraries.googlecode.com/git-history/release/javadoc/com/google/common/collect/multiset.html#setcount(e,%20int)">setcount(e, int)</a>
設定給定元素在multiset中的計數,不可以為負數
size()
傳回集合元素的總個數(包括重複的元素)
請注意,multiset<e>不是map<e, integer>,雖然map可能是某些multiset實作的一部分。準确來說multiset是一種collection類型,并履行了collection接口相關的契約。關于multiset和map的顯著差別還包括:
multiset中的元素計數隻能是正數。任何元素的計數都不能為負,也不能是0。elementset()和entryset()視圖中也不會有這樣的元素。
multiset.size()傳回集合的大小,等同于所有元素計數的總和。對于不重複元素的個數,應使用elementset().size()方法。(是以,add(e)把multiset.size()增加1)
multiset.iterator()會疊代重複元素,是以疊代長度等于multiset.size()。
multiset支援直接增加、減少或設定元素的計數。setcount(elem, 0)等同于移除所有elem。
對multiset 中沒有的元素,multiset.count(elem)始終傳回0。
guava提供了多種multiset的實作,大緻對應jdk中map的各種實作:
<b>map</b>
<b>對應的</b><b>multiset</b><b></b>
<b>是否支援</b><b>null</b><b>元素</b><b></b>
hashmap
<a href="http://docs.guava-libraries.googlecode.com/git/javadoc/com/google/common/collect/hashmultiset.html">hashmultiset</a>
是
treemap
<a href="http://docs.guava-libraries.googlecode.com/git/javadoc/com/google/common/collect/treemultiset.html">treemultiset</a>
是(如果comparator支援的話)
linkedhashmap
<a href="http://docs.guava-libraries.googlecode.com/git/javadoc/com/google/common/collect/linkedhashmultiset.html">linkedhashmultiset</a>
concurrenthashmap
<a href="http://docs.guava-libraries.googlecode.com/git/javadoc/com/google/common/collect/concurrenthashmultiset.html">concurrenthashmultiset</a>
否
immutablemap
<a href="http://docs.guava-libraries.googlecode.com/git/javadoc/com/google/common/collect/immutablemultiset.html">immutablemultiset</a>
treemultiset實作sortedmultiset接口。在撰寫本文檔時,immutablesortedmultiset還在測試和gwt的相容性。
可以用兩種方式思考multimap的概念:”鍵-單個值映射”的集合:
a -> 1 a -> 2 a ->4 b -> 3 c -> 5
或者”鍵-值集合映射”的映射:
a -> [1, 2, 4] b -> 3 c -> 5
一般來說,multimap接口應該用第一種方式看待,但asmap()視圖傳回map<k, collection<v>>,讓你可以按另一種方式看待multimap。重要的是,不會有任何鍵映射到空集合:一個鍵要麼至少到一個值,要麼根本就不在multimap中。
很少會直接使用multimap接口,更多時候你會用listmultimap或setmultimap接口,它們分别把鍵映射到list或set。
對值視圖集合進行的修改最終都會反映到底層的multimap。例如:
其他(更直接地)修改multimap的方法有:
<b>方法簽名</b><b></b>
<b>等價于</b><b></b>
<a href="http://docs.guava-libraries.googlecode.com/git-history/release/javadoc/com/google/common/collect/multimap.html#put(k,%20v)">put(k, v)</a>
添加鍵到單個值的映射
multimap.get(key).add(value)
<a href="http://docs.guava-libraries.googlecode.com/git-history/release/javadoc/com/google/common/collect/multimap.html#putall(k,%20java.lang.iterable)">putall(k, iterable<v>)</a>
依次添加鍵到多個值的映射
iterables.addall(multimap.get(key), values)
<a href="http://docs.guava-libraries.googlecode.com/git-history/release/javadoc/com/google/common/collect/multimap.html#remove(java.lang.object,%20java.lang.object)">remove(k, v)</a>
移除鍵到值的映射;如果有這樣的鍵值并成功移除,傳回true。
multimap.get(key).remove(value)
<a href="http://docs.guava-libraries.googlecode.com/git-history/release/javadoc/com/google/common/collect/multimap.html#removeall(java.lang.object)">removeall(k)</a>
清除鍵對應的所有值,傳回的集合包含所有之前映射到k的值,但修改這個集合就不會影響multimap了。
multimap.get(key).clear()
<a href="http://docs.guava-libraries.googlecode.com/git-history/release/javadoc/com/google/common/collect/multimap.html#replacevalues(k,%20java.lang.iterable)">replacevalues(k, iterable<v>)</a>
清除鍵對應的所有值,并重新把key關聯到iterable中的每個元素。傳回的集合包含所有之前映射到k的值。
multimap.get(key).clear(); iterables.addall(multimap.get(key), values)
multimap還支援若幹強大的視圖:
multimap<k, v>不是map<k,collection<v>>,雖然某些multimap實作中可能使用了map。它們之間的顯著差別包括:
multimap.get(key)總是傳回非null、但是可能空的集合。這并不意味着multimap為相應的鍵花費記憶體建立了集合,而隻是提供一個集合視圖友善你為鍵增加映射值——譯者注:如果有這樣的鍵,傳回的集合隻是包裝了multimap中已有的集合;如果沒有這樣的鍵,傳回的空集合也隻是持有multimap引用的棧對象,讓你可以用來操作底層的multimap。是以,傳回的集合不會占據太多記憶體,資料實際上還是存放在multimap中。
當且僅當有值映射到鍵時,multimap.containskey(key)才會傳回true。尤其需要注意的是,如果鍵k之前映射過一個或多個值,但它們都被移除後,multimap.containskey(key)會傳回false。
multimap.entries()傳回multimap中所有”鍵-單個值映射”——包括重複鍵。如果你想要得到所有”鍵-值集合映射”,請使用asmap().entryset()。
multimap.size()傳回所有”鍵-單個值映射”的個數,而非不同鍵的個數。要得到不同鍵的個數,請改用multimap.keyset().size()。
multimap提供了多種形式的實作。在大多數要使用map<k, collection<v>>的地方,你都可以使用它們:
<b>實作</b><b></b>
<b>鍵行為類似</b><b></b>
<b>值行為類似</b><b></b>
arraylist
hashset
linkedhashmap*
linkedlist*
treeset
<a href="http://docs.guava-libraries.googlecode.com/git/javadoc/com/google/common/collect/immutablelistmultimap.html">immutablelistmultimap</a>
immutablelist
immutableset
除了兩個不可變形式的實作,其他所有實作都支援null鍵和null值
*linkedlistmultimap.entries()保留了所有鍵和值的疊代順序。詳情見doc連結。
**linkedhashmultimap保留了映射項的插入順序,包括鍵插入的順序,以及鍵映射的所有值的插入順序。
請注意,并非所有的multimap都和上面列出的一樣,使用map<k, collection<v>>來實作(特别是,一些multimap實作用了自定義的hashtable,以最小化開銷)
傳統上,實作鍵值對的雙向映射需要維護兩個單獨的map,并保持它們間的同步。但這種方式很容易出錯,而且對于值已經在map中的情況,會變得非常混亂。例如:
<b>鍵</b><b>–</b><b>值實作</b><b></b>
<b>值</b><b>–</b><b>鍵實作</b><b></b>
<b>對應的</b><b>bimap</b><b>實作</b><b></b>
enummap
<a href="http://docs.guava-libraries.googlecode.com/git-history/release/javadoc/com/google/common/collect/enumbimap.html">enumbimap</a>
<a href="http://docs.guava-libraries.googlecode.com/git-history/release/javadoc/com/google/common/collect/enumhashbimap.html">enumhashbimap</a>
table有如下幾種實作:
classtoinstancemap有唯一的泛型參數,通常稱為b,代表map支援的所有類型的上界。例如:
從技術上講,classtoinstancemap<b>實作了map<class<? extends b>, b>——或者換句話說,是一個映射b的子類型到對應執行個體的map。這讓classtoinstancemap包含的泛型聲明有點令人困惑,但請記住b始終是map所支援類型的上界——通常b就是object。
rangeset描述了一組不相連的、非空的區間。當把一個區間添加到可變的rangeset時,所有相連的區間會被合并,空區間會被忽略。例如:
注:rangeset不支援gwt,也不支援jdk5和更早版本;因為,rangeset需要充分利用jdk6中navigablemap的特性。
rangeset的實作支援非常廣泛的視圖:
complement():傳回rangeset的補集視圖。complement也是rangeset類型,包含了不相連的、非空的區間。
subrangeset(range<c>):傳回rangeset與給定range的交集視圖。這擴充了傳統排序集合中的headset、subset和tailset操作。
asranges():用set<range<c>>表現rangeset,這樣可以周遊其中的range。
asset(discretedomain<c>)(僅immutablerangeset支援):用immutablesortedset<c>表現rangeset,以區間中所有元素的形式而不是區間本身的形式檢視。(這個操作不支援discretedomain 和rangeset都沒有上邊界,或都沒有下邊界的情況)
為了友善操作,rangeset直接提供了若幹查詢方法,其中最突出的有:
contains(c):rangeset最基本的操作,判斷rangeset中是否有任何區間包含給定元素。
rangecontaining(c):傳回包含給定元素的區間;若沒有這樣的區間,則傳回null。
encloses(range<c>):簡單明了,判斷rangeset中是否有任何區間包括給定區間。
span():傳回包括rangeset中所有區間的最小區間。
rangemap描述了”不相交的、非空的區間”到特定值的映射。和rangeset不同,rangemap不會合并相鄰的映射,即便相鄰的區間映射到相同的值。例如:
rangemap提供兩個視圖:
asmapofranges():用map<range<k>, v>表現rangemap。這可以用來周遊rangemap。
subrangemap(range<k>):用rangemap類型傳回rangemap與給定range的交集視圖。這擴充了傳統的headmap、submap和tailmap操作。