拥抱Guava之集合操作

深入Guava集合操作

在Java开发中,Google Guava库是处理集合的强大工具。起源于Google内部需求,Guava以简洁性、性能优化为理念,提供高效不可变集合和实用工具类。本文深入剖析Guava的核心功能,为开发者呈现集合操作的全新视角,无论经验水平,都能获得实用技巧和深刻见解。

一、不可变集合

1、为什么使用不可变集合

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

  • 当对象被不可信的库调用时,不可变形式是安全的;
  • 不可变对象被多个线程调用时,不存在竞态条件问题
  • 可变集合不需要考虑变化,因此可以节省时间和空间。所有不可变的集合都比它们的可变形式有更好的内存利用率(分析和测试细节);
  • 不可变对象因为有固定不变,可以作为常量来安全使用。
2、创建不可变集合的方式:
  • copyOf方法,如ImmutableSet.copyOf(set);
  • of方法,如ImmutableSet.of(“a”, “b”, “c”)或 ImmutableMap.of(“a”, 1, “b”, 2);
  • Builder工具,如:
private static final ImmutableSet<String> SET = 
            ImmutableSet.<String>builder()
                    .add("a","b")
                    .addAll(Lists.newArrayList("c","d"))
                    .build();

此外,对有序不可变集合来说,排序是在构造集合的时候完成的,如: ImmutableSortedSet.of("a", "b", "c", "a", "d", "b");

会在构造时就把元素排序为a, b, c, d。

3、asList视图

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

asList()返回的ImmutableList通常是——并不总是——开销稳定的视图实现,而不是简单地把元素拷贝进List。也就是说,asList返回的列表视图通常比一般的列表平均性能更好,比如,在底层集合支持的情况下,它总是使用高效的contains方法。

二、关联可变集合和不可变集合

可变集合接口 属于JDK还是Guava 不可变版本
Collection JDK ImmutableCollection
List JDK ImmutableList
Set JDK ImmutableSet
SortedSet/NavigableSet JDK ImmutableSortedSet
Map JDK ImmutableMap
SortedMap JDK ImmutableSortedMap
Multiset Guava ImmutableMultiset
SortedMultiset Guava ImmutableSortedMultiset
Multimap Guava ImmutableMultimap
ListMultimap Guava ImmutableListMultimap
SetMultimap Guava ImmutableSetMultimap
BiMap Guava ImmutableBiMap
ClassToInstanceMap Guava ImmutableClassToInstanceMap
Table Guava ImmutableTable

三、新集合类型

1、Multiset

Multiset可以多次添加相等元素,集合[set]概念的延伸,它的元素可以重复出现…与集合[set]相同而与元组[tuple]相反的是,Multiset元素的顺序是无关紧要的:Multiset {a, a, b}和{a, b, a}是相等的

可以用两种方式看待Multiset:

  • 没有元素顺序限制的ArrayList
  • Map,键为元素,值为计数
