Guava 集合工具类

Multiset

名如其意,多重Set,意思是可以重复添加,Multiset可以使用count函数将重复添加的key值进行统计。

特点:

  • 这就像ArrayList<E>没有排序约束:排序无关紧要。
  • 这就像一个Map<E, Integer>,带有元素和计数。

Guava 的MultisetAPI 结合了对以下方式的思考:

  • 当被视为一个 普通的集合(Collection)时,Multiset其行为很像一个 无序的 ArrayList
    • 调用add(E)添加给定元素的单次出现。
    • Multiset的iterator()迭代每个元素的每次出现。
    • Multiset的size()是所有元素的所有出现的总数。
  • 额外的查询操作以及性能特征将其视作为Map<E, Integer>.
    • count(Object)返回与该元素关联的计数。对于  HashMultiset,count 是 O(1),对于 TreeMultiset,count 是 O(log n),等等。
    • entrySet()返回 Set<Multiset.Entry<E>>,其工作方式类似于 的 entrySet Map
    • elementSet()返回Set<E>多重集的不同元素 ,就像keySet()a一样Map
    • 实现的内存消耗Multiset与不同元素的数量成线性关系。

值得注意的是MultiSet并不是一个Map,尽管它实现了类似于Map<E, Integer>的功能,MultiSet是一个集合类型,实现了Collection接口,实现了Collection接口规范。

实现

Guava 提供了很多 的实现Multiset大致对应 JDK 的 Map 实现。

MapMutlliSet支持null元素
HashMapHashMultiset
TreeMapTreeMultiset
LinkedHashMapLinkedHashMultiset
ConcurrentHashMapConcurrentHashMultiset
ImmutableMapImmutableMultiset

SortedMultisetMultiset接口的一种变体,它支持在指定范围内有效地获取子多集。TreeMultiset实现SortedMultiset接口。


SortedMultiset<String> sortedMultiset= TreeMultiset.create();
//后面的数字occurrences代表出现的次数,可以手动指定,不指定将自动统计
sortedMultiset.add("a",2);
sortedMultiset.add("b",4);
sortedMultiset.add("c",5);
sortedMultiset.add("d",7);
System.out.println(sortedMultiset.count("b"));
//截取子结果集,BoundType.OPEN表示开区间,不包含临界值,BoundType.CLOSE表示闭区间,包含临界值
System.out.println(sortedMultiset.subMultiset("a",BoundType.OPEN,"d",BoundType.CLOSED));

输出:

4
[b x 4, c x 5, d x 7]

Multimap

多重Map,可以重复添加key和键值,内部实现为Map<K, Collection<V>>

Multimap<K,V>相当于Map<K, List<V>>或者Map<K, Set<V>>

创建 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();

Multimap<K, V>虽然内部使用Map<K, Collection<V>>实现 ,但是与Map有些许的不同点:

  • Multimap.get(key)总是返回一个非空的的集合。这并不意味着 multimap 会花费与键关联的任何内存,而是返回的集合是一个视图,允许您根据需要添加与键的关联。
  • 如果你更喜欢像Map一样不存在值的时候返回null,请使用asMap()视图获取
  • Multimap.entries()返回key的entry。如果想使用key-value的entry,请使用asMap().entrySet().
  • Multimap.size()返回整个Multimap中的条目数,而不是键的数量。使用Multimap.keySet().size()获取键的数量。

另外一种就是直接使用实现类进行create();

实现
实现类键相当于…值相当于..
ArrayListMultimapHashMapArrayList
HashMultimapHashMapHashSet
LinkedListMultimap *LinkedHashMap``*LinkedList``*
LinkedHashMultimap**LinkedHashMapLinkedHashSet
TreeMultimapTreeMapTreeSet
ImmutableListMultimapImmutableMapImmutableList
ImmutableSetMultimapImmutableMapImmutableSet

BiMap

双向Map,我们经常有需要用到值映射回key的操作,比如:

Map<String, Integer> nameToId = Maps.newHashMap();
Map<Integer, String> idToName = Maps.newHashMap();
nameToId.put("Bob", 42);
idToName.put(42, "Bob");

上面这种情况一般需要构造两个Map,并且要使它们put的值都保持同步,如果忘了或者粗心都会导致bug,还有可能是重复的key值导致的bug。BigMap就是为了解决这种现象而设计的。

BiMap特点

  • 支持值(value)获取键(key),只需要调用inverse()函数即可
  • 确保键值唯一,BiMap.put(key, value)存在重复的key的时候会抛出 IllegalArgumentException异常,如果你不想抛异常可以使用BiMap.forcePut(key, value) 强制put进去,避免键值重复导致的bug。
