Guava系列之新的集合类型

x33g5p2x  于2021-12-18 转载在 其他  
字(6.5k)|赞(0)|评价(0)|浏览(413)

Guava中新增加了一些集合类型,是对JDK集合类型的补充,使开发者更方便使用集合类型来实现业务功能

Multiset

首先我们来看一下日常研发中常用到的一个场景,统计一个list中每个元素出现的次数,我们大概会这样写:

List<String> list = new ArrayList<String>();
list.add("a");
list.add("b");
list.add("c");
list.add("d");
list.add("b");
list.add("b");
Map<String,Integer> countMap = new HashMap<String,Integer>();
for(String str : list){
    if(countMap.get(str) != null){
        countMap.put(str,countMap.get(str) + 1);
    } else {
        countMap.put(str,1);
    }
}
  • 首先定义一个list,然后往里面新增若干个元素
  • 定义一个Map,遍历list将每个元素出现的次数记录在map里面

显然这样的写法,看起来还是挺笨拙的

Guava提供了Multiset,虽然名字带有Set,但它可以添加重复的元素

Multiset可以看成是ArrayList和Map的结合体

  • Multiset是没有元素顺序限制的ArrayList
  • Multiset提供了键为元素,值为计数的Map

下面我们来看一下Multiset的实际用法

//通过create()方法创建
        Multiset<String> multiset = HashMultiset.create();
        //可直接添加元素
        multiset.add("a");
        multiset.add("b");
        multiset.add("c");
        multiset.add("c");
        multiset.add("c");
        List<String> list = new ArrayList<String>();
        list.add("xx");
        list.add("yy");
        list.add("zz");
        //也可用addAll方法添加集合进来
        multiset.addAll(list);

        //获取元素"c"的计数
        System.out.println(multiset.count("c"));

        //返回去重后的元素set集合
        Set<String> set = multiset.elementSet();

        //multiset所有元素的个数
        System.out.println("multiset.size():" + multiset.size());
        //multiset去重后的元素个数
        System.out.println("elementSet().size():" + multiset.elementSet().size());

        //元素迭代
       Iterator<String> it = multiset.iterator();
        while(it.hasNext()){
            System.out.println(it.next());
        }

        //可以通过设置元素的计数,来批量的添加元素,当然能加也能减
        multiset.setCount("c",5);

        //将元素的计数设为0,就相当于移除所有的"c"元素
        multiset.setCount("c",0);

        //移除一个元素
        multiset.remove("c");

        //移除两个"c"元素
        multiset.remove("c",2);

可以看出来,Multiset包含了基本的集合操作外,还有一些特有的操作,比如setCount()、按个数remove()

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

Map对应的Multiset是否支持null元素
HashMapHashMultiset
TreeMapTreeMultiset
LinkedHashMapLinkedHashMultiset
ConcurrentHashMapConcurrentHashMultiset
ImmutableMapImmutableMultiset
SortedMultiset

SortedMultiset支持高效的获取指定范围的子集

SortedMultiset<String> sortedMultiset = TreeMultiset.create();
sortedMultiset.setCount("c",5);
sortedMultiset.setCount("a",3);
sortedMultiset.setCount("b",2);
//获取第一个元素
sortedMultiset.firstEntry().getElement();
//获了最后一个元素
sortedMultiset.lastEntry().getElement();
//获取子集
SortedMultiset<String> subMultiset = sortedMultiset.subMultiset("a", BoundType.OPEN,"b",BoundType.CLOSED);

需要注意,SortedMultiset默认是排好序的,是按元素来进行排序的而不是元素的个数

Multimap

在JDK中Map是存储Key-value型数据结构的,并且Key相同时会被覆盖掉,比如

Map<String,String> map = new HashMap<String,String>();
map.put("key","value1");
map.put("key","value2");

最终map里只会存key->value2的值

在很多时候,我们需要一个key对应多个值,在JDK中只能以类似于Map<K,List>这种形式来表达,操作起来很不方便

Guava为我们提供了Multimap,可以用来做一个Key映射多个值的操作

Multimap multimap = ArrayListMultimap.create();
//新增元素,直接put
multimap.put("a","123");
multimap.put("a","111");
multimap.put("b","456");
multimap.put("d","789");

Multimap multimap1 = LinkedListMultimap.create();
multimap1.put("a","a1_value");
multimap1.put("k2","k2_value");
//使用putAll方法可以添加一个multimap,这个跟JDK中的putAll一样,而且key相同时会进行合并
multimap.putAll(multimap1);

