拥抱Guava之集合操作

发布时间:2024年01月10日

深入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不可变版本
CollectionJDKImmutableCollection
ListJDKImmutableList
SetJDKImmutableSet
SortedSet/NavigableSetJDKImmutableSortedSet
MapJDKImmutableMap
SortedMapJDKImmutableSortedMap
MultisetGuavaImmutableMultiset
SortedMultisetGuavaImmutableSortedMultiset
MultimapGuavaImmutableMultimap
ListMultimapGuavaImmutableListMultimap
SetMultimapGuavaImmutableSetMultimap
BiMapGuavaImmutableBiMap
ClassToInstanceMapGuavaImmutableClassToInstanceMap
TableGuavaImmutableTable

三、新集合类型

1、Multiset

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

可以用两种方式看待Multiset:

  • 没有元素顺序限制的ArrayList
  • Map<E, Integer>,键为元素,值为计数
(1)、常见方法
方法描述
int count(E)给定元素在Multiset中的计数
Set<E> elementSet()Multiset中不重复元素的集合,类型为Set<E>
Set<Multiset.Entry<E>> entrySet()和Map的entrySet类似,返回Set<Multiset.Entry<E>>,其中包含的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<K, Collection>,让你可以按另一种方式看待Multimap。重要的是,不会有任何键映射到空集合:一个键要么至少到一个值,要么根本就不在Multimap中。 很少会直接使用Multimap接口,更多时候你会用ListMultimap或SetMultimap接口,它们分别把键映射到List或Set。

(1)、常用方法
方法描述等价于
boolean put(K, V)添加键到单个值的映射multimap.get(key).add(value)
boolean putAll(K, Iterable<V>)依次添加键到多个值的映射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<V>)清除键对应的所有值,并重新把key关联到Iterable中的每个元素。返回的集合包含所有之前映射到K的值。multimap.get(key).clear(); Iterables.addAll(multimap.get(key), values)
Map<K,Collection<V>> 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<Map.Entry<K,V>> entries()MultiMap为Map<Entry>情况下,返回所有的键值对集合
Collection<V> get(K k)返回键k对应的所有集合
boolean isEmpty()判断MultiMap是否是空,即不包含键值对
MultiSet<K> keys()返回所有的键值K,包含重复
Set<K> keySet()返回所有的键值K,不重复
int size()返回键值对的数量
Collection<V> 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<K, V>不是Map<K,Collection>,虽然某些Multimap实现中可能使用了map。它们之间的显著区别包括:

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

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

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

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

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

(4)、Multimap的各种实现
实现键行为类似值行为类似
ArrayListMultimapHashMapArrayList
HashMultimapHashMapHashSet
LinkedListMultimapLinkedHashMapLinkedList
LinkedHashMultimapLinkedHashMapLinkedHashMap
TreeMultimapTreeMapTreeSet
ImmutableListMultimapImmutableMapImmutableList
ImmutableSetMultimapImmutableMapImmutableSet

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

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

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

3、BiMap

BiMap<K, V>是特殊的Map:

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

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

(1)、常用方法
方法描述
V forcePut(String key, V value)对于特定的值,强制替换它的键
BiMap<K,V> inverse()k-v键值对的转换,即v-k
V put<K key,V value>关联v到k
void putAll(Map<? extend k,? extend V> map)将map加入到BiMap
Set values()返回BiMap映射中包含的Collection视图
(2)、BiMap的各种实现
键–值实现值–键实现对应的BiMap实现
HashMapHashMapHashBiMap
ImmutableMapImmutableMapImmutableBiMap
EnumMapEnumMapEnumBiMap
EnumMapHashMapEnumHashBiMap
(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<R,C,V>,由rowKey+columnKey+value组成 它有两个键,一个值,和一个n行三列的数据表类似,n行取决于Table对对象中存储了多少个数据。

(1)、常用方法
方法描述
Set<Table.Cell<R,C,V>> cellSet()返回集合中的行键,列键,值三元组
void clear()清除所有的键值对
Map<R,V> column(C columnKey)获取列键对应的键值对
Map<C,V> row(R row)获取行键对应的列以及值
Set<C> columnKeySet()获取所有的列键
Set<R> rowKeySet()获取行键
Map<C,Map<R,V>> 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<? extend R,? extend C,? extend V> table)put指定的table
V remove(Object rowKey,Object columnKey)如果有,则移除指定行键,列键
Map<R,Map<C,V>> rowMap()获取每个行键对应的列键,值的视图
int size()集合的个数(行键/列键/值)
Collection<V> 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<R, HashMap<C, V>>实现;

  • TreeBasedTable:本质上用TreeMap<R, TreeMap<C,V>>实现;

  • ImmutableTable:本质上用ImmutableMap<R, ImmutableMap<C, V>>实现;注: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、中间件、架构设计、面试题、程序员攻略等

文章来源:https://blog.csdn.net/zhaojw_420/article/details/135510709
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。