(1)、常见方法
方法 描述
int count(E) 给定元素在Multiset中的计数
Set elementSet() Multiset中不重复元素的集合,类型为Set
Set> entrySet() 和Map的entrySet类似,返回Set>,其中包含的Entry支持getElement()和getCount()方法
int add(E, int) 增加给定元素在Multiset中的计数
boolean add(E element) 增加一个指定的元素到multiset
boolean contains(E element) 判断此多集中是否包含指定的元素
boolean containsAll(Collection elements) 判断此多集至少包含一个出现指定集合的所有元素
remove(E, int) 减少给定元素在Multiset中的计数,删除指定元素
removeAll(Collection c) 删除包含在指定集合中的元素
boolean retainAll(Collection e) 保持包含指定集合中的元素
int setCount(E, int) 设置给定元素在Multiset中的计数,不可以为负数,添加/删除指定元素,使其达到所期望的元素个数
int size() 返回集合元素的总个数(包括重复的元素)
Iterator iterator() 返回一个迭代器,包含Multiset的所有元素(包括重复的元素)
(2)、示例
    /**
     * MultiSet
     */
    @Test
    public void multiSetTest(){
        Multiset<String> multiset = HashMultiset.create();
        List<String> list = Lists.newArrayList("a","b","c","d","a","c","d","a","d","a");
        multiset.addAll(list);

        System.out.println("a的个数:"+multiset.count("a"));
        System.out.println("multiset的个数:"+multiset.size());

        Set<String> set = multiset.elementSet();
        System.out.println("不重复元素:"+ Joiner.on(",").join(set));

        Iterator<String> iterator = multiset.iterator();
        System.out.println("multiset元素:"+Joiner.on(",").join(iterator));

        Set<Multiset.Entry<String>> entrySet =  multiset.entrySet();
        Map<String,Integer> setMap = Maps.newHashMap();
        entrySet.forEach(e -> {
            setMap.put(e.getElement(),e.getCount());
        });
        System.out.println("元素详情:"+Joiner.on(";").withKeyValueSeparator("=").join(setMap));

        multiset.remove("a",2);
        System.out.println("删除a后,a的个数:"+multiset.count("a"));

        System.out.println("是否包含List:"+multiset.containsAll(Lists.newArrayList("a","c")));
        System.out.println("是否包含List:"+multiset.containsAll(Lists.newArrayList("a","c","e")));
    }
(3)、SortedMultiset

SortedMultiset是Multiset 接口的变种,它支持高效地获取指定范围的子集

2、MultiMap

Multimap可以很容易地把一个键映射到多个值。换句话说,Multimap是把键映射到任意多个值的一般方式。 可以用两种方式思考Multimap的概念:”键-单个值映射”的集合:
a -> 1 a -> 2 a ->4 b -> 3 c -> 5
或者”键-值集合映射”的映射:
a -> [1, 2, 4] b -> 3 c -> 5
一般来说,Multimap接口应该用第一种方式看待,但asMap()视图返回Map,让你可以按另一种方式看待Multimap。重要的是,不会有任何键映射到空集合:一个键要么至少到一个值,要么根本就不在Multimap中。 很少会直接使用Multimap接口,更多时候你会用ListMultimap或SetMultimap接口,它们分别把键映射到List或Set。

