Guava新集合类型

guava的collection包里新增加了几个集合类型非常实用
新增类型有:Multiset、Multimap、BiMap、Table、



Multiset

Multiset继承自Collection, 类似于Set, 里面的元素是无顺序的, 但不同的是它可以多次添加相等的元素, 并能记录每个元素的个数. Multiset {a, a, b}和{a, b, a}是相等的, Multiset类似于但绝不等同于Map<E, Integer>.
Collection中的方法Multiset都有, 注意size()方法, 重复的元素也会算个数(类似的其它方法也会包含重复元素)
除此之外Multiset接口中定义的方法有:

  • int count(Object element): 返回给定元素的计数
  • int add(E element, int occurrences): 添加元素并指定元素个数; 返回添加之前该元素的个数, 一般为0
  • int remove(Object element, int occurrences): 移除元素, 若该元素个数小于指定个数,则全移除; 返回操作之前该元素的个数
  • int setCount(E element, int count): 设定某一个元素的重复次数, 相当于add和remove的组合体; 返回操作之前该元素的个数
  • boolean setCount(E element, int oldCount, int newCount): 将符合原有重复个数的元素修改为新的重复次数, 原来个数不为oldCount不会修改

视图操作

对视图的所有操作都会反映到原来的Multiset上

  • Set<E> elementSet(): 返回仅包含不同元素的set, 对set进行移除, 会反映到multiset上(若multi中有keyX2, 则会全被移除)
  • Set<Entry<E>> entrySet(): 返回Set, 包含的Entry支持使用`getElement()`和`getCount()`

Multiset的各种实现

Multiset的实现类, 可以通过构造方法new出来, 也可以调用XXXMultiset.create()静态函数来创建
它们都直接继承了Multiset接口:

Guava实现 对比JDK的Map 是否支持null
HashMultiset HashMap Y
LinkedHashSet LinkedHashMap Y
TreeMultiset TreeMap Y
EnumMultiset EnumMap N
ImmutableMultiset ImmutableMap N
ConcurrentHashMultiset ConcurrentHashMap N
ForwardingMultiset    

Multimap

Multimap把一个键映射到多个值, 类似于但不等同于Map<K, Collection<V>>.
Multimap跟JDK中的Map并没有什么关系, 但Map中的方法Multimap也有对应的

Multimap接口定义的普通方法有:

  • int size(): 返回键值对的个数 a->1, a->2算两个
  • boolean isEmpty(): 是否为空
  • boolean containsKey(Object key): 是否包含key
  • boolean containsValue(Object value): 是否包含value
  • boolean containsEntry(Object key, Object value): 是否包含key-value对
  • boolean put(K key, V value): 如果map元素增加了则返回true, 对于允许存在重复键值对的实现类总是返回true, 不允许重复键值对存在的实现类才有可能返回false
  • boolean putAll(K key, Iterable values): map改变则返回true
  • boolean putAll(Multimap multimap): map改变则返回true
  • boolean remove(Object key, Object value): 移除键值对, map变了返回true
  • Collection<V> removeAll(Object key): 移除与key相关的所有values(key也没了), 并返回values组成的集合(可能为空)
  • Collection<V> replaceValues(K key, Iterable values): 把与key相关的value全替换掉, 如果values为空, 则等同于removeAll(key), 如果原来不包含key,则相当于putAll(key, values); 返回被替换掉的value集合(可能为空)
  • void clear(): 清空map
  • Collection<V> get(K key): 返回key对应的value, 没有key则返回空集合(不是null)

视图操作

对这些返回结果的所有操作都会反映到原来的Multimap上

  • Set<K> keySet(): 返回不重复的key集合
  • Multiset<K> keys(): 返回可重复的key集合
  • Collection<V> values(): 返回value的集合, 包含重复值
  • Collection<Map.Entry<K, V>> entries(): 返回所有键值对,包括重复键
  • Map<K, Collection<V>> asMap(): 返回Map<K,Collection<V>>形式的视图, 返回的Map支持remove操作, 并且会反映到Multimap, 但它不支持put或putAll操作; ListMultimap的asMap.get(key)不能直接返回List, 可以使用Multimaps.asMap.get(key)来返回具体的集合类型

