Guava学习之Collections ——New Collection Types

新集合类型

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对于多种不同元素的实现消耗的内存时线性的。

尤其是, MultisetCollection接口契约完全一致,这是JDK中的罕见实例--- 此外, TreeMultiset, 像TreeSet,使用comparison 在执行相同对比以代替Object.equals. 特殊的是, Multiset.addAll(Collection) 为每一个出现的元素都添加一个事件,这比上述中Map的for循环更加方便。

MethodDescription
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的实现对应如下图所示:.

MapCorresponding MultisetSupports null elements
HashMapHashMultisetYes
TreeMapTreeMultisetYes
LinkedHashMapLinkedHashMultisetYes
ConcurrentHashMapConcurrentHashMultisetNo
ImmutableMapImmutableMultisetNo

SortedMultiset

SortedMultisetMultiset 的一种变种,支持在特定范围上取子集。例如, 可以使用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 的方法(更直接)包括:

SignatureDescriptionEquivalent
put(K, V)增加key对应的valuemultimap.get(key).add(value)
putAll(K, Iterable<V>)一次添加key对应的每一个valueIterables.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 同样支持更有力的视图:

  • asMapMultimap<K, V> 转换为形如 Map<K, Collection<V>>的视图。返回的map支持remove, 以及写操作会改变原集合,但是不支持putputAll方法. 然而,你能够使用 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>>地方使用:

ImplementationKeys behave like...Values behave like..
ArrayListMultimapHashMapArrayList
HashMultimapHashMapHashSet
LinkedListMultimap *LinkedHashMap``*LinkedList``*
LinkedHashMultimap**LinkedHashMapLinkedHashSet
TreeMultimapTreeMapTreeSet
ImmutableListMultimapImmutableMapImmutableList
ImmutableSetMultimapImmutableMapImmutableSet

除了immutable类其他的实现都支持null键与null值。

* LinkedListMultimap.entries() 保持不同关键值的顺序。详情请参阅链接。

** LinkedHashMultimap 保存条目的插入顺序,以及键的插入顺序,以及与任何一个键相关联的值集。

注意不是所有的实现都真正的实现了 Map<K, Collection<V>> ! (一些Multimap实现使用自定义哈希表来最小化开销。)

 

如果您需要更多的定制,使用Multimaps.newMultimap(Map, Supplier<Collection>) 或者 listset 去使用自定义集合、列表或集合实现自己的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>

  • 允许使用inverse()BiMap<V, K>的视图“反转”。
  • 确保值都是唯一的,使values()是一个Set

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 ImplValue-Key Map ImplCorresponding BiMap
HashMapHashMapHashBiMap
ImmutableMapImmutableMapImmutableBiMap
EnumMapEnumMapEnumBiMap
EnumMapHashMapEnumHashBiMap

备注: 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支持多个视图,允许您从任意角度使用数据,包括:

提供的一系列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操作。
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值