新集合类型
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);
}
}
这种方法笨拙,易错并且不支持收集其他有用的统计数据,像单词的总数等。事实上,我们可以做的更好。
Guava 提供的一种新的集合类型, Multiset
, 支持加入多重元素。维基百科是这样定义multiset的, 数学方面定义multiset是一种集合中元素可以多次出现的集合。...在multisets的实现上与集合的定义不同的是元素的顺序是不相关的: multisets {a, a, b} 与 {a, b, a} 是等价的。
可以用下面两个方法设计上述的问题:
一个没有排序的ArrayList<E>
:顺序不重要.- 一个有元素与计数的
Map<E, Integer>
Guava’s Multiset
API 结合这两种思想设计了 Multiset
, 如下:
- 如果把
Multiset
看做是一个普通的集合,它更像是一个无序的ArrayList
:调用add(E)
来执行添加指定元素的事件。iterator()
方法可以迭代遍历出每一个元素。size()
方法可以统计出Multiset元素的总数。
- 查询操作的行为特征更像是一个
Map<E, Integer>
.count(Object)
方法返回与该元素想关联的技术。对于HashMultiset来说计数的时间复杂度为
O(1),对于TreeMultiset
,来说是O(log n)。entrySet()
方法返回一个功能与Map的
entrySet相似的Set<Multiset.Entry<E>>
。elementSet()方法返回一个
包含唯一元素的Set<E>
就像keySet()对于Map
一样。- 此外,
Multiset对于多种不同元素的实现消耗的内存时线性的。
尤其是, Multiset
与Collection
接口契约完全一致,这是JDK中的罕见实例--- 此外, TreeMultiset
, 像TreeSet
,使用comparison 在执行相同对比以代替Object.equals
. 特殊的是, Multiset.addAll(Collection)
为每一个出现的元素都添加一个事件,这比上述中Map的for循环更加方便。
Method | Description |
---|---|
count(E) |
记录已经被添加进multiset的元素出现次数。 |
elementSet() |
像一个Set<E>映射出在Multiset<E>中不同的元素 |
entrySet() |
返回与Map.entrySet()功能类似的 Set<Multiset.Entry<E>> , 支持 getElement() 与 getCount() 方法。 |
add(E, int) |
添加指定元素的指定出现次数。 |
remove(E, int) |
删除指定元素的指定出现次数。 |
setCount(E, int) |
将指定元素的次数设置为指定的非负值。 |
size() |
返回Multiset所有元素的出现次数的总和。 |
Multiset Is Not A Map
注意Multiset<E>
不是Map<E, Integer>
,尽管它是Multiset
实现的一部分。Multiset
是一个Collection
类型, 并满足所有相关契约。其他显著的差异包括:
Multiset<E>
只能记录正计数的元素的,不能记录负计数的元素,以及计数为0的元素被认为不在 multiset中。不会出现在elementSet()
或者entrySet()
视图中。multiset.size()
返回的是全部元素的数量。如果想要返回不同元素的数量需要使用elementSet().size()
方法。 (例如,add(E)
不管添加什么都会将multiset.size()
加一)multiset.iterator()
迭代遍历所有的元素长度与multiset.size()
一致。Multiset<E>
支持添加,删除,直接设置元素数量的操作。setCount(elem, 0)
等同于删除元素的所有出现次数。执行multiset.count(elem)
方法时,如果元素不在multiset中则返回0
。
Implementations
Guava提供了许多 Multiset
的实现与JDK map的实现对应如下图所示:.
Map | Corresponding Multiset | Supports null elements |
---|---|---|
HashMap |
HashMultiset |
Yes |
TreeMap |
TreeMultiset |
Yes |
LinkedHashMap |
LinkedHashMultiset |
Yes |
ConcurrentHashMap |
ConcurrentHashMultiset |
No |
ImmutableMap |
ImmutableMultiset |
No |
SortedMultiset
SortedMultiset
是 Multiset
的一种变种,支持在特定范围上取子集。例如, 可以使用latencies.subMultiset(0, BoundType.CLOSED, 100, BoundType.OPEN).size()
来确定站点有多少点击率低于100Ms的延迟,然后将它与latencies.size()
进行比较,以确定总体比例。
TreeMultiset
实现了 SortedMultiset
接口。 ImmutableSortedMultiset
仍然在不断完善中,还在测试GWT兼容性。
Multimap
每个Java程序员都会在不同程度上实现Map<K, List<V>>
or Map<K, Set<V>>
, 并处理这种尴尬的结构。例如, Map<K, Set<V>>
是用来表示无权有向图的一种方法。Guava的 Multimap
框架使得处理从键到多个值的映射变得容易。 Multimap
是将多个value与key向关联的有效方法。
从概念上来说,有两种方法来考虑多聚体:
作为从单个键到单个值的映射集合:
a -> 1
a -> 2
a -> 4
b -> 3
c -> 5
或者是一个key值对应多个值的集合:
a -> [1, 2, 4]
b -> [3]
c -> [5]
通常, Multimap
接口来表示第一个视图是最好的选择,并且允许将这个视图用 asMap()
v视图来查看, 将返回 Map<K, Collection<V>>
. 最重要的是, 没有一个键会映射到一个空集合:一个键要么映射到至少一个值,要么简单地不存在于Multimap
中。
你将会很少直接使用 Multimap
接口, 更常用的做法是使用 ListMultimap
或者 SetMultimap
, 这两个的键值分别对应着List或者
Set
。
Construction
最直接的构造 Multimap
的方法是使用 MultimapBuilder
, 它允许你配置如何输入键与值。例如:
// creates a ListMultimap with tree keys and array list values
ListMultimap<String, Integer> treeListMultimap = MultimapBuilder.treeKeys().arrayListValues().build();
// creates a SetMultimap with hash keys and enum set values
SetMultimap<Integer, MyEnum> hashEnumMultimap = MultimapBuilder.hashKeys().enumSetValues(MyEnum.class).build();
你可能会选择 create()
方法直接实现实现类,但是更加明智的做法是使用 MultimapBuilder
。
Modifying
Multimap.get(key)
返回key所对应的多个值的视图,即使其目前还没有对应的值。 对 ListMultimap
会返回一个 List
, 对SetMultimap会返回
Set
.
修改将会写入底层的 Multimap
中, 例如,
Set<Person> aliceChildren = childrenMultimap.get(alice);
aliceChildren.clear();
aliceChildren.add(bob);
aliceChildren.add(carol);
其他的修改 multimap 的方法(更直接)包括:
Signature | Description | Equivalent |
---|---|---|
put(K, V) |
增加key对应的value | multimap.get(key).add(value) |
putAll(K, Iterable<V>) |
一次添加key对应的每一个value | Iterables.addAll(multimap.get(key), values) |
remove(K, V) |
移除key对应的一个value,如果multimap被修改了则返回true。 | multimap.get(key).remove(value) |
removeAll(K) |
移除key对应的所有value。返回的集合可能是可修改或者不可修改的,但是修改它不会影响multimap. (返回的是适当的集合类型) | multimap.get(key).clear() |
replaceValues(K, Iterable<V>) |
清除key所对应的所有value,并其对应的value设置为传入值。返回先前与key所关联的值。 | multimap.get(key).clear(); Iterables.addAll(multimap.get(key), values) |
Views
Multimap
同样支持更有力的视图:
asMap
将Multimap<K, V>
转换为形如Map<K, Collection<V>>
的视图。返回的map支持remove
, 以及写操作会改变原集合,但是不支持put
和putAll方法
. 然而,你能够使用asMap().get(key)
当你想要从不存在的key获取null
而不是获取一个新的可写的集合 . (应该将asMap.get(key)转换为适当类型的集合
-- 一个SetMultimap的Set
,一个ListMultimap的List
-- 但这里不允许ListMultimap
返回Map<K, List<V>>
。)entries
返回Collection<Map.Entry<K, V>>
的所有entries在Multimap的视图
. (对于SetMultimap来说这是一个
Set
.)keySet
返回不同键值在Multimap
上的Set视图
.keys
返回所有键值在Multimap
上的Multiset
视图, 包含等数量的key对应的值。元素能够被删除从Multiset中
,但是不能被添加;修改将被写入。values()
是所有值在Multimap
上的 "扁平"Collection<V>
视图。 有点像有点像Iterables.concat(multimap.asMap().values())
, 但会返回一个满的Collection
.
Multimap Is Not A Map
Multimap<K, V>
不是 Map<K, Collection<V>>
, 即使Multimap
会用到相关的实现。值得注意的不同包括:
Multimap.get(key)
的返回是非null的, 但可能是一个空的集合。这并不代表着 multimap 花费很多内存保存key的关联,相反 返回的几个是一个如果你愿意你可以添加与key相关联的集合。- 如何你更喜欢
Map的行为是key对应的值返回
null, 使用asMap()
视图获取Map<K, Collection<V>>
. (或者, 从ListMultimap中获取一个
Map<K,
List
<V>>
, 使用静态Multimaps.asMap()
方法。 相似方法存在于SetMultimap
于SortedSetMultimap
中。) Multimap.containsKey(key)
当且仅当存在于当前key相关联的元素才会返回true。特别的是, 如何key之前关联着一个或多个value因为从multimap中被移除Multimap.containsKey(k)
将返回 false.Multimap.entries()
返回包含Multimap中所有的key的
entries. 如果想要一个key的集合请使用asMap().entrySet()
。Multimap.size()
返回整个multimap中的条目数,而不是不同键的数目。 使用Multimap.keySet().size()去获取
不同键的数目。
Implementations
Multimap
提供多种多样的实现. 你能够在想使用 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 |
除了immutable类其他的实现都支持null键与null值。
*
LinkedListMultimap.entries()
保持不同关键值的顺序。详情请参阅链接。
**
LinkedHashMultimap
保存条目的插入顺序,以及键的插入顺序,以及与任何一个键相关联的值集。
注意不是所有的实现都真正的实现了 Map<K, Collection<V>>
! (一些Multimap
实现使用自定义哈希表来最小化开销。)
如果您需要更多的定制,使用
Multimaps.newMultimap(Map, Supplier<Collection>)
或者 list 与set 去使用自定义集合、列表或集合实现自己的multimap.
BiMap
将值映射到键上的传统做法是维护两个分离的map并且保持他们之间同步,这是很容易出错的,并且当一个值已经存在于map中时会变得非常混乱。例如:
Map<String, Integer> nameToId = Maps.newHashMap();
Map<Integer, String> idToName = Maps.newHashMap();
nameToId.put("Bob", 42);
idToName.put(42, "Bob");
// what happens if "Bob" or 42 are already present?
// weird bugs can arise if we forget to keep these in sync...
BiMap<K, V>
是一种这样的 Map<K, V>
BiMap.put(key, value)
当你尝试将键映射到已经存在的值时将抛出 IllegalArgumentException
异常。删除具有指定值的任何存在的条目请使用 BiMap.forcePut(key, value)
。
BiMap<String, Integer> userId = HashBiMap.create(); ...
String userForId = userId.inverse().get(id);
Implementations
Key-Value Map Impl | Value-Key Map Impl | Corresponding BiMap |
---|---|---|
HashMap |
HashMap |
HashBiMap |
ImmutableMap |
ImmutableMap |
ImmutableBiMap |
EnumMap |
EnumMap |
EnumBiMap |
EnumMap |
HashMap |
EnumHashBiMap |
备注: BiMap
工具很像 Maps中的
synchronizedBiMap
。
Table
Table<Vertex, Vertex, Double> weightedGraph = HashBasedTable.create(); weightedGraph.put(v1, v2, 4);
weightedGraph.put(v1, v3, 20);
weightedGraph.put(v2, v3, 5);
weightedGraph.row(v1); // returns a Map mapping v2 to 4, v3 to 20
weightedGraph.column(v3); // returns a Map mapping v1 to 20, v2 to 5
典型地, 当您试图一次对多个键进行索引时将会发现像Map<FirstName, Map<LastName, Person>>
使用起来不合适。Guava 提供一种新的集合类型Table
, 其支出户任何 "row" 类型与"column" 类型. Table
支持多个视图,允许您从任意角度使用数据,包括:
rowMap()
, 返回形如Map<R, Map<C, V>>的
Table<R, C, V>
视图 ,简单说,rowKeySet()
返回Set<R>
.row(r)
返回一个非null的Map<C, V>
. 对Map
的写操作将穿透至Table
.- 提供相似的column 方法:
columnMap()
,columnKeySet()
,和
column(c)
. (基于列的访问比基于行的访问效率要低一些。) cellSet()
返回形如Table.Cell<R, C, V>的
Table
视图。Cell
像Map.Entry一样
, 但区分行键和列键。
提供的一系列Table
实现包括:
HashBasedTable
, 基本上是一个HashMap<R, HashMap<C, V>>
.TreeBasedTable
, 基本上是一个TreeMap<R, TreeMap<C, V>>
.ImmutableTable
ArrayTable
, 需要在建造将行与列全部完成,并且当表密集时,由二维数组支持,以提高速度和存储效率。ArrayTable
的实现与其他有所不同请参阅Javadoc以获取详细信息。
ClassToInstanceMap
有时map的key值不全是一样的类型,但是又想将不同类型的值映射到一种类型上去。Guava 提供的 ClassToInstanceMap
能够达到这个目的。
除了扩展 Map
接口外, ClassToInstanceMap
还提供了 T getInstance(Class<T>)
与
T putInstance(Class<T>, T)
方法, 在执行类型安全的同时,消除了不必要的需要分配。
ClassToInstanceMap
有着单一类型的参数,通常命名为B,表示映射管理的类型的上限。例如:
ClassToInstanceMap<Number> numberDefaults = MutableClassToInstanceMap.create();
numberDefaults.putInstance(Integer.class, Integer.valueOf(0));
从技术上说, ClassToInstanceMap<B>
实现了Map<Class<? extends B>, B>
-- 或者可以这样说, 一个B的子类map是B的实例。这使得ClassToInstanceMap与泛型稍微混淆
,但只要记住B
一直是最顶层的父类型就好了 -- 通常, B
就是 Object
.
Guava提供了很有的实现他们是 MutableClassToInstanceMap
与
ImmutableClassToInstanceMap
.
重要:其他的Map<Class, Object>
, 一个 ClassToInstanceMap
可以包含基元类型的条目,并且基元类型及其对应的包装类型可以映射到不同的值。
RangeSet
RangeSet被描述成离散且非空的
集合 。向RangeSet
中添加范围时任何连接的范围都会被合并,且空范围会被忽略。例如:
RangeSet<Integer> rangeSet = TreeRangeSet.create();
rangeSet.add(Range.closed(1, 10)); // {[1, 10]}
rangeSet.add(Range.closedOpen(11, 15)); // disconnected range: {[1, 10], [11, 15)}
rangeSet.add(Range.closedOpen(15, 20)); // connected range; {[1, 10], [11, 20)}
rangeSet.add(Range.openClosed(0, 0)); // empty range; {[1, 10], [11, 20)}
rangeSet.remove(Range.open(5, 10)); // splits [1, 10]; {[1, 5], [10, 10], [11, 20)}
将会合并 Range.closed(1, 10)
与Range.closedOpen(11, 15)
, 您必须先对范围进行预处理。Range.canonical(DiscreteDomain)
, 或 DiscreteDomain.integers()
.
NOTE: RangeSet
不支持GWT, 也不支持JDK 1.5 后端; RangeSet
需要充分使用JDK 1.6中的 NavigableMap
的特性。
Views
RangeSet
的实现支持非常广泛的视图,包括:
complement()
: 返回RangeSet
的补充视图. 补充也是RangeSet
, 它包含断开的非空范围。subRangeSet(Range<C>)
: 返回特定范围的RangeSet交叉视图。
包括headSet
,subSet
,与tailSet
传统排序集合视图。asRanges()
: 返回形容Set<Range<C>>
的RangeSet
视图,其能够被迭代遍历。asSet(DiscreteDomain<C>)
(ImmutableRangeSet
only): 返回形如ImmutableSortedSet<C>的
RangeSet<C>
视图 , 在范围内的元素,而不是范围本身。 (如果DiscreteDomain
和RangeSet既没有上界,也没有下界,则此操作不受支持。
Queries
除了对其视图进行操作之外,RangeSet还直接支持多个查询操作,其中最突出的是:
contains(C)
:RangeSet中最基础的操作
, 查询RangeSet中任何一个范围内是否包含特定的元素。
rangeContaining(C)
: 返回包含指定元素的范围,如果没有,则返回NULL。encloses(Range<C>)
: 直截了当的说,测试RangeSet
中的任何范围都包含指定范围。span()
: 返回RangeSet
内包含的每个范围的最小范围。
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"}
Views
RangeMap
提供两种视图:
asMapOfRanges()
: 返回Map<Range<K>, V>视图
. 例如能够被用来迭代RangeMap
.subRangeMap(Range<K>)
将返回RangeMap
的指定范围的交集视图。这包括了headMap
,subMap
,与tailMap
操作。