Multimap的各种实现

实现类 键行为类似 值行为类似 是否支持null
LinkedListMultimap LinkedHashMap LinkedList Y
ArrayListMultimap HashMap ArrayList Y
ImmutableListMultimap ImmutableMap ImmutableList N
HashMultimap HashMap HashSet Y
LinkedHashMultimap LinkedHashMap LinkedHashSet Y
ImmutableSetMultimap ImmutableMap ImmutableSet N
TreeMultimap TreeMap TreeSet Y

其实现类的继承关系图如下: Multimap继承关系图


BiMap

BiMap是个特殊的Map(继承自JDK的Map), 它可以很方便地实现key-value的双向映射, 所以它要求value也必须是唯一的
BiMap的put方法与Map不太一样, put键值对KV时:
若先前KV都不存在,直接put; KV都已存在,则相当于没改变; K在V不在, 则KV替换KV';(至此与Map都一样)
K不在V在, Map可以直接put进去, 但BiMap则不可以
与Map意义不一样的方法:

  • V put(K key, V value): K不在V在时, 抛出IllegalArgumentException
  • void putAll(Map map): 同样可能抛异常, 但有可能只加了部分元素进去, 这取决于迭代顺序, 在发生异常之前迭代到的元素可能已经添加进去了
  • Set<V> values(): 视图操作, 由于BiMap里的value是唯一的, 因此返回的是Set而不是Collection
  • boolean containsValue(Object value): 是否包含value

BiMap比Map新增的方法:

  • V forcePut(K key, V value): K不在V在时, 则KV替换K'V; 返回先前与key关联的value, 若先前没有相同的key则返回null
  • BiMap<V, K> inverse(): 视图操作, 返回value到key的映射Map, 两个map里的数据是公用的, 即删除V1->K1时, K1->V1也没了

BiMap接口的实现类

实现类 key-value value->key
HashBiMap HashMap HashMap
EnumBiMap EnumMap EnumMap
EnumHashBiMap EnumMap HashMap
ImmutableBiMap ImmutableMap ImmutableMap

Table

Table支持两个键进行, 就像名字一样, 可以通过行和列确定一个元素
Table是这样定义的Table<R, C, V>, 它提供了多种视图:

  • Map<R, Map<C, V>> rowMap(): 用Map<R, Map<C, V>>表现Table<R, C, V>
  • Set<R> rowKeySet(): 返回行的集合Set<R>
  • Map<C, V> row(R rowKey): 用Map<C, V>返回给定行的所有列,对这个map进行的写操作也将写入Table中
  • Map<C, Map<R, V>> columnMap()
  • Set<C> columnKeySet()
  • Map<R, V> column(C columnKey)
  • Set<Cell<R, C, V>> cellSet(): 用元素类型为Table.Cell<R, C, V>的Set表现Table<R, C, V>. Cell类似于Map.Entry,但它是用行和列两个键区分的
  • Collection<V> values(): 返回V的集合

Table接口的实现类

实现类 本质 说明
HashBasedTable HashMap<R, HashMap<C, V>>  
TreeBasedTable TreeMap<R, TreeMap<C,V>>  
ArrayTable 二维数组 要求在构造时就指定行和列的大小
ImmutableTable ImmutableMap<R, ImmutableMap<C, V>> 这是个抽象类,对稀疏或密集的数据集都有优化

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)}

注意:

  • 要合并Range.closed(1, 10)Range.closedOpen(11,15)这样的区间, 你需要首先用Range.canonical(DiscreteDomain)对区间进行预处理,例如DiscreteDomain.integers()
  • RangeSet不支持GWT,也不支持JDK5和更早版本;因为,RangeSet需要充分利用JDK6中NavigableMap的特性

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"}