BiMap<String, Integer> userId = HashBiMap.create();
...

String userForId = userId.inverse().get(id);
实现
Key-Value Map实现Value-Key Map实现BiMap实现
HashMapHashMapHashBiMap
ImmutableMapImmutableMapImmutableBiMap
EnumMapEnumMapEnumBiMap
EnumMapHashMapEnumHashBiMap

Table

通常我们要用到多重映射的时候会这样使用Map<String,Map<String,String>>,这样显得很臃肿,也很难用,Guava提供了新的集合类型Table,Table引用了表格的概念,行,列值代替双重Map。

Table<String,String,String> 第一个值代表是行(row),第二个代表列(column),第三个值代表值(value)通过一行一列可以确定一个值。

        Table<String, String, Integer> table = HashBasedTable.create();
        table.put("行1", "列1", 4);
        table.put("行2", "列2", 20);
        table.put("行3", "列2", 5);

        table.row("行1"); // 返回Map结果集 列1 to 4, 列2 to 20
        table.column("列2"); // 返回Map结果集 行2 to 20, 行3 to 5

Table支持多种视图,让您可以从任何角度使用数据,包括

实现
  • HashBasedTable, 基本对应HashMap<R, HashMap<C, V>>.
  • TreeBasedTable, 基本对应TreeMap<R, TreeMap<C, V>>.
  • ImmutableTable
  • ArrayTable,这要求在构建时指定完整的行和列,但在表密集时由二维数组支持以提高速度和内存效率。ArrayTable与其他实现方式有些不同;

不可变集合

为什么使用不可变集合?

不可变对象有很多优点,包括:

  • 不受信任的库可以安全使用。
  • 线程安全:可以被许多线程使用,没有竞争条件的风险。
  • 不需要支持可变性,可以尽量节省空间和时间的开销, 所有的不可变集合实现都比可变集合更加有效的利用内存。

制作对象的不可变副本是一种很好的防御性编程技术。Guava 为每种标准类型提供简单易用的不可变版本 Collection,包括 Guava 自己的Collection变体。

JDK 提供了Collections.unmodifiableXXX方法,但是没有做到真正的不可变,当你引用对象添加或删除值的时候,不可变对象也会跟着变化,比如:

  List<String> list = new ArrayList<String>();
  list.add("a");
  list.add("b");
  list.add("c");
  
  List<String> unmodifiableList = Collections.unmodifiableList(list);
  list.add("d");
  System.out.println(unmodifiableList);

我们对list添加了一个元素,结果输出不可变集合也跟着变化了,这并不是我们期望得到的结果。

使用Guava的不可变集合可以解决这个问题。

提示当你不希望修改集合或希望集合保持不变时,最好将其防御性地复制到不可变集合中。每个 Guava 不可变集合实现都拒绝空值要添加null元素的话可以使用Collections.unmodifiableList的实现。

如何去使用

可以ImmutableXXX通过多种方式创建集合:

  • 使用该copyOf方法,例如,ImmutableSet.copyOf(set)
  • of例如,使用该方法,ImmutableSet.of("a", "b", "c")或 ImmutableMap.of("a", 1, "b", 2)
  • 使用Builder,例如:
public static final ImmutableSet<Color> GOOGLE_COLORS =
   ImmutableSet.<Color>builder()
       .addAll(WEBSAFE_COLORS)
       .add(new Color(0, 191, 255))
       .build();

所有不可变集合都有一个asList()方法提供ImmutableList视图,来帮助你用列表形式方便地读取集合元素。例如,你可以使用sortedSet.asList().get(k)从ImmutableSortedSet中读取第k个最小元素。

实现
接口JDK /Guava不可变实现
CollectionJDKImmutableCollection
ListJDKImmutableList
SetJDKImmutableSet
SortedSet/NavigableSetJDKImmutableSortedSet
MapJDKImmutableMap
SortedMapJDKImmutableSortedMap
MultisetGuavaImmutableMultiset
SortedMultisetGuavaImmutableSortedMultiset
MultimapGuavaImmutableMultimap
ListMultimapGuavaImmutableListMultimap
SetMultimapGuavaImmutableSetMultimap
BiMapGuavaImmutableBiMap
ClassToInstanceMapGuavaImmutableClassToInstanceMap
TableGuavaImmutableTable

参考资料

https://github.com/google/guava/wiki/ImmutableCollectionsExplained

https://github.com/google/guava/wiki/NewCollectionTypesExplained