新集合类型
Guava引入了很多JDK没有的、但我们发现明显有用的新集合类型。这些新类型是为了和JDK集合框架共存,而没有往JDK集合抽象中硬塞其他概念。作为一般规则,Guava集合非常精准地遵循了JDK接口契约。
Multiset
统计一个词在文档中出现了多少次,传统的做法是这样的:
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);
}
}
可以用两种方式看待Multiset:
1. 没有元素顺序限制的ArrayList
2.Map<E, Integer>
,键为元素,值为计数
Guava的Multiset API也结合考虑了这两种方式:
当把Multiset看成普通的Collection时,它表现得就像无序的ArrayList:
- add(E)添加单个给定元素
- iterator()返回一个迭代器,包含Multiset的所有元素(包括重复的元素)
- size()返回所有元素的总个数(包括重复的元素)
当把Multiset看作Map
package collection.immu;
import com.google.common.collect.HashMultimap;
import java.util.Collection;
import java.util.List;
import java.util.Map;
private static void create() {
List<String> words = Lists.newArrayList("A", "B","A" ,"B","C", "D", "E", "F");
HashMultiset<String> hashMultiset = HashMultiset.create(words);
hashMultiset.addAll(words);
System.err.println(hashMultiset.count("A"));
hashMultiset.setCount("V",4);//增加4个V元素
System.err.println(hashMultiset.size());
hashMultiset.add("X",4);
System.err.println(hashMultiset.size());
hashMultiset.remove("O",6);
System.err.println(hashMultiset.size());
// hashMultiset.setCount("P",-4);//报错
// System.err.println(hashMultiset.size());
}
Multiset的各种实现
Guava提供了多种Multiset的实现,大致对应JDK中Map的各种实现:
Map | 对应的Multiset | 是否支持null元素 |
---|---|---|
HashMap | HashMultiset | 是 |
TreeMap | TreeMultiset | 是(如果comparator支持的话) |
LinkedHashMap | LinkedHashMultiset | 是 |
ConcurrentHashMap | ConcurrentHashMultiset | 否 |
ImmutableMap | ImmutableMultiset | 否 |
SortedMultiset
SortedMultiset是Multiset 接口的变种,它支持高效地获取指定范围的子集。比方说,你可以用 latencies.subMultiset(0,BoundType.CLOSED, 100, BoundType.OPEN).size()来统计你的站点中延迟在100毫秒以内的访问,然后把这个值和latencies.size()相比,以获取这个延迟水平在总体访问中的比例。
TreeMultiset实现SortedMultiset接口。在撰写本文档时,ImmutableSortedMultiset还在测试和GWT的兼容性。
Multimap
每个有经验的Java程序员都在某处实现过Map<K, List<V>>
或Map<K, Set<V>>
,并且要忍受这个结构的笨拙。例如,Map<K, Set<V>>
通常用来表示非标定有向图。Guava的 Multimap可以很容易地把一个键映射到多个值。换句话说,Multimap是把键映射到任意多个值的一般方式。
可以用两种方式思考Multimap的概念:”键-单个值映射”的集合:
a -> 1 a -> 2 a ->4 b -> 3 c -> 5
或者”键-值集合映射”的映射:
a -> [1, 2, 4] b -> 3 c -> 5
方法签名 | 描述 | 等价于 |
---|---|---|
put(K, V) | 添加键到单个值的映射 | multimap.get(key).add(value) |
putAll(K, Iterable) | 依次添加键到多个值的映射 | 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) | 清除键对应的所有值,并重新把key关联到Iterable中的每个元素。返回的集合包含所有之前映射到K的值。 | multimap.get(key).clear(); Iterables.addAll(multimap.get(key), values) |
package collection.immu;
import com.google.common.collect.HashMultimap;
import java.util.Collection;
import java.util.List;
import java.util.Map;
/**
*
*
* Created by LF on 2017/3/6.
ArrayListMultimap HashMap ArrayList
HashMultimap HashMap HashSet
LinkedListMultimap* LinkedHashMap* LinkedList*
LinkedHashMultimap** LinkedHashMap LinkedHashMap
TreeMultimap TreeMap TreeSet
ImmutableListMultimap ImmutableMap ImmutableList
ImmutableSetMultimap ImmutableMap ImmutableSet
*/
public class MyMultimap {
public static void main(String[] args) {
HashMultimap<String, String> hashMultimap = HashMultimap.create();
hashMultimap.put("1","A");
hashMultimap.put("2","B");
hashMultimap.put("2","SS");
hashMultimap.put("2","SSA");
hashMultimap.put("3","C");
hashMultimap.put("4","D");
hashMultimap.put("4","D");
hashMultimap.put("5","E");
System.err.println(hashMultimap.get("2"));
Map<String, Collection<String>> stringCollectionMap = hashMultimap.asMap();
//
System.err.println(stringCollectionMap.get("23"));
}
}
Multimap的视图
Multimap还支持若干强大的视图:
- asMap为
Multimap<K, V>
提供Map<K,Collection<V>>
形式的视图。返回的Map支持remove操作,并且会反映到底层的Multimap,但它不支持put或putAll操作。更重要的是,如果你想为Multimap中没有的键返回null,而不是一个新的、可写的空集合,你就可以使用asMap().get(key)
。(你可以并且应当把asMap.get(key)返回的结果转化为适当的集合类型——如SetMultimap.asMap.get(key)的结果转为Set,ListMultimap.asMap.get(key)的结果转为List——Java类型系统不允许ListMultimap直接为asMap.get(key)返回List——译者注:也可以用Multimaps中的asMap静态方法帮你完成类型转换)
entries用Collection<Map.Entry<K, V>>
返回Multimap中所有”键-单个值映射”——包括重复键。(对SetMultimap,返回的是Set) - keySet用Set表示Multimap中所有不同的键。
- keys用Multiset表示Multimap中的所有键,每个键重复出现的次数等于它映射的值的个数。可以从这个Multiset中移除元素,但不能做添加操作;移除操作会反映到底层的Multimap。
- values()用一个”扁平”的Collection包含Multimap中的所有值。这有一点类似于Iterables.concat(multimap.asMap().values()),但它直接返回了单个Collection,而不像multimap.asMap().values()那样是按键区分开的Collection。
Multimap的各种实现
Multimap提供了多种形式的实现。在大多数要使用Map
BiMap
传统上,实现键值对的双向映射需要维护两个单独的map,并保持它们间的同步。但这种方式很容易出错,而且对于值已经在map中的情况,会变得非常混乱。例如:
Map<String, Integer> nameToId = Maps.newHashMap();
Map<Integer, String> idToName = Maps.newHashMap();
nameToId.put("Bob", 42);
idToName.put(42, "Bob");
//如果"Bob"和42已经在map中了,会发生什么?
//如果我们忘了同步两个map,会有诡异的bug发生...
BiMap<K, V>
是特殊的Map:
- 可以用 inverse()反转
BiMap<K, V>
的键值映射 - 保证值是唯一的,因此 values()返回Set而不是普通的Collection
BiMap的各种实现
键–值实现 | 值–键实现 | 对应的BiMap实现 |
---|---|---|
HashMap | HashMap | HashBiMap |
ImmutableMap | ImmutableMap | ImmutableBiMap |
EnumMap | EnumMap | EnumBiMap |
EnumMap | HashMap | EnumHashBiMap |
注:Maps类中还有一些诸如synchronizedBiMap的BiMap工具方法.
Table
通常来说,当你想使用多个键做索引的时候,你可能会用类似Map<FirstName, ``Map<LastName, Person>>
的实现,这种方式很丑陋,使用上也不友好。Guava为此提供了新集合类型Table,它有两个支持所有类型的键:”行”和”列”。Table提供多种视图,以便你从各种角度使用它:
- rowMap():用
Map<R, Map<C, V>>
表现Table<R, C, V>
。同样的, rowKeySet()返回”行”的集合Set<R>
。 - row(r) :用
Map<C, V>
返回给定”行”的所有列,对这个map进行的写操作也将写入Table中。 - 类似的列访问方法:columnMap()、columnKeySet()、column(c)。(基于列的访问会比基于的行访问稍微低效点)
- cellSet():用元素类型为
Table.Cell<R, C, V>
的Set表现Table<R, C, V>
。Cell类似于Map.Entry,但它是用行和列两个键区分的。
Table有如下几种实现:
- HashBasedTable:本质上用
HashMap<R, HashMap<C, V>>
实现;
1.TreeBasedTable:本质上用TreeMap<R, TreeMap<C,V>>
实现; - ImmutableTable:本质上用
ImmutableMap<R, ImmutableMap<C, V>>
实现;注:ImmutableTable对稀疏或密集的数据集都有优化。 - ArrayTable:要求在构造时就指定行和列的大小,本质上由一个二维数组实现,以提升访问速度和密集Table的内存利用率。ArrayTable与其他Table的工作原理有点不同,请参见Javadoc了解详情。
ClassToInstanceMap
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));
从技术上讲,ClassToInstanceMap实现了Map
RangeSet
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)}
RangeSet的视图
RangeSet的实现支持非常广泛的视图:
- complement():返回RangeSet的补集视图。complement也是RangeSet类型,包含了不相连的、非空的区间。
- subRangeSet(Range):返回RangeSet与给定Range的交集视图。这扩展了传统排序集合中的headSet、subSet和tailSet操作。
- asRanges():用Set
RangeSet的查询方法
为了方便操作,RangeSet直接提供了若干查询方法,其中最突出的有:
- contains(C):RangeSet最基本的操作,判断RangeSet中是否有任何区间包含给定元素。
- rangeContaining(C):返回包含给定元素的区间;若没有这样的区间,则返回null。
- encloses(Range):简单明了,判断RangeSet中是否有任何区间包括给定区间。
- span():返回包括RangeSet中所有区间的最小区间。
package collection.immu;
import com.google.common.collect.Range;
import com.google.common.collect.RangeSet;
import com.google.common.collect.TreeRangeSet;
import org.junit.Test;
import java.util.Set;
/**
* Created by LF on 2017/4/25.
*/
public class MyRangSet {
public static void main(String[] args) {
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)}
}
@Test
public void methord() {
RangeSet<Integer> rangeSet = TreeRangeSet.create();
Range<Integer> closed = Range.closed(1, 10);
rangeSet.add(closed);
//获取补集
RangeSet<Integer> complement = rangeSet.complement();
}
@Test
public void subRangeSet() {
RangeSet<Integer> rangeSet = TreeRangeSet.create();
Range<Integer> closed = Range.closed(1, 10);
rangeSet.add(closed);
Range<Integer> closed2 = Range.closed(3, 10);
//交集
RangeSet<Integer> integerRangeSet = rangeSet.subRangeSet(closed2);
}
@Test
public void asRanges() {
RangeSet<Integer> rangeSet = TreeRangeSet.create();
Range<Integer> closed = Range.closed(1, 10);
rangeSet.add(closed);
Set<Range<Integer>> ranges = rangeSet.asRanges();
}
@Test
public void query() {
RangeSet<Integer> rangeSet = TreeRangeSet.create();
Range<Integer> closed = Range.closed(1, 10);
rangeSet.add(closed);
rangeSet.contains(1);//判断RangeSet中是否有任何区间包含给定元素。
rangeSet.rangeContaining(1);//给定元素的区间
}
}
RangeMap
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"}
RangeMap的视图
RangeMap提供两个视图:
- asMapOfRanges():用
Map<Range<K>, V>
表现RangeMap。这可以用来遍历RangeMap。 subRangeMap(Range<K>)
:用RangeMap类型返回RangeMap与给定Range的交集视图。这扩展了传统的headMap、subMap和tailMap操作。