(1)、常用方法
方法 描述 等价于
boolean put(K, V) 添加键到单个值的映射 multimap.get(key).add(value)
boolean putAll(K, Iterable) 依次添加键到多个值的映射 Iterables.addAll(multimap.get(key), values)
remove(K, V) 移除键到值的映射;如果有这样的键值并成功移除,返回true。 multimap.get(key).remove(value)
removeAll(K) 清除键对应的所有值,返回的集合包含所有之前映射到K的值,但修改这个集合就不会影响Multimap了。 multimap.get(key).clear()
replaceValues(K, Iterable) 清除键对应的所有值,并重新把key关联到Iterable中的每个元素。返回的集合包含所有之前映射到K的值。 multimap.get(key).clear(); Iterables.addAll(multimap.get(key), values)
Map> asMap() 获取MultiMap的视图,键值K,以及K对应的集合
void clear() 清除所有的键值对
boolean containsEntry(Object key,Object value) 判断是否包含key-value对应的键值对
boolean containsKey(Object key) 判断是否包含键值key
boolean containsValue(Object value) 判断是否包含值value
Collection> entries() MultiMap为Map情况下,返回所有的键值对集合
Collection get(K k) 返回键k对应的所有集合
boolean isEmpty() 判断MultiMap是否是空,即不包含键值对
MultiSet keys() 返回所有的键值K,包含重复
Set keySet() 返回所有的键值K,不重复
int size() 返回键值对的数量
Collection values 返回所有的value
(2)、示例
    /**
     * MultiMap
     */
    @Test
    public void multiMapTest(){
        Multimap<String,String> multimap = HashMultimap.create();
        multimap.putAll("lower",Lists.newArrayList("a","b","c","d"));
        multimap.putAll("upper",Lists.newArrayList("A","B","C","D"));

        Map<String, Collection<String>> asMap = multimap.asMap();
        System.out.println("asMap视图:"+Joiner.on(";").withKeyValueSeparator("=").join(asMap));

        Multiset<String> multisetKey = multimap.keys();
        System.out.println("所有的key:"+Joiner.on(",").join(multisetKey.iterator()));

        Set<String> keySet = multimap.keySet();
        System.out.println("不重复的key:"+Joiner.on(",").join(keySet));

        System.out.println("lower:"+Joiner.on(",").join(multimap.get("lower")));

        multimap.put("lower","e");
        System.out.println("添加后的lower:"+Joiner.on(",").join(multimap.get("lower")));

        System.out.println("upper:"+Joiner.on(",").join(multimap.get("upper")));
        multimap.remove("upper","D");
        System.out.println("移除元素后的upper:"+Joiner.on(",").join(multimap.get("upper")));

        System.out.println("是否包含lower-b:"+multimap.containsEntry("lower","b"));
        System.out.println("是否包含lower-b:"+multimap.containsEntry("lower","f"));

        System.out.println("是否包含key(upper):"+multimap.containsKey("upper"));
        System.out.println("是否包含value(c):"+multimap.containsValue("c"));

        Collection<Map.Entry<String,String>> collection = multimap.entries();
        System.out.println("MultiMap详情:"+Joiner.on(";").withKeyValueSeparator("=").join(collection));

        Collection<String> values = multimap.values();
        System.out.println("MultiMap所有的value:"+Joiner.on(",").join(values));
    }
(3)、Multimap不是Map

Multimap不是Map,虽然某些Multimap实现中可能使用了map。它们之间的显著区别包括:

  • Multimap.get(key)总是返回非null、但是可能空的集合。这并不意味着Multimap为相应的键花费内存创建了集合,而只是提供一个集合视图方便你为键增加映射值——译者注:如果有这样的键,返回的集合只是包装了Multimap中已有的集合;如果没有这样的键,返回的空集合也只是持有Multimap引用的栈对象,让你可以用来操作底层的Multimap。因此,返回的集合不会占据太多内存,数据实际上还是存放在Multimap中。

  • 如果你更喜欢像Map那样,为Multimap中没有的键返回null,请使用asMap()视图获取一个Map>。(或者用静态方法Multimaps.asMap()为ListMultimap返回一个Map>。对于SetMultimap和SortedSetMultimap,也有类似的静态方法存在)

  • 当且仅当有值映射到键时,Multimap.containsKey(key)才会返回true。尤其需要注意的是,如果键k之前映射过一个或多个值,但它们都被移除后,Multimap.containsKey(key)会返回false。

  • Multimap.entries()返回Multimap中所有”键-单个值映射”——包括重复键。如果你想要得到所有”键-值集合映射”,请使用asMap().entrySet()。

  • Multimap.size()返回所有”键-单个值映射”的个数,而非不同键的个数。要得到不同键的个数,请改用Multimap.keySet().size()。

(4)、Multimap的各种实现
实现 键行为类似 值行为类似
ArrayListMultimap HashMap ArrayList
HashMultimap HashMap HashSet
LinkedListMultimap LinkedHashMap LinkedList
LinkedHashMultimap LinkedHashMap LinkedHashMap
TreeMultimap TreeMap TreeSet
ImmutableListMultimap ImmutableMap ImmutableList
ImmutableSetMultimap ImmutableMap ImmutableSet

除了两个不可变形式的实现,其他所有实现都支持null键和null值

  • LinkedListMultimap.entries()保留了所有键和值的迭代顺序。详情见doc链接。

  • LinkedHashMultimap保留了映射项的插入顺序,包括键插入的顺序,以及键映射的所有值的插入顺序。 请注意,并非所有的Multimap都和上面列出的一样,使用Map>来实现(特别是,一些Multimap实现用了自定义的hashTable,以最小化开销)

