Guava引入了很多JDK没有的、但我们发现明显有用的新集合类型。这些新类型是为了和JDK集合框架共存,而没有往JDK集合抽象中硬塞其他概念。作为一般规则,Guava集合非常精准地遵循了JDK接口契约。
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);
}
}
这种写法很笨拙,也容易出错,并且不支持同时收集多种统计信息,如总词数。我们可以做的更好。
Map | 对应的Multiset | 是否支持null元素 |
---|---|---|
HashMap | HashMultiset | 是 |
TreeMap | TreeMultiset | 是(如果comparator支持的话) |
LinkedHashMap | LinkedHashMultiset | 是 |
ConcurrentHashMap | ConcurrentHashMultiset | 否 |
ImmutableMap | ImmutableMultiset | 否 |
方法 | 描述 |
---|---|
count(E) | 给定元素在Multiset中的计数 |
elementSet() | Multiset中不重复元素的集合,类型为Set<E> |
entrySet() | 和Map的entrySet类似,返回Set<Multiset.Entry<E>>,其中包含的Entry支持getElement()和getCount()方法 |
add(E, int) | 增加给定元素在Multiset中的计数 |
remove(E, int) | 减少给定元素在Multiset中的计数 |
setCount(E, int) | 设置给定元素在Multiset中的计数,不可以为负数 |
size() | 返回集合元素的总个数(包括重复的元素) |
package collections;
import com.google.common.collect.HashMultiset;
import com.google.common.collect.Lists;
import com.google.common.collect.Multiset;
import java.util.Set;
public class GuavaMultiset {
public static void main(String[] args) {
Multiset<String> multiset = HashMultiset.create();
multiset.addAll( Lists.newArrayList("I","love","China","China","is","my","love"));
int love = multiset.count("love");
System.out.println("love num:" + love);
Set<String> elementSet = multiset.elementSet();
System.out.println("Multiset中不重复元素的集合,类型为Set<E>:" + elementSet);
Set<Multiset.Entry<String>> entries = multiset.entrySet();//统计元素频次
System.out.println("entrySet:" + entries);//[love x 2, China x 2, I, is, my]
multiset.add("add",2);
System.out.println("增加add后的数据:" + multiset);
multiset.remove("add",2);
System.out.println("移除add后的数据:" + multiset);
multiset.setCount("love", 1);
System.out.println("设置love计数为1后的数据:" + multiset);
System.out.println("返回集合中的总个数:" + multiset.size());
}
}
运行结果:
可以用两种方式看待Multiset:
Guava的Multiset API也结合考虑了这两种方式: 当把Multiset看成普通的Collection时,它表现得就像无序的ArrayList:
当把Multiset看作Map<E, Integer>时,它也提供了符合性能期望的查询操作:
值得注意的是,除了极少数情况,Multiset和JDK中原有的Collection接口契约完全一致——具体来说,TreeMultiset在判断元素是否相等时,与TreeSet一样用compare,而不是Object.equals。另外特别注意,Multiset.addAll(Collection)可以添加Collection中的所有元素并进行计数,这比用for循环往Map添加元素和计数方便多了。
每个有经验的Java程序员都在某处实现过Map<K, List<V>>或Map<K, Set<V>>,并且要忍受这个结构的笨拙,以便做相应的业务逻辑处理。例如:
Map<String, List<Student>> studentMap = new HashMap<>();
for (int i = 0; i < 5; i++) {
Student student = new Student();
student.setName("multimap"+i);
student.setAge(i);
List<Student> list = studentMap.get(student.getName());
if (list != null) {
list.add(student);
} else {
list = new ArrayList<>();
list.add(student);
studentMap.put(student.getName(), list);
}
}
像 Map<String, List<StudentScore>> StudentScoreMap = new HashMap<String, List<StudentScore>>()这样的数据结构,自己实现起来太麻烦,你需要检查key是否存在,不存在时则创建一个,存在时在List后面添加上一个。这个过程是比较痛苦的,如果你希望检查List中的对象是否存在,删除一个对象,或者遍历整个数据结构,那么则需要更多的代码来实现。
Guava的Multimap就提供了一个方便地把一个键对应到多个值的数据结构。让我们可以简单优雅的实现上面复杂的数据结构,让我们的精力和时间放在实现业务逻辑上,而不是在数据结构上,下面我们具体来看看Multimap的相关知识点。
可以用两种方式思考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。
方法签名 | 描述 | 等价于 |
---|---|---|
put(K, V) | 添加键到单个值的映射 | multimap.get(key).add(value) |
putAll(K, Iterable<V>) | 依次添加键到多个值的映射 | Iterables.addAll(multimap.get(key), values) |
remove(K, V) | 移除键到值的映射;如果有这样的键值并成功移除,返回true。 | multimap.get(key).remove(value) |
removeAll(K) | 清除键对应的所有值,返回的集合包含所有之前映射到K的值,但修改这个集合就不会影响Multimap了。 | multimap.get(key).clear() |
replaceValues(K, Iterable<V>) | 清除键对应的所有值,并重新把key关联到Iterable中的每个元素。返回的集合包含所有之前映射到K的值。 | multimap.get(key).clear(); Iterables.addAll(multimap.get(key), values) |
例子:
package collections;
import com.google.common.collect.ArrayListMultimap;
import com.google.common.collect.Multimap;
import java.util.ArrayList;
import java.util.List;
public class GuavaMultimap {
public static void main(String[] args) {
Multimap<String,Student> stuMultimap = ArrayListMultimap.create();
Student student1 = new Student();
student1.setName("student1");
student1.setAge(1);
stuMultimap.put("student",student1);
stuMultimap.put("student1",student1);
System.out.println("-------------put(K, V),添加键到单个值的映射---------------");
System.out.println(stuMultimap);
Student student2 = new Student();
student2.setName("student2");
student2.setAge(2);
Student student3 = new Student();
student3.setName("student3");
student3.setAge(3);
List<Student> stuList = new ArrayList<>();
stuList.add(student2);
stuList.add(student3);
stuMultimap.putAll("student2and3",stuList);
System.out.println("-------------putAll(K, Iterable<V>),依次添加键到多个值的映射---------------");
System.out.println(stuMultimap);
stuMultimap.remove("student",student1);
System.out.println("-------------remove(K, V),移除键到值的映射;如果有这样的键值并成功移除,返回true。---------------");
System.out.println(stuMultimap);
stuMultimap.removeAll("student2and3");
System.out.println("-------------removeAll(K),清除键对应的所有值,返回的集合包含所有之前映射到K的值,但修改这个集合就不会影响Multimap了。---------------");
System.out.println(stuMultimap);
stuMultimap.replaceValues("student1",stuList);
System.out.println("-------------replaceValues(K, Iterable<V>),清除键对应的所有值,并重新把key关联到Iterable中的每个元素。返回的集合包含所有之前映射到K的值。---------------");
System.out.println(stuMultimap);
}
}
运行结果:
Multimap提供了多种形式的实现。在大多数要使用Map<K, Collection<V>>的地方,你都可以使用它们:
实现 | 键行为类似 | 值行为类似 |
---|---|---|
ArrayListMultimap | HashMap | ArrayList |
HashMultimap | HashMap | HashSet |
LinkedListMultimap* | LinkedHashMap* | LinkedList* |
LinkedHashMultimap** | LinkedHashMap | LinkedHashMap |
TreeMultimap | TreeMap | TreeSet |
ImmutableListMultimap | ImmutableMap | ImmutableList |
ImmutableSetMultimap | ImmutableMap | ImmutableSet |
以上这些实现,除了immutable的实现都支持null的键和值。
*LinkedListMultimap.entries()保留了所有键和值的迭代顺序。
**LinkedHashMultimap保留了映射项的插入顺序,包括键插入的顺序,以及键映射的所有值的插入顺序。
请注意,并非所有的Multimap都和上面列出的一样,使用Map<K, Collection<V>>来实现(特别是,一些Multimap实现用了自定义的hashTable,以最小化开销)
如果你想要更大的定制化,请用Multimaps.newMultimap(Map, Supplier<Collection>)或list和 set版本,使用自定义的Collection、List或Set实现Multimap。
1.asMap把自身Multimap<K, V>映射成Map<K, Collection<V>>视图。这个Map视图支持remove和修改操作,但是不支持put和putAll。严格地来讲,当你希望传入参数是不存在的key,而且你希望返回的是null而不是一个空的可修改的集合的时候就可以调用 asMap().get(key)。(你可以强制转型asMap().get(key)的结果类型-对SetMultimap的结果转成Set,对ListMultimap的结果转成List型-但是直接把ListMultimap转成Map<K, List<V>>是不行的。) 2.entries视图是把Multimap里所有的键值对以Collection<Map.Entry<K, V>>的形式展现。 3.keySet视图是把Multimap的键集合作为视图 4.keys视图返回的是个Multiset,这个Multiset是以不重复的键对应的个数作为视图。这个Multiset可以通过支持移除操作而不是添加操作来修改Multimap。 5.values()视图能把Multimap里的所有值“平展”成一个Collection<V>。这个操作和Iterables.concat(multimap.asMap().values())很相似,只是它返回的是一个完整的Collection。
尽管Multimap的实现用到了Map,但Multimap<K, V>不是Map<K, Collection<V>>。因为两者有明显区别: 1.Multimap.get(key)一定返回一个非null的集合。但这不表示Multimap使用了内存来关联这些键,相反,返回的集合只是个允许添加元素的视图。 2.如果你喜欢像Map那样当不存在键的时候要返回null,而不是Multimap那样返回空集合的话,可以用asMap()返回的视图来得到Map<K, Collection<V>>。(这种情况下,你得把返回的Collection<V>强转型为List或Set)。 3.Multimap.containsKey(key)只有在这个键存在的时候才返回true。 4.Multimap.entries()返回的是Multimap所有的键值对。但是如果需要key-collection的键值对,那就得用asMap().entries()。 5.Multimap.size()返回的是entries的数量,而不是不重复键的数量。如果要得到不重复键的数目就得用Multimap.keySet().size()。
例子:
package collections;
import com.google.common.collect.ArrayListMultimap;
import com.google.common.collect.Multimap;
import com.google.common.collect.Multiset;
import java.util.Collection;
import java.util.Map;
import java.util.Set;
public class GuavaMultimap {
public static void main(String[] args) {
Multimap<String,Student> stuMultimap = ArrayListMultimap.create();
Student student1 = new Student();
student1.setName("student1");
student1.setAge(1);
Student student2 = new Student();
student2.setName("student2");
student2.setAge(2);
Student student3 = new Student();
student3.setName("student3");
student3.setAge(3);
stuMultimap.put("student1",student1);
stuMultimap.put("student2and3",student2);
stuMultimap.put("student2and3",student3);
Map<String, Collection<Student>> stringCollectionMap = stuMultimap.asMap();
System.out.println("-------------asMap把自身Multimap<K, V>映射成Map<K, Collection<V>>视图。---------------");
System.out.println(stringCollectionMap);
Collection<Map.Entry<String, Student>> entries = stuMultimap.entries();
System.out.println("-------------entries视图是把Multimap里所有的键值对以Collection<Map.Entry<K, V>>的形式展现。---------------");
System.out.println(entries);
Set<String> strings = stuMultimap.keySet();
System.out.println("-------------keySet视图是把Multimap的键集合作为视图---------------");
System.out.println(strings);
Multiset<String> keys = stuMultimap.keys();
System.out.println("-------------keys视图返回的是个Multiset,这个Multiset是以不重复的键对应的个数作为视图。这个Multiset可以通过支持移除操作而不是添加操作来修改Multimap。---------------");
System.out.println(keys);
Collection<Student> values = stuMultimap.values();
System.out.println("-------------values()视图能把Multimap里的所有值“平展”成一个Collection<V>。---------------");
System.out.println(values);
Collection<Student> stus = stuMultimap.get("student1");
System.out.println("-------------Multimap.get(key)一定返回一个非null的集合。---------------");
System.out.println(stus);
boolean student1Key = stuMultimap.containsKey("student1");
System.out.println("-------------Multimap.containsKey(key)只有在这个键存在的时候才返回true。---------------");
System.out.println(student1Key);
int size = stuMultimap.size();
System.out.println("-------------Multimap.size()返回的是entries的数量,而不是不重复键的数量。---------------");
System.out.println(size);
}
}
运行结果:
相信有很多开发者会遇到这种情况:在开发的过程中,定义了一个Map,往往是通过key来查找value的,但如果需要通过value来查找key,我们就需要额外编写一些代码了。
刚好BiMap提供了一种新的集合类型,它提供了key和value的双向关联的数据结构。下面我们来看看这两者的实现:
package com.guava;
import java.util.Map;
import java.util.Map.Entry;
import com.google.common.collect.Maps;
public class BiMapTest {
public static void main(String[] args) {
Map<Integer,String> idToName = Maps.newHashMap();
idToName.put(1,"zhangsan");
idToName.put(2,"lisi");
idToName.put(3,"wangwu");
System.out.println("idToName:"+idToName);
Map<String,Integer> nameToId = Maps.newHashMap();
for(Entry<Integer, String> entry: idToName.entrySet()) {
nameToId.put(entry.getValue(), entry.getKey());
}
System.out.println("nameToId:"+nameToId);
}
}
运行结果:
上面的代码可以帮助我们实现map倒转的要求,但是还有一些我们需要考虑的问题: 1. 如何处理重复的value的情况。不考虑的话,反转的时候就会出现覆盖的情况. 2. 如果在反转的map中增加一个新的key,倒转前的map是否需要更新一个值呢? 在这种情况下需要考虑的业务以外的内容就增加了,编写的代码也变得不那么易读了。这时我们就可以考虑使用Guava中的BiMap了。
package com.guava;
import com.google.common.collect.BiMap;
import com.google.common.collect.HashBiMap;
public class BiMapTest {
public static void main(String[] args) www.jiuyueguojizc.cn{
BiMap<Integer,String> idToName = HashBiMap.create();
idToName.put(1,"zhangsan");
idToName.put(2,www.javachenglei.com"lisi");
idToName.put(www.chuancenpt.com 3,"wangwu")www.xingyunylpt.com;
System.out.println("idToName:"+idToName);
BiMap<String,Integer>www.yixinpt2.cn/ nameToId = idToName.inverse();
System.out.println("nameToId:www.zhuyngyule.cn"+nameToId);
}
}
运行结果:
在使用BiMap进行key、value反转时,会要求Value的唯一性。如果value重复了则会抛出错误:java.lang.IllegalArgumentException,例如:
package com.guava;
import com.google.common.collect.BiMap;
import com.google.common.collect.HashBiMap;
public class BiMapTest {
public static void main(String[] args) {
BiMap<Integer,String> idToName = HashBiMap.create();
idToName.put(1,"zhangsan");
idToName.put(2,www.jintianxuesha.com "lisi");
idToName.put(3, www.iceason.net "wangwu");
idToName.put(4, www.ued3zc.cn"wangwu");
System.out.println("idToName:"+idToName);
BiMap<String,Integer> www.javachenglei.com nameToId = idToName.inverse();
System.out.println("nameToId:"+nameToId);
}
}
运行结果:
如果我们确实需要插入重复的value值,那可以选择forcePut方法。但是我们需要注意的是前面的key也会被覆盖了。
package com.guava;
import com.google.common.collect.BiMap;
import com.google.common.collect.HashBiMap;
public class BiMapTest {
public static void main(String[] args) {
BiMap<Integer,www.yuanmenyul.cn String> idToName = HashBiMap.create();
idToName.put(1,"zhangsan");
idToName.put(2,"lisi");
idToName.put(3,"wangwu");
idToName.forcePut(4, "wangwu");
System.out.println("idToName:"+idToName);
BiMap<String,Integer> nameToId = idToName.inverse();
System.out.println("nameToId:"+nameToId);
}
}
运行结果:
键–值实现 | 值–键实现 | 对应的BiMap实现 |
---|---|---|
HashMap | HashMap | HashBiMap |
ImmutableMap | ImmutableMap | ImmutableBiMap |
EnumMap | EnumMap | EnumBiMap |
EnumMap | HashMap | EnumHashBiMap |
当我们需要多个索引的数据结构的时候,通常情况下,我们只能用这种丑陋的Map<FirstName, Map<LastName, Person>>来实现。为此Guava提供了一个新的集合类型-Table集合类型,来支持这种数据结构的使用场景。Table支持“row”和“column”,而且提供多种视图。
package com.guava;
import com.google.common.collect.HashBasedTable;
import com.google.common.collect.Table;
public class TableTest {
public static void main(String[] args) {
Table<String, Integer, String> aTable = HashBasedTable.create();
aTable.put("A", 1, "A1");
aTable.put("A", 2, "A2");
aTable.put("B", 2, "B2");
System.out.println(aTable.column(2));
System.out.println(aTable.row("B"));
System.out.println(aTable.get("B", 2));
System.out.println(aTable.contains("B", 2));
System.out.println(aTable.containsColumn(2));
System.out.println(aTable.containsRow("B"));
System.out.println(aTable.columnMap());
System.out.println(aTable.rowMap());
System.out.println(aTable.remove("B", 2));
}
}
运行结果:
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。