Guava中的集合类
Contents
Guava中的集合类
Guava提供了若干个新的集合类型,它们很常用但是却不在JCF(Java Collections Framework)中,这组集合类型遵循JCF的API接口约定,因此可以用熟悉的方式才操作它们,只要做很少的改动就可以将遗留代码迁移到新的集合上。
每个集合类型都有各自适用的场景,谨慎明智地选择其中之一。
Guava 提供了 Multiset Multimap BiMap Table RangeSet RangeMap 多种数据结构并且提供了很多种类的实现,如:
- Multiset 的实现包括
HashMultiset
TreeMultiset
LinkedHashMultiset
ConcurrentHashMulitset
ImmutableMulitset
EnumMultiset
; - MultiMap 的实现包括
ArrayListMultimap
HashMultimap
LinkedListMultimap
LinkedHashMultimap
TreeMultimap
ImmutableListMultimap
ImmutableSetMultimap
; - BiMap 的实现包括
HashBiMap
ImmutableBiMap
EnumBiMap
EnumHashBiMap
; - Table 的实现包括
HashBasedTable
TreeBasedTable
ImmutableTable
ArrayTable
;
这些实现跟JDK中的集合类型一样覆盖了哈希、链表、并发实现,并且提供了JDK中没有的不可变(immutable)实现,以在不同的场景下获得最佳的性能。
注意:所有这些新的集合类都有对应的 Immutable、Synchronized、Unmodified 实现。
Multiset
Multiset 类似于 Set,支持顺序无关的相等性,并且一个元素可以出现多次,从概念上讲就是一个包(bag)数据结构。Multiset 中相同的多个元素被认为是一个元素出现多次(occurrences),次数总和由 API count
方法返回。Wikipedia 中定义 Multiset 为“在数学中,允许一个元素出现多次的通用集合(set)的概念”,比如 {a,a,b} 和 {a,b} 是不同的 Multiset 但却是相同的 set。在 Multiset 中,元素是顺序无关的,因此 {a, a, b} 和 {a, b, a} 是一样的。
Multiset 优化了 Collection 的 contains
containsAll
remove
removeAll
retainAll
方法,使得即便在错误的类型或者 null 值时也不会抛出异常。除此之外,Multiset 还提供了 add(E, int)
remove(E, int)
setCount(E, int)
来批量添加、删除、设置元素的出现次数。当需要所有元素的出现次数总和时可以调用size()
方法。iterator()
将遍历所有元素的每次出现,它的长度等于 size 。elementSet()
提供了 Multiset 的非重复元素集合, entrySet()
方法同样返回非重复元素集合,在此基础上还提供每个元素的次数,这个集合由 Multiset.Entry
组成,每个对象包含 getElement()
和 getCount()
方法。Multiset 提供了两个静态方法 create()
和 create(Iterable<? extends E>)
来构建新的 Multiset 对象。
除了 TreeMultiset 用 compare 比较元素的同一性,别的实现都是用 equals 方法进行比较。TreeMultiset 是 SortedMultiset 的一种实现,支持快速选取子 Multiset 以及按大小进行遍历。
Multiset 和 ArrayList 有些类似,除了在 Multiset 中元素顺序是无关的。Multiset 和 Map<E, Integer> 也有些类似,除了 Multiset 不能包含 0 或者负数的出现次数。
需要注意的是 Multiset 中的元素出现次数一定是正整数,当为 0 时,元素将从 Multiset 中删除。如果你需要的集合能够应对 0 、负数或者范围超出整数时,可以使用 Guava 中的 AtomicLongMap 。
Multimap
Multimap 是一种类似于 Map 的数据结构,并且一个键可以关联多个值。你可以把 multimap 看做一个键对应非空集合的映射表,也可以把它看做多个键值对的扁平结构。比如:
a->[1, 2, 4] b->[3] c->[5] 和 a->1 a->2 a->4 b->3 c->5
是等价的。最重要的是,一个键不会对应一个空集合,即:一个键要么至少对应一个值,要么不存在于 Multimap 中。尽管在概念上看第一种方式更能够表达问题域,但是 Multimap 的 API 设计是基于第二种形式的。所以上面的 size()
方法将返回 5 而不是 3,values()
方法返回 [1, 2, 4, 3, 5] 而不是 [[1,2,4], [3], [5]] 。put(K, V)
将键值对添加到 Multimap 中,putAll(K, Iterable<V>)
将键与每个值的关联添加进来,remove(K, V)
将键值对从 Multimap 中移除, removeAll(K)
移除并返回与键关联的所有值的集合,修改这个集合将不影响 Multimap 。replaceValues(K, Iterable<V>)
替换与键关联的所有值为新的值集合,并返回原来的值集合,修改这个集合将不影响 Multimap 。
Multimap 有一个非常重要的方法: get(key)
返回与键关联的值的视图,可以通过这个视图给对应的键添加、删除值,如果一个键不在 Multimap 中将返回空集合而不是 null
值。这个视图与 Multimap 是相互影响的,修改任何一个都会同时改变另外一个。示例代码:
|
|
这样做的一个好处是不用在添加键值对之前先添加一个空集合,比如:
|
|
虽然一个不在 Multimap 中的键调用 get
方法时会返回空集合,但是 containsKey()
方法将返回 false
,表示对应的键不在 Multimap 中。如果任何操作会导致一个键与 0 个值关联将导致键从 Multimap 中移除。如果需要在不存在时返回 null
值,可以调用 asMap().get(K)
方法。
Multimap 拥有多个视图,这些视图反应了 Multimap 的最新状态,并且与 Multimap 是相互影响,改变任何一方都会影响(write-through) Multimap 的底层。反过来 Multimap 的实际内容改变也会同时影响到视图。
asMap(): Map<K, Collection<V>>
将 Multimap<K, V> 当做一个 Map<K, Collection> ,与一个键相关的所有值都在 Collection 中,可以往返回的 Collection 中添加/替换/删除数据,但不能替换/删除 Collection 本身;keys(): Multiset<K>
将 Multimap 中的键与出现次数当做一个 Multiset ,可以对 Multiset 进行删除但不能新增,结果将反映到 Multimap 本身;keySet(): Set<K>
将 Multimap 的所有去重后的键当做一个 Set ;values(): Collection<V>
返回所有值的扁平集合;entries(): Collection<Entry<K,V>>
返回所有键值对的扁平集合;
以上所有视图都不是真正的创建对应的集合,而是代理到 Multimap 本身,所以才称之为视图;
Multimap 有两个子接口:ListMultimap
和 SetMultimap
。ListMultimap 中同一个键的值集合类似于 List,并保存插入顺序。SetMultimap 中的同一键的值的集合类似于 Set,值得注意的是 SetMultimap 中同一键的值不能重复,而 ListMultimap 的值可以重复。如果希望 SetMultimap 中的值按大小排序,可以选择 SortedSetMultimap 接口。更常用的是这些子接口而不是通用接口,这个跟 Collection 与 List 和 Set 的关系类似。需要注意的是以上接口只指定了值的顺序并没有具体指定键的顺序。键的顺序和值的顺序是相互独立的。对于 SetMultimap
的实现 TreeMultimap
保存了键和值的大小顺序,LinkedHashMultimap
保存了键和值的插入顺序,HashMultimap
没有保存键和值的任何顺序。
需要注意的是多种 Multimap 实现的 equals
和 hashCode
的实现策略很可能是不同的,即便键值对都完全一致,因为对于 ListMultimap 键值对的顺序也会产生影响,所以最好不要拿它们去做比较,而子接口则做了更强的保证。除了以上标准实现外,可以通过传入指定的 Map 和 Collection 的类型给 Multimaps.newMultimap
方法族来定制自己的 Multimap 。
BiMap
BiMap 是一种键和值都是唯一的映射表(Map),BiMap 的这种限制让它能够非常高效的表示由值查找键的反向视图(inverse view),因而也被成为双向映射(Bidirectional Map)。在没有 BiMap 的情况下,要表示值到键的映射需要维护另外一个独立的 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>,更进一步的,通过 inverse()
得到一个反向 BiMap<V, K>,确保所有的值都是唯一的,因而 values()
返回一个 Set
。
BiMap.put(key, value)
会在值已经存在的情况下抛出 IllegalArgumentException
,如果希望删除掉之前值对应的条目(Entry)调用 BiMap.forcePut(key, value)
。
Author zoro.wang
LastMod 2016-10-30