List<String> list = Lists.newArrayList("a_value1","a_value2","a_value3");
//还可以指定key进行批量添加元素,注意此处是追加到key中,不是替换
multimap.putAll("a",list);

//multimap中的所有键值对,重复的算多个
System.out.println(multimap.size());
//key的个数
System.out.println(multimap.keySet().size());

//移除指定key的指定value
multimap.remove("a","111");
System.out.println(multimap);
//移除整个key的所有value
multimap.removeAll("a");
System.out.println(multimap);

//替换指定key的value
multimap.replaceValues("b",Lists.newArrayList("b1_value","b2_value"));

//是否包含指定的key
System.out.println(multimap.containsKey("d"));
//是否包含指定的键值对
System.out.println(multimap.containsEntry("d","789"));
//获取multimap中所有的value
System.out.println(multimap.values());
//返回Multiset
System.out.println(multimap.keys());
//返回Map类型
Map<String,List<String>> map = multimap.asMap();

//清空整个集合
multimap.clear();

System.out.println(multimap);

Multimap的各种实现

实现键行为类似值行为类似
ArrayListMultimapHashMapArrayList
HashMultimapHashMapHashSet
LinkedListMultimapLinkedHashMapLinkedList
LinkedHashMultimapLinkedHashMapLinkedHashMap
TreeMultimapTreeMapTreeSet
ImmutableListMultimapImmutableMapImmutableList
ImmutableSetMultimapImmutableMapImmutableSet
BiMap

在JDK中,如果我们要维护一个双向Map,需要定义两个Map来存储,并且两个要同时进行变更

Map<String,String> map1 = Maps.newHashMap();
Map<String,String> map2 = Maps.newHashMap();
map1.put("Hello","Kai");
map2.put("Kai","Hello");

Guava提供了BiMap,它是一种特殊的Map,可以实现键值的反转

public static void main(String[] args){
        BiMap biMap = HashBiMap.create();
        biMap.put("a","123");
        System.out.println(biMap);
        //对键值对进行反转
        System.out.println(biMap.inverse());

        //试图将一个key映射到已经存在的值上,会抛异常
        biMap.put("b","123");

        //强值将一个key映射到已经存在的值上,会将原来的key覆盖掉
        biMap.forcePut("b","123");
        System.out.println(biMap);

    }

可以看出,因为BiMap要支持反转,所以它的key和value都必须是唯一的,要不然反转过来就存在一对多的情况

BiMap的各种实现

键–值实现值–键实现对应的BiMap实现
HashMapHashMapHashBiMap
ImmutableMapImmutableMapImmutableBiMap
EnumMapEnumMapEnumBiMap
EnumMapEnumMapEnumHashBiMap
Table

在JDK中当需要做key映射到Key-value对时,你需要这样写Map<K,Map<K,V>>,这种写法同样不够友好,同时也不便维护

这实际上就是一个表格的行、列、值的结构,Guava里面提供了表格来解决这种场景

//创建row,column,value结构的table
Table<String,String,Integer> table = HashBasedTable.create();
table.put("a1","c1",23);
table.put("a1","c2",77);
table.put("a2","c2",44);
//通过rowKey获取columnKey->value的映射关系
System.out.println(table.row("a1"));
//通过columnKey获取rowKey ->value的映射关系
System.out.println(table.column("c2"));

Table有如下几种实现:

  • HashBasedTable:本质上用HashMap<R, HashMap<C, V>>实现;
  • TreeBasedTable:本质上用TreeMap<R, TreeMap<C,V>>实现;
  • ImmutableTable:本质上用ImmutableMap<R, ImmutableMap<C, V>>实现;注:ImmutableTable对稀疏或密集的数据集都有优化。
  • ArrayTable:要求在构造时就指定行和列的大小,本质上由一个二维数组实现,以提升访问速度和密集Table的内存利用率。ArrayTable与其他Table的工作原理有点不同,请参见Javadoc了解详情。
ClassToInstanceMap

ClassToInstanceMap是一种特殊的Map:它的键是类型,而值是符合键所指类型的对象

ClassToInstanceMap<Number> numberDefaults= MutableClassToInstanceMap.create();
numberDefaults.putInstance(Integer.class,6);

还有RangeSet、RangeMap平时用的比较少,大家有需要可以去Guava官网上了解一下
https://github.com/google/guava/wiki/NewCollectionTypesExplained

相关文章