Guava 引入了不少 JDK 沒有的、但咱們發現明顯有用的新集合類型。這些新類型是爲了和 JDK 集合框架共存,而沒有往 JDK 集合抽象中硬塞其餘概念。做爲通常規則,Guava 集合很是精準地遵循了 JDK 接口契約。java
統計一個詞在文檔中出現了多少次,傳統的作法是這樣的:數組
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); } }
這種寫法很笨拙,也容易出錯,而且不支持同時收集多種統計信息,如總詞數。安全
Guava 提供了一個新集合類型 Multiset,它能夠屢次添加相等的元素。維基百科從數學角度這樣定義 Multiset:集合[set]概念的延伸,它的元素能夠重複出現…與集合[set]相同而與元組[tuple]相反的是,Multiset 元素的順序是可有可無的:Multiset {a, a, b}和{a, b, a}是相等的」。——譯者注:這裏所說的集合[set]是數學上的概念,Multiset繼承自 JDK 中的 Collection 接口,而不是 Set 接口,因此包含重複元素並無違反原有的接口契約。數據結構
Multiset和Set的區別就是能夠保存多個相同的對象。在JDK中,List和Set有一個基本的區別,就是List能夠包含多個相同對象,且是有順序的,而Set不能有重複,且不保證順序(有些實現有順序,例如LinkedHashSet和SortedSet等)因此Multiset佔據了List和Set之間的一個灰色地帶:容許重複,可是不保證順序。框架
常見使用場景:Multiset有一個有用的功能,就是跟蹤每種對象的數量,因此你能夠用來進行數字統計。 常見的普通實現方式以下:工具
public void testWordCount() { String strWorld = "wer|dffd|ddsa|dfd|dreg|de|dr|ce|ghrt|cf|gt|ser|tg|ghrt|cf|gt|" + "ser|tg|gt|kldf|dfg|vcd|fg|gt|ls|lser|dfr|wer|dffd|ddsa|dfd|dreg|de|dr|" + "ce|ghrt|cf|gt|ser|tg|gt|kldf|dfg|vcd|fg|gt|ls|lser|dfr"; String[] words = strWorld.split("\\|"); Map<String, Integer> countMap = new HashMap<String, Integer>(); for (String word : words) { Integer count = countMap.get(word); if (count == null) { countMap.put(word, 1); } else { countMap.put(word, count + 1); } } System.out.println("countMap:"); for (String key : countMap.keySet()) { System.out.println(key + " count:" + countMap.get(key)); } }
上面的代碼實現的功能很是簡單,用於記錄字符串在數組中出現的次數。這種場景在實際的開發過程仍是容易常常出現的,若是使用實現Multiset接口的具體類就能夠很容易實現以上的功能需求:優化
public void testMultsetWordCount() { String strWorld = "wer|dfd|dd|dfd|dda|de|dr"; String[] words = strWorld.split("\\|"); List<String> wordList = new ArrayList<String>(); for (String word : words) { wordList.add(word); } Multiset<String> wordsMultiset = HashMultiset.create(); wordsMultiset.addAll(wordList); for (String key : wordsMultiset.elementSet()) { System.out.println(key + " count:" + wordsMultiset.count(key)); } }
Multiset接口定義的接口主要有:spa
Multiset不是Mapcode
須要注意的是Multiset不是一個Map<E,Integer>,儘管Multiset提供一部分相似的功能實現。其它值得關注的差異有:orm
Multiset中的元素的重複個數只會是正數,且最大不會超過Integer.MAX_VALUE。設定計數爲0的元素將不會出現multiset中,也不會出現elementSet()和entrySet()的返回結果中。
multiset.size() 方法返回的是全部的元素的總和,至關因而將全部重複的個數相加。若是須要知道每一個元素的個數可使用elementSet().size()獲得.(於是調用add(E)方法會是multiset.size()增長1)。
multiset.iterator() 會循環迭代每個出現的元素,迭代的次數與multiset.size()相同。 iterates over each occurrence of each element, so the length of the iteration is equal to multiset.size()。
Multiset 支持添加、移除多個元素以及從新設定元素的個數。執行setCount(element,0)至關於移除multiset中全部的相同元素。
調用multiset.count(elem)方法時,若是該元素不在該集中,那麼返回的結果只會是0。
Multiset的實現Guava提供了Multiset的多種實現,這些實現基本對應了JDK中Map的實現:
Map | 對應的Multiset | 是否支持null元素 |
---|---|---|
HashMap | HashMultiset | Yes |
TreeMap | TreeMultiset | Yes(若是 comparator 支持的話) |
LinkedHashMap | LinkedHashMultiset | Yes |
ConcurrentHashMap | ConcurrentHashMultiset | No |
ImmutableMap | ImmutableMultiset | No |
在平常的開發工做中,咱們有的時候須要構造像Map<K, List<V>>或者Map<K, Set<V>>這樣比較複雜的集合類型的數據結構,以便作相應的業務邏輯處理。
像 Map<String, List<StudentScore>> StudentScoreMap = new HashMap<String, List<StudentScore>>()這樣的數據結構,本身實現起來太麻煩,你須要檢查key是否存在,不存在時則建立一個,存在時在List後面添加上一個。這個過程是比較痛苦的,若是你但願檢查List中的對象是否存在,刪除一個對象,或者遍歷整個數據結構,那麼則須要更多的代碼來實現。
Guava的Multimap就提供了一個方便地把一個鍵對應到多個值的數據結構。讓咱們能夠簡單優雅的實現上面複雜的數據結構,讓咱們的精力和時間放在實現業務邏輯上,而不是在數據結構上,下面咱們具體來看看Multimap的相關知識點。
public class MultimapTest { Map<String, List<StudentScore>> StudentScoreMap = new HashMap<String, List<StudentScore>>(); public void testStudentScore(){ for(int i=10;i<20;i++){ StudentScore studentScore=new StudentScore(); studentScore.CourseId=1001+i; studentScore.score=100-i; addStudentScore("peida",studentScore); } System.out.println("StudentScoreMap:"+StudentScoreMap.size()); System.out.println("StudentScoreMap:"+StudentScoreMap.containsKey("peida")); System.out.println("StudentScoreMap:"+StudentScoreMap.containsKey("jerry")); System.out.println("StudentScoreMap:"+StudentScoreMap.size()); System.out.println("StudentScoreMap:"+StudentScoreMap.get("peida").size()); List<StudentScore> StudentScoreList=StudentScoreMap.get("peida"); if(StudentScoreList!=null&&StudentScoreList.size()>0){ for(StudentScore stuScore:StudentScoreList){ System.out.println("stuScore one:"+stuScore.CourseId+" score:"+stuScore.score); } } } public void addStudentScore(final String stuName,final StudentScore studentScore) { List<StudentScore> stuScore = StudentScoreMap.get(stuName); if (stuScore == null) { stuScore = new ArrayList<StudentScore>(); StudentScoreMap.put(stuName, stuScore); } stuScore.add(studentScore); } } class StudentScore{ int CourseId; int score; } }
上面的代碼和數據結構用Multimap來實現:
public class MultimapTest { Map<String, List<StudentScore>> StudentScoreMap = new HashMap<String, List<StudentScore>>(); public void teststuScoreMultimap(){ Multimap<String,StudentScore> scoreMultimap = ArrayListMultimap.create(); for(int i=10;i<20;i++){ StudentScore studentScore=new StudentScore(); studentScore.CourseId=1001+i; studentScore.score=100-i; scoreMultimap.put("peida",studentScore); } System.out.println("scoreMultimap:"+scoreMultimap.size()); System.out.println("scoreMultimap:"+scoreMultimap.keys()); } } class StudentScore{ int CourseId; int score; } }
Multimap也支持一系列強大的視圖功能:
儘管Multimap的實現用到了Map,但Multimap<K, V>不是Map<K, Collection<V>>。由於二者有明顯區別:
Multimap提供了豐富的實現,因此你能夠用它來替代程序裏的Map<K, Collection<V>>,具體的實現以下:
實現 | 鍵行爲相似 | 值行爲相似 |
---|---|---|
ArrayListMultimap | HashMap | ArrayList |
HashMultimap | HashMap | HashSet |
LinkedListMultimap* | LinkedHashMap* | LinkedList* |
LinkedHashMultimap** | LinkedHashMap | LinkedHashMap |
TreeMultimap | HashMap | TreeSet |
ImmutableListMultimap | ImmutableMap | ImmutableList |
ImmutableSetMultimap | ImmutableMap | ImmutableSet |
除了兩個不可變形式的實現,其餘全部實現都支持 null 鍵和 null 值*LinkedListMultimap.entries()保留了全部鍵和值的迭代順序。詳情見 doc 連接。
**LinkedHashMultimap 保留了映射項的插入順序,包括鍵插入的順序,以及鍵映射的全部值的插入順序。
請注意,並不是全部的 Multimap 都和上面列出的同樣,使用 Map<K, Collection>來實現(特別是,一些 Multimap 實現用了自定義的 hashTable,以最小化開銷)若是你想要更大的定製化,請用Multimaps.newMultimap(Map, Supplier)或 list 和 set 版本,使用自定義的 Collection、List 或 Set 實現 Multimap。
傳統上,實現鍵值對的雙向映射須要維護兩個單獨的 map,並保持它們間的同步。但這種方式很容易出錯,並且對於值已經在 map 中的狀況,會變得很是混亂。例如:
出現下面一種場景的狀況,咱們就須要額外編寫一些代碼了。首先來看下面一種表示標識序號和文件名的map結構。
Map<Integer,String> logfileMap = Maps.newHashMap(); logfileMap.put(1,"a.log"); logfileMap.put(2,"b.log"); logfileMap.put(3,"c.log"); System.out.println("logfileMap:"+logfileMap);
當咱們須要經過序號查找文件名,很簡單。可是若是咱們須要經過文件名查找其序號時,咱們就不得不遍歷map了。固然咱們還能夠編寫一段Map倒轉的方法來幫助實現倒置的映射關係。
public static <S,T> Map<T,S> getInverseMap(Map<S,T> map) { Map<T,S> inverseMap = new HashMap<T,S>(); for(Entry<S,T> entry: map.entrySet()) { inverseMap.put(entry.getValue(), entry.getKey()); } return inverseMap; }
public void logMapTest(){ Map<Integer,String> logfileMap = Maps.newHashMap(); logfileMap.put(1,"a.log"); logfileMap.put(2,"b.log"); logfileMap.put(3,"c.log"); System.out.println("logfileMap:"+logfileMap); Map<String,Integer> logfileInverseMap = Maps.newHashMap(); logfileInverseMap=getInverseMap(logfileMap); System.out.println("logfileInverseMap:"+logfileInverseMap); }
上面的代碼能夠幫助咱們實現map倒轉的要求,可是還有一些咱們須要考慮的問題:
在這種狀況下須要考慮的業務之外的內容就增長了,編寫的代碼也變得不那麼易讀了。這時咱們就能夠考慮使用Guava中的BiMap了。
Bimap使用很是的簡單,對於上面的這種使用場景,咱們能夠用很簡單的代碼就實現了:
public void BimapTest(){ BiMap<Integer,String> logfileMap = HashBiMap.create(); logfileMap.put(1,"a.log"); logfileMap.put(2,"b.log"); logfileMap.put(3,"c.log"); System.out.println("logfileMap:"+logfileMap); BiMap<String,Integer> filelogMap = logfileMap.inverse(); System.out.println("filelogMap:"+filelogMap); }
logfileMap.put(5,"d.log") 會拋出java.lang.IllegalArgumentException: value already present: d.log的錯誤。若是咱們確實須要插入重複的value值,那能夠選擇forcePut方法。可是咱們須要注意的是前面的key也會被覆蓋了。
public void BimapTest(){ BiMap<Integer,String> logfileMap = HashBiMap.create(); logfileMap.put(1,"a.log"); logfileMap.put(2,"b.log"); logfileMap.put(3,"c.log"); logfileMap.put(4,"d.log"); logfileMap.forcePut(5,"d.log"); System.out.println("logfileMap:"+logfileMap); }
inverse方法會返回一個反轉的BiMap,可是注意這個反轉的map不是新的map對象,它實現了一種視圖關聯,這樣你對於反轉後的map的全部操做都會影響原先的map對象。
鍵–值實現 | 值–鍵實現 | 對應的BiMap實現 |
---|---|---|
HashMap | HashMap | HashBiMap |
ImmutableMap | ImmutableMap | ImmutableBiMap |
EnumMap | EnumMap | EnumBiMap |
EnumMap | HashMap | EnumHashBiMap |
注:Maps 類中還有一些諸如 synchronizedBiMap 的 BiMap 工具方法。 |
一般來講,當你想使用多個鍵作索引的時候,你可能會用相似 Map<FirstName, Map<LastName, Person>>的實現,這種方式很醜陋,使用上也不友好。Guava 爲此提供了新集合類型 Table,它有兩個支持全部類型的鍵:」行」和」列」。Table 提供多種視圖,以便你從各類角度使用它:
Table 有以下幾種實現:
public void TableTest(){ 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.column(2)); System.out.println(aTable.row("B")); System.out.println(aTable.get("B", 2)); System.out.println(aTable.contains("D", 1)); System.out.println(aTable.containsColumn(3)); System.out.println(aTable.containsRow("C")); System.out.println(aTable.columnMap()); System.out.println(aTable.rowMap()); System.out.println(aTable.remove("B", 3)); }
ClassToInstanceMap 是一種特殊的 Map:它的鍵是類型,而值是符合鍵所指類型的對象。
爲了擴展 Map 接口,ClassToInstanceMap 額外聲明瞭兩個方法:T getInstance(Class) 和 T putInstance(Class, T),從而避免強制類型轉換,同時保證了類型安全。
ClassToInstanceMap 有惟一的泛型參數,一般稱爲 B,表明 Map 支持的全部類型的上界。
ClassToInstanceMap<Number> numberDefaults = MutableClassToInstanceMap.create(); numberDefaults.putInstance(Integer.class, Integer.valueOf(0));
大體意思爲:
有時,咱們的映射鍵並非全部類型都相同:它們是類型,如鍵分別是Integer、Bigdecimal等,可是他們都是屬於Number類型.咱們若是但願將它們映射到該類型的值,就可使用Guava爲此提供的新集合類型 - ClassToInstanceMap。
值得注意的是:
public void classToInstanceMapTest(){ //可存放全部數字類型的map ClassToInstanceMap<Number> numberDefaults= MutableClassToInstanceMap.create(); numberDefaults.putInstance(Integer.class, 1); numberDefaults.putInstance(Double.class, 1.00); numberDefaults.putInstance(BigDecimal.class, new BigDecimal("52")); numberDefaults.put(Integer.class, 2); numberDefaults.replace(Integer.class, 3); //修改 numberDefaults.getInstance(Integer.class); //獲取value 3 //經過傳入新值和舊值計算,修改舊值,例: 將Integer,class的值+3 numberDefaults.merge(Integer.class,2,(x,y)->x.intValue()+y.intValue()); numberDefaults.forEach((x,y)-> System.out.println(x+","+y)); }
RangeSet描述了一組不相連的、非空的區間。當把一個區間添加到可變的RangeSet時,全部相連的區間會被合併,空區間會被忽略。例如:
RangeSet<Integer> rangeSet = TreeRangeSet.create(); rangeSet.add(Range.closed(1, 10)); // {[1,10]} rangeSet.add(Range.closedOpen(11, 15));//不相連區間:{[1,10], [11,15)} rangeSet.add(Range.closedOpen(15, 20)); //相連區間; {[1,10], [11,20)} rangeSet.add(Range.openClosed(0, 0)); //空區間; {[1,10], [11,20)} rangeSet.remove(Range.open(5, 10)); //分割[1, 10]; {[1,5], [10,10], [11,20)}
請注意,要合併 Range.closed(1, 10)和 Range.closedOpen(11, 15)這樣的區間,你須要首先用 Range.canonical(DiscreteDomain)對區間進行預處理,例如 DiscreteDomain.integers()。
注:RangeSet不支持 GWT,也不支持 JDK5 和更早版本;由於,RangeSet 須要充分利用 JDK6 中 NavigableMap 的特性。
RangeSet 的實現支持很是普遍的視圖:
爲了方便操做,RangeSet 直接提供了若干查詢方法,其中最突出的有:
RangeMap 描述了」不相交的、非空的區間」到特定值的映射。和 RangeSet 不一樣,RangeMap 不會合並相鄰的映射,即使相鄰的區間映射到相同的值。例如:
RangeMap<Integer, String> rangeMap = TreeRangeMap.create(); rangeMap.put(Range.closed(1, 10), "foo"); //{[1,10] => "foo"} rangeMap.put(Range.open(3, 6), "bar"); //{[1,3] => "foo", (3,6) => "bar", [6,10] => "foo"} rangeMap.put(Range.open(10, 20), "foo"); //{[1,3] => "foo", (3,6) => "bar", [6,10] => "foo", (10,20) => "foo"} rangeMap.remove(Range.closed(5, 11)); //{[1,3] => "foo", (3,5) => "bar", (11,20) => "foo"}
遍歷:
Map<Range<Integer>, Integer> map = rangeMap.asMapOfRanges(); Set<Map.Entry<Range<Integer>, Integer>> entrySet = map.entrySet(); Set<Range<Integer>> keySet = map.keySet(); Collection<Integer> values = map.values();
RangeMap 提供兩個視圖: