coding……
但行好事 莫问前程

Guava新集合类型

开发中,集合类型无疑是JDK最常用的数据类型之一。但是JDK提供的诸如List、Set、Map都是一些基础的集合类型,有这特定的使用场景限制,往往不能满足我们的开发需求,比如集合类型嵌套的情况,Map<string, List<String>>甚至更复杂的嵌套类型等。Guava在JDK集合框架的基础上新开发了一套常用的集合类型,以简化开发。包括MutilSet、MultiMap、BiMap、Table、ClassToinstanceMap、RangeSet、RangeMap等等,本文依照我的理解,对MultisetMultiMapBiMap做一些简单的介绍,如有需要了解其他集合类型的可以去看一下Guava官方用户文档

  • MultiSet

JDK中的set和数学上集合的概念是相同的,Set最大的特性就是不允许在其中存放重复的元素可以被用来过滤在其他集合中存放的元素,从而得到一个没有包含重复新的集合。假如需要统计一个词在文档中出现了多少次,传统的做法是这样的:

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提供了一种新型的set集合类型 Multiset,它可以多次添加相等的元素。同时,Multiset继承自JDK中的Collection接口,而不是Set接口,并没有破坏Set的原有特性。除了支持添加重复的元素,其他的属性跟JDK中的set相同,比如都是无序集合。

S.N.
方法及说明
1 boolean add(E var1)
MultiSet集合中添加一个元素var1
2 int add(@Nullable E var1, int var2)
MultiSet集合中添加var2个var1元素
3 boolean contains(@Nullable Object var1)
MultiSet集合中是否包含var1
4 boolean containsAll(Collection<?> var1)
MultiSet集合中是否包含集合var1
5 int count(@CompatibleWith(“E”) @Nullable Object var1)
MultiSet集合中var1出现的次数
6 Set<E> elementSet()
MultiSet转set(去除重复元素,转化为JDK中的标准Set)
7 Set<Multiset.Entry<E>> entrySet()
返回MultiSet的Entry组成的set,和Map的entrySet类似,Entry包含元素信息和元素的出现次数信息
8 Iterator<E> iterator()
返回一个迭代器,包含Multiset的所有元素(包括重复的元素)
9 boolean remove(@Nullable Object var1)
从MultiSet中删除var1,如果var1出现多次,执行此操作后出现次数减1
10 int remove(@CompatibleWith(“E”) @Nullable Object var1, int var2)
从MultiSet中减少var2次计数,如果var2大于var1在MultiSet中出现的次数,则var1的出现次数为0(被完全移除)
11 boolean removeAll(Collection<?> var1)
从MultiSet中清除列表var1包含的元素,无论列表var1中元素出现多少次,MultiSet都会将包含的var1中的元素清除掉
12 boolean retainAll(Collection<?> var1)
保留MultiSet中列表var1包含的元素
13 int setCount(E var1, int var2)
设置元素var1在MultiSet中的出现次数,无论var1是否在MultiSet中出现过
14 boolean setCount(E var1, int var2, int var3)
将MultiSet中var1元素出现的次数由var2修改为var3,var2必须上送正确的出现次数,否则不生效,如果var1没出现过,上送0
15 int size()
返回集合元素的总个数(包括重复的元素)

示例代码:

@Test
public void multiSetTest() {
    Multiset<String> multiset = HashMultiset.create();
    multiset.add("a");
    multiset.add("b", 2);
    List<String> addList = Lists.newArrayList("c", "c", "c");
    multiset.addAll(addList);
    /*输出 [a, b x 2, c x 3] */
    System.out.println(multiset);
    assertEquals(2, multiset.count("b"));

    assertEquals(6, multiset.size());

    assertTrue(multiset.contains("a"));

    assertTrue(multiset.containsAll(Lists.newArrayList("a", "c")));
    assertFalse(multiset.containsAll(Lists.newArrayList("a", "c", "e")));

    Set<String> set = Sets.newLinkedHashSet("a", "b", "c");
    assertEquals(set, multiset.elementSet());

    Set<Multiset.Entry<String>> setEntry = multiset.entrySet();
    setEntry.forEach(entry -> {
        System.out.println(entry.getElement() + ": " + entry.getCount());
    });

    multiset.remove("a");
    assertEquals(5, multiset.size());
    multiset.remove("b", 10);
    assertEquals(3, multiset.size());
    multiset.removeAll(Lists.newArrayList("c"));
    assertEquals(0, multiset.size());

    multiset.setCount("x", 10);
    assertEquals(10, multiset.size());

    multiset.setCount("y", 3);
    assertEquals(13, multiset.size());

    multiset.setCount("y", 3, 5);
    assertEquals(15, multiset.size());
    multiset.setCount("y", 3, 8);
    assertEquals(15, multiset.size());
}

Guava提供了多种Multiset的实现,大致对应JDK中Map的各种实现:

Map 对应的Multiset  是否支持null元素
HashMap HashMultiset
TreeMap TreeMultiset 是(如果comparator支持的话)
LinkedHashMap LinkedHashMultiset
ConcurrentHashMap ConcurrentHashMultiset
ImmutableMap ImmutableMultiset
  • MultiMap

JDK中的Map,如果先后为同一个key添加value,后添加的value会覆盖之前添加的value,所以当需要一个key可以映射多个value值时,往往需要做类似于这样的Map<K, List<V>>或Map<K, Set<V>>这样的嵌套,而这种嵌套的操作往往是比较繁琐的。Guava中提供了一种新的Map集合类型MultiMap,允许在map中一个key映射多个value值。

S.N. 方法及说明
1 Map<K, Collection<V>> asMap()
将MultiMap转为嵌套的JDK中的Map视图,返回的Map支持remove操作,不支持put和putAll操作,对视图的操作会影响MultiMap
2 void clear()
清空MultiMap
3 boolean containsKey(@CompatibleWith(“K”) @Nullable Object var1)
MultiMap是否包含key var1
4 boolean containsValue(@CompatibleWith(“V”) @Nullable Object var1)
MultiMap是否包含value var1
5 Collection<Entry<K, V>> entries()
返回MultiMap中所有键-单个值的视图
6 Collection<V> get(@Nullable K var1)
返回MultiMap中key var1对应的value值的集合
7 boolean isEmpty()
判断MultiMap是否为空(size() == 0)
8 Multiset<K> keys()
用Multiset表示Multimap中的所有键,每个键重复出现的次数等于它映射的值的个数,返回的是一个视图,返回的视图不支持添加操作,对视图的操作会影响MultiMap
9 Set<K> keySet()
用Set表示Multimap中所有不同的键,返回的是一个视图,返回的视图不支持添加操作,对视图的操作会影响MultiMap
10 boolean put(@Nullable K var1, @Nullable V var2)
MultiMap中添加键值对
11 boolean putAll(@Nullable K var1, Iterable<? extends V> var2)
MultiMap中为key var1依次添加多个值的映射
12 boolean remove(@CompatibleWith(“K”) @Nullable Object var1, @CompatibleWith(“V”) @Nullable Object var2)
从MultiMap中移除键到值的映射,如果var2不存在,不会对MultiMap产生任何影响
13 Collection<V> removeAll(@CompatibleWith(“K”) @Nullable Object var1);
清除对应的所有值,返回的集合包含所有之前映射到K的值,注意对返回集合的操作不影响MultiMap
14 Collection<V> replaceValues(@Nullable K var1, Iterable<? extends V> var2)
清除键对应的所有值,并重新把key关联到Iterable中的每个元素,返回的集合包含所有之前映射到K的值,如果key var1不存在,则会新建一个var1 key,并且将var2作为value返回的集合不是MultiMap的视图,对集合的操作不影响MultiMap
15 int size()
返回MultiMap总的键值对的数量
16 Collection<V> values()
返回MultiMap中的所有值,返回一个视图,视图支持添加、删除操作,对视图的操作不会影响MultiMap

示例代码:

@Test
public void multiMapTest(){
    Multimap<String, String> multimap = ArrayListMultimap.create();
    multimap.put("key1", "this");
    multimap.put("key1", "is");
    multimap.put("key1", "key1List");
    System.out.println(multimap);
    assertEquals(3, multimap.size());
    List<String> key2List = Lists.newArrayList("this", "is", "key2List");
    multimap.putAll("key2", key2List);
    System.out.println(multimap);
    assertEquals(6, multimap.size());

    assertTrue(multimap.containsKey("key1"));
    assertTrue(multimap.containsValue("key2List"));

    Multiset<String> key1MultiSet = multimap.keys();
    assertEquals(3, key1MultiSet.count("key1"));
    /*对keys返回的视图操作会影响multiMap*/
    key1MultiSet.remove("key1");
    System.out.println(multimap);
    assertEquals(5, multimap.size());

    /*对keySet返回的视图操作会影响multiMap*/
    Set<String> keySet = multimap.keySet();
    assertEquals(2, keySet.size());
    keySet.remove("key1");
    System.out.println(multimap);

    multimap.remove("key2", "this");
    System.out.println(multimap);
    multimap.remove("key2","testDeleteNonExistValue");
    System.out.println(multimap);
    assertEquals(2, multimap.size());

    List<String> key3List = Lists.newArrayList("key3","is", "create");
    multimap.putAll("key3", key3List);
    multimap.removeAll("key2");
    System.out.println(multimap);
    assertEquals(3, multimap.size());

    key3List = Lists.newArrayList("new", "key3","has","been","created");
    multimap.replaceValues("key3", key3List);
    System.out.println(multimap);
    assertEquals(5, multimap.size());
    /*key2不存在,新建key2*/
    multimap.replaceValues("key2", key3List);
    System.out.println(multimap);
    assertEquals(10, multimap.size());

    List<String> valuesCollection = Lists.newArrayList(multimap.values());
    /*对values返回的视图的操作不会影响multiMap*/
    valuesCollection.removeAll(Lists.newArrayList("key3"));
    valuesCollection.add("addNew");
    System.out.println(multimap);
    assertEquals(10, multimap.size());

    multimap.clear();
    assertTrue(multimap.isEmpty());
}

Guava提供了多种Multiset的实现,可以在大多数使用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
  • BiMap

如果要实现一个双向Map,需要维护两个单独的map,并保持它们间的同步,显然这种方式很容易出错,比如更新一个Map的时候忘了同步更新另一个Map就会产生一些bug。Guava中提供了一个新型的双向Map集合类型BiMap,继承自java.util.Map

S.N. 方法及说明
1 V put(@Nullable K var1, @Nullable V var2)
BiMap添加key为var1,value为var2的键值对
2 V forcePut(@Nullable K var1, @Nullable V var2)
如果BiMap中var2已存在,则会将原键值对删除,插入新的键值对
3 Set<V> values()
返回BiMap value集合
4 BiMap<V, K> inverse()
反转BiMap<K, V>的键值映射

注:BiMap继承自Map,Map中的方法,BiMap也可以使用

示例代码:

@Test
public void biMapTest(){
    BiMap<Integer, String> idNameBiMap = HashBiMap.create();
    idNameBiMap.put(1, "Michael");
    idNameBiMap.put(2, "Jane");
    idNameBiMap.put(3, "Marry");
    /*put添加已存在的value会报IllegalArgumentException*/
    assertThatThrownBy(()->idNameBiMap.put(4, "Marry"))
            .isInstanceOf(IllegalArgumentException.class)
            .hasNoCause();

    /*put添加已存在的key,会覆盖之前key对应的value值*/
    idNameBiMap.put(3,"zhuoli");
    System.out.println(idNameBiMap);
    assertEquals(3, idNameBiMap.size());

    /*forcePut强制插入已存在的valu,会删除之前的键值对 2 - Jane*/
    idNameBiMap.forcePut(4, "Jane");
    System.out.println(idNameBiMap);
    assertEquals(3, idNameBiMap.size());

    Set<String> biMapValuesSet = idNameBiMap.values();
    System.out.println(biMapValuesSet);

    BiMap<String, Integer> nameIdBiMap = idNameBiMap.inverse();
    System.out.println(nameIdBiMap);
    assertEquals(4, nameIdBiMap.get("Jane").intValue());
}

Guava提供了多种BiMap的实现

键–值实现 值–键实现
BiMap实现
HashMap HashMap HashBiMap
ImmutableMap ImmutableMap ImmutableBiMap
EnumMap EnumMap EnumBiMap
EnumMap HashMap EnumHashBiMap

测试代码:码云 – 卓立 – Guava测试代码

  1. Google Guava Docs
  2. Google Guava User Guide

赞(0) 打赏
Zhuoli's Blog » Guava新集合类型
分享到: 更多 (0)

评论 抢沙发

  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址