3、BiMap

BiMap是特殊的Map:

  • 可以用 inverse()反转BiMap的键值映射
  • 保证值是唯一的,因此 values()返回Set而不是普通的Collection

在BiMap中,如果你想把键映射到已经存在的值,会抛出IllegalArgumentException异常。

(1)、常用方法
方法 描述
V forcePut(String key, V value) 对于特定的值,强制替换它的键
BiMap inverse() k-v键值对的转换,即v-k
V put 关联v到k
void putAll(Map map) 将map加入到BiMap
Set values() 返回BiMap映射中包含的Collection视图
(2)、BiMap的各种实现
键–值实现 值–键实现 对应的BiMap实现
HashMap HashMap HashBiMap
ImmutableMap ImmutableMap ImmutableBiMap
EnumMap EnumMap EnumBiMap
EnumMap HashMap EnumHashBiMap
(3)、示例
    /**
     * BiMap
     */
    @Test
    public void biMapTest(){
        BiMap<String, String> biMap = HashBiMap.create();
        biMap.putAll(ImmutableMap.of("a","1","b","2","c","3","d","4","e","5"));

        System.out.println("所有的值:"+Joiner.on(",").join(biMap.values()));
        System.out.println("转换后所有的值:"+Joiner.on(",").join(biMap.inverse().values()));

        String v = biMap.forcePut("a","10");
        System.out.println("替换的值:"+v);
        System.out.println("所有的值:"+Joiner.on(",").join(biMap.values()));
    }
4、Table

Table是Guava提供的一个接口 Interface Table,由rowKey+columnKey+value组成 它有两个键,一个值,和一个n行三列的数据表类似,n行取决于Table对对象中存储了多少个数据。

(1)、常用方法
方法 描述
Set> cellSet() 返回集合中的行键,列键,值三元组
void clear() 清除所有的键值对
Map column(C columnKey) 获取列键对应的键值对
Map row(R row) 获取行键对应的列以及值
Set columnKeySet() 获取所有的列键
Set rowKeySet() 获取行键
Map> columnMap 返回列键对应的行键-值的视图
boolean contains(Object rowKey,Object columnKey) 判断是否包含指定的行键,列键
boolean containsColumn(Object columnKey) 判断是否包含指定的列键
boolean containsRow(Object rowKey) 判断是否包含指定的行键
boolean containsValue(Object value) 判断是否包含值
V get(Object rowKey,Object columnKey) 返回指定的行键,列键对应的值,不存在则返回null
boolean isEmpty() 判断集合是否为空
V put(Object rowKey,Object columnKey,Object value) put值
void putAll(Table table) put指定的table
V remove(Object rowKey,Object columnKey) 如果有,则移除指定行键,列键
Map> rowMap() 获取每个行键对应的列键,值的视图
int size() 集合的个数(行键/列键/值)
Collection values() 集合值的集合,包括重复的
(2)、示例
    /**
     * Table
     */
    @Test
    public void tableTest(){
        Table<String,String,Integer> table = HashBasedTable.create();
        table.put("grade_1","class_1",100);
        table.put("grade_1","class_2",95);
        table.put("grade_1","class_3",80);
        table.put("grade_2","class_1",88);
        table.put("grade_2","class_2",95);
        table.put("grade_2","class_3",99);
        table.put("grade_2","class_3",100);

        Set<Table.Cell<String,String,Integer>> cellSet = table.cellSet();
        cellSet.forEach(cell -> {
            System.out.println("table中的行:"+cell.getRowKey()+";列:"+cell.getColumnKey()+";值:"+cell.getValue());
        });

        System.out.println("grade1对应的class:"+Joiner.on(";").withKeyValueSeparator("=").join(table.row("grade_1")));

        System.out.println("class1对应的grade:"+Joiner.on(";").withKeyValueSeparator("=").join(table.column("class_1")));

        System.out.println("所有的grade:"+Joiner.on(",").join(table.rowKeySet()));

        System.out.println("所有的class:"+Joiner.on(",").join(table.columnKeySet()));

        Map<String,Map<String,Integer>> rowMap = table.rowMap();
        rowMap.forEach((row,map) -> {
            System.out.println(row +"行对应的列值:"+Joiner.on(";").withKeyValueSeparator("=").join(map));
        });

        Map<String,Map<String,Integer>> columnMap = table.columnMap();
        columnMap.forEach((column,map) -> {
            System.out.println(column +"列对应的行值:"+Joiner.on(";").withKeyValueSeparator("=").join(map));
        });

        System.out.println("是否包含grade_1 和 class_2:"+table.contains("grade_1","class_2"));
        table.remove("grade_1","class_2");
        System.out.println("是否包含grade_1 和 class_2:"+table.contains("grade_1","class_2"));
    }
