开发中,集合类型无疑是JDK最常用的数据类型之一。但是JDK提供的诸如List、Set、Map都是一些基础的集合类型,有这特定的使用场景限制,往往不能满足我们的开发需求,比如集合类型嵌套的情况,Map<string, List<String>>甚至更复杂的嵌套类型等。Guava在JDK集合框架的基础上新开发了一套常用的集合类型,以简化开发。包括MutilSet、MultiMap、BiMap、Table、ClassToinstanceMap、RangeSet、RangeMap等等,本文依照我的理解,对Multiset、MultiMap、BiMap做一些简单的介绍,如有需要了解其他集合类型的可以去看一下Guava官方用户文档
JDK中的set和数学上集合的概念是相同的,Set最大的特性就是不允许在其中存放重复的元素,可以被用来过滤在其他集合中存放的元素,从而得到一个没有包含重复新的集合。假如需要统计一个词在文档中出现了多少次,传统的做法是这样的:
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提供了一种新型的set集合类型 Multiset,它可以多次添加相等的元素。同时,Multiset继承自JDK中的Collection接口,而不是Set接口,并没有破坏Set的原有特性。除了支持添加重复的元素,其他的属性跟JDK中的set相同,比如都是无序集合。
S.N. |
方法及说明 |
---|---|
1 | boolean add(E var1) MultiSet集合中添加一个元素var1 |
2 | int add(@Nullable E var1, int var2) MultiSet集合中添加var2个var1元素 |
3 | boolean contains(@Nullable Object var1) MultiSet集合中是否包含var1 |
4 | boolean containsAll(Collection<?> var1) MultiSet集合中是否包含集合var1 |
5 | int count(@CompatibleWith(“E”) @Nullable Object var1) MultiSet集合中var1出现的次数 |
6 | Set<E> elementSet() MultiSet转set(去除重复元素,转化为JDK中的标准Set) |
7 | Set<Multiset.Entry<E>> entrySet() 返回MultiSet的Entry组成的set,和Map的entrySet类似,Entry包含元素信息和元素的出现次数信息 |
8 | Iterator<E> iterator() 返回一个迭代器,包含Multiset的所有元素(包括重复的元素) |
9 | boolean remove(@Nullable Object var1) 从MultiSet中删除var1,如果var1出现多次,执行此操作后出现次数减1 |
10 | int remove(@CompatibleWith(“E”) @Nullable Object var1, int var2) 从MultiSet中减少var2次计数,如果var2大于var1在MultiSet中出现的次数,则var1的出现次数为0(被完全移除) |
11 | boolean removeAll(Collection<?> var1) 从MultiSet中清除列表var1包含的元素,无论列表var1中元素出现多少次,MultiSet都会将包含的var1中的元素清除掉 |
12 | boolean retainAll(Collection<?> var1) 保留MultiSet中列表var1包含的元素 |
13 | int setCount(E var1, int var2) 设置元素var1在MultiSet中的出现次数,无论var1是否在MultiSet中出现过 |
14 | boolean setCount(E var1, int var2, int var3) 将MultiSet中var1元素出现的次数由var2修改为var3,var2必须上送正确的出现次数,否则不生效,如果var1没出现过,上送0 |
15 | int size() 返回集合元素的总个数(包括重复的元素) |
示例代码:
@Test
public void multiSetTest() {
Multiset<String> multiset = HashMultiset.create();
multiset.add("a");
multiset.add("b", 2);
List<String> addList = Lists.newArrayList("c", "c", "c");
multiset.addAll(addList);
/*输出 [a, b x 2, c x 3] */
System.out.println(multiset);
assertEquals(2, multiset.count("b"));
assertEquals(6, multiset.size());
assertTrue(multiset.contains("a"));
assertTrue(multiset.containsAll(Lists.newArrayList("a", "c")));
assertFalse(multiset.containsAll(Lists.newArrayList("a", "c", "e")));
Set<String> set = Sets.newLinkedHashSet("a", "b", "c");
assertEquals(set, multiset.elementSet());
Set<Multiset.Entry<String>> setEntry = multiset.entrySet();
setEntry.forEach(entry -> {
System.out.println(entry.getElement() + ": " + entry.getCount());
});
multiset.remove("a");
assertEquals(5, multiset.size());
multiset.remove("b", 10);
assertEquals(3, multiset.size());
multiset.removeAll(Lists.newArrayList("c"));
assertEquals(0, multiset.size());
multiset.setCount("x", 10);
assertEquals(10, multiset.size());
multiset.setCount("y", 3);
assertEquals(13, multiset.size());
multiset.setCount("y", 3, 5);
assertEquals(15, multiset.size());
multiset.setCount("y", 3, 8);
assertEquals(15, multiset.size());
}
Guava提供了多种Multiset的实现,大致对应JDK中Map的各种实现:
Map | 对应的Multiset | 是否支持null元素 |
---|---|---|
HashMap | HashMultiset | 是 |
TreeMap | TreeMultiset | 是(如果comparator支持的话) |
LinkedHashMap | LinkedHashMultiset | 是 |
ConcurrentHashMap | ConcurrentHashMultiset | 否 |
ImmutableMap | ImmutableMultiset | 否 |
JDK中的Map,如果先后为同一个key添加value,后添加的value会覆盖之前添加的value,所以当需要一个key可以映射多个value值时,往往需要做类似于这样的Map<K, List<V>>或Map<K, Set<V>>这样的嵌套,而这种嵌套的操作往往是比较繁琐的。Guava中提供了一种新的Map集合类型MultiMap,允许在map中一个key映射多个value值。
S.N. | 方法及说明 |
---|---|
1 | Map<K, Collection<V>> asMap() 将MultiMap转为嵌套的JDK中的Map视图,返回的Map支持remove操作,不支持put和putAll操作,对视图的操作会影响MultiMap |
2 | void clear() 清空MultiMap |
3 | boolean containsKey(@CompatibleWith(“K”) @Nullable Object var1) MultiMap是否包含key var1 |
4 | boolean containsValue(@CompatibleWith(“V”) @Nullable Object var1) MultiMap是否包含value var1 |
5 | Collection<Entry<K, V>> entries() 返回MultiMap中所有键-单个值的视图 |
6 | Collection<V> get(@Nullable K var1) 返回MultiMap中key var1对应的value值的集合 |
7 | boolean isEmpty() 判断MultiMap是否为空(size() == 0) |
8 | Multiset<K> keys() 用Multiset表示Multimap中的所有键,每个键重复出现的次数等于它映射的值的个数,返回的是一个视图,返回的视图不支持添加操作,对视图的操作会影响MultiMap |
9 | Set<K> keySet() 用Set表示Multimap中所有不同的键,返回的是一个视图,返回的视图不支持添加操作,对视图的操作会影响MultiMap |
10 | boolean put(@Nullable K var1, @Nullable V var2) MultiMap中添加键值对 |
11 | boolean putAll(@Nullable K var1, Iterable<? extends V> var2) MultiMap中为key var1依次添加多个值的映射 |
12 | boolean remove(@CompatibleWith(“K”) @Nullable Object var1, @CompatibleWith(“V”) @Nullable Object var2) 从MultiMap中移除键到值的映射,如果var2不存在,不会对MultiMap产生任何影响 |
13 | Collection<V> removeAll(@CompatibleWith(“K”) @Nullable Object var1); 清除键对应的所有值,返回的集合包含所有之前映射到K的值,注意对返回集合的操作不影响MultiMap |
14 | Collection<V> replaceValues(@Nullable K var1, Iterable<? extends V> var2) 清除键对应的所有值,并重新把key关联到Iterable中的每个元素,返回的集合包含所有之前映射到K的值,如果key var1不存在,则会新建一个var1 key,并且将var2作为value。返回的集合不是MultiMap的视图,对集合的操作不影响MultiMap |
15 | int size() 返回MultiMap总的键值对的数量 |
16 | Collection<V> values() 返回MultiMap中的所有值,返回一个视图,视图支持添加、删除操作,对视图的操作不会影响MultiMap |
示例代码:
@Test
public void multiMapTest(){
Multimap<String, String> multimap = ArrayListMultimap.create();
multimap.put("key1", "this");
multimap.put("key1", "is");
multimap.put("key1", "key1List");
System.out.println(multimap);
assertEquals(3, multimap.size());
List<String> key2List = Lists.newArrayList("this", "is", "key2List");
multimap.putAll("key2", key2List);
System.out.println(multimap);
assertEquals(6, multimap.size());
assertTrue(multimap.containsKey("key1"));
assertTrue(multimap.containsValue("key2List"));
Multiset<String> key1MultiSet = multimap.keys();
assertEquals(3, key1MultiSet.count("key1"));
/*对keys返回的视图操作会影响multiMap*/
key1MultiSet.remove("key1");
System.out.println(multimap);
assertEquals(5, multimap.size());
/*对keySet返回的视图操作会影响multiMap*/
Set<String> keySet = multimap.keySet();
assertEquals(2, keySet.size());
keySet.remove("key1");
System.out.println(multimap);
multimap.remove("key2", "this");
System.out.println(multimap);
multimap.remove("key2","testDeleteNonExistValue");
System.out.println(multimap);
assertEquals(2, multimap.size());
List<String> key3List = Lists.newArrayList("key3","is", "create");
multimap.putAll("key3", key3List);
multimap.removeAll("key2");
System.out.println(multimap);
assertEquals(3, multimap.size());
key3List = Lists.newArrayList("new", "key3","has","been","created");
multimap.replaceValues("key3", key3List);
System.out.println(multimap);
assertEquals(5, multimap.size());
/*key2不存在,新建key2*/
multimap.replaceValues("key2", key3List);
System.out.println(multimap);
assertEquals(10, multimap.size());
List<String> valuesCollection = Lists.newArrayList(multimap.values());
/*对values返回的视图的操作不会影响multiMap*/
valuesCollection.removeAll(Lists.newArrayList("key3"));
valuesCollection.add("addNew");
System.out.println(multimap);
assertEquals(10, multimap.size());
multimap.clear();
assertTrue(multimap.isEmpty());
}
Guava提供了多种Multiset的实现,可以在大多数使用Map <K,Collection <V >>的地方使用:
Implementation | Keys behave like… | Values behave like.. |
---|---|---|
ArrayListMultimap | HashMap | ArrayList |
HashMultimap | HashMap | HashSet |
LinkedListMultimap * | LinkedHashMap“* | LinkedList“* |
LinkedHashMultimap** | LinkedHashMap | LinkedHashSet |
TreeMultimap | TreeMap | TreeSet |
ImmutableListMultimap | ImmutableMap | ImmutableList |
ImmutableSetMultimap | ImmutableMap | ImmutableSet |
如果要实现一个双向Map,需要维护两个单独的map,并保持它们间的同步,显然这种方式很容易出错,比如更新一个Map的时候忘了同步更新另一个Map就会产生一些bug。Guava中提供了一个新型的双向Map集合类型BiMap,继承自java.util.Map
S.N. | 方法及说明 |
---|---|
1 | V put(@Nullable K var1, @Nullable V var2) BiMap添加key为var1,value为var2的键值对 |
2 | V forcePut(@Nullable K var1, @Nullable V var2) 如果BiMap中var2已存在,则会将原键值对删除,插入新的键值对 |
3 | Set<V> values() 返回BiMap value集合 |
4 | BiMap<V, K> inverse() 反转BiMap<K, V>的键值映射 |
注:BiMap继承自Map,Map中的方法,BiMap也可以使用
示例代码:
@Test
public void biMapTest(){
BiMap<Integer, String> idNameBiMap = HashBiMap.create();
idNameBiMap.put(1, "Michael");
idNameBiMap.put(2, "Jane");
idNameBiMap.put(3, "Marry");
/*put添加已存在的value会报IllegalArgumentException*/
assertThatThrownBy(()->idNameBiMap.put(4, "Marry"))
.isInstanceOf(IllegalArgumentException.class)
.hasNoCause();
/*put添加已存在的key,会覆盖之前key对应的value值*/
idNameBiMap.put(3,"zhuoli");
System.out.println(idNameBiMap);
assertEquals(3, idNameBiMap.size());
/*forcePut强制插入已存在的valu,会删除之前的键值对 2 - Jane*/
idNameBiMap.forcePut(4, "Jane");
System.out.println(idNameBiMap);
assertEquals(3, idNameBiMap.size());
Set<String> biMapValuesSet = idNameBiMap.values();
System.out.println(biMapValuesSet);
BiMap<String, Integer> nameIdBiMap = idNameBiMap.inverse();
System.out.println(nameIdBiMap);
assertEquals(4, nameIdBiMap.get("Jane").intValue());
}
Guava提供了多种BiMap的实现
键–值实现 | 值–键实现 |
BiMap实现 |
---|---|---|
HashMap | HashMap | HashBiMap |
ImmutableMap | ImmutableMap | ImmutableBiMap |
EnumMap | EnumMap | EnumBiMap |
EnumMap | HashMap | EnumHashBiMap |
测试代码:码云 – 卓立 – Guava测试代码