(3)、Table有如下几种实现:
  • HashBasedTable:本质上用HashMap>实现;

  • TreeBasedTable:本质上用TreeMap>实现;

  • ImmutableTable:本质上用ImmutableMap>实现;注:ImmutableTable对稀疏或密集的数据集都有优化。

  • ArrayTable:要求在构造时就指定行和列的大小,本质上由一个二维数组实现,以提升访问速度和密集Table的内存利用率。ArrayTable与其他Table的工作原理有点不同。

5、ClassToInstanceMap

ClassToInstanceMap是一种特殊的Map:它的键是类型,而值是符合键所指类型的对象。
为了扩展Map接口,ClassToInstanceMap额外声明了两个方法:T getInstance(Class T) 和T putInstance(Class , T),从而避免强制类型转换,同时保证了类型安全。

ClassToInstanceMap有唯一的泛型参数,通常称为B,代表Map支持的所有类型的上界。

对于ClassToInstanceMap,Guava提供了两种有用的实现:MutableClassToInstanceMap和 ImmutableClassToInstanceMap。

示例
    /**
     * ClassToInstanceMap
     */
    @Test
    public void classToInstanceMapTest(){
        ClassToInstanceMap<Number> instanceMap = MutableClassToInstanceMap.create();
        instanceMap.putInstance(Integer.class,123);
        instanceMap.putInstance(Long.class,456L);
        instanceMap.putInstance(Double.class,789.09);

        System.out.println("Integer:"+instanceMap.getInstance(Integer.class));
        System.out.println("Long:"+instanceMap.getInstance(Long.class));
        System.out.println("Double:"+instanceMap.getInstance(Double.class));
    }
6、RangSet

RangeSet描述了一组不相连的、非空的区间。当把一个区间添加到可变的RangeSet时,所有相连的区间会被合并,空区间会被忽略。

结论

通过深入探索Google Guava库的集合操作,我们不仅仅发现了一个功能强大的工具,更是领略到了一个高效、简洁的Java编程理念。Guava不仅提供了基础数据结构,还为开发者提供了一整套处理集合的利器,从不可变集合到高效工具类,无一不展现出其设计的巧妙之处。

在实际项目中,Guava为我们提供了更清晰、更简单的集合操作方式,帮助我们避免了许多常见的错误和异常。它的性能优化更是让我们在处理大规模数据时事半功倍。

作为Java开发者,我们应该充分了解并灵活运用Guava库,以提高代码的可读性、可维护性和性能。无论是新手还是老手,Guava都能为我们的开发工作带来便捷和效率。

本文已收录于我的个人博客:码农Academy的博客,专注分享Java技术干货,包括Java基础、Spring Boot、Spring Cloud、Mysql、Redis、Elasticsearch、中间件、架构设计、面试题、程序员攻略等

你可能感兴趣的:(java,guava,java,后端)