玩转Guava(二)

集合操作

guava对现有jdk 中集合进行了扩展并进行了一系列优化使集合使用起来更加方便。

不可变集合

在开发中很多时候是需要使用不可变集合,不可变集合顾名思义就是说集合是不可被修改的。集合的数据项是在创建的时候提供,并且在整个生命周期中都不可改变。比如我们在购物场景中,用户选完商品最后生成购物清单,每个商品数量和种类不可变,我们要是保存在可变集合中,当某个环节运算不小心将商品数量增加和减少我们是完全不知情的,但是使用不可变集合保存时,当出现异常增加或减少数量,此时会直接报错。

在JDK中提供了Collections.unmodifiableXXX系列方法来实现不可变集合, 但是存在一些问题:

上面通过list创建不可变集合,最后通过向list里面添加数据,不可变集合也随着变化,无法达到真正的不可变集合。JDK提供的不可变集合有如下缺点:

  • 笨重而且累赘:不能舒适地用在所有想做防御性拷贝的场景;
  • 不安全:要保证没人通过原集合的引用进行修改,返回的集合才是事实上不可变的;
  • 低效:其数据结构本质还是集合,包装过的集合仍然保有可变集合的开销,比如并发修改的检查、散列表的额外空间,等等。

所以Guava重做了不可变集合。

不可变集合可以用如下多种方式创建:

  • copyOf方法,如ImmutableSet.copyOf(set);

  • of方法,如ImmutableSet.of(“1”, “2”, “3”)或```` ImmutableMap.of(“1”, 2, “3”, 4);```

  • Builder工具,如

    public static final ImmutableSet<Language> LANGUAGE_TYPE =
            ImmutableSet.<Language>builder()
                .addAll(ASIA_LANGUAGE)
                .add(new EuropeLanguage("French","EN"))
                .build();
    

通常情况下我们通过copyOf 方法创建不可变集合,该方法创建不可变集合时有如下特点:

  • 在常量时间内使用底层数据结构是可能的——例如,ImmutableSet.copyOf(ImmutableList)就不能在常量时间内完成。
  • 不会造成内存泄露——例如,你有个很大的不可变集合ImmutableList hugeList, ImmutableList.copyOf(hugeList.subList(0, 10))就会显式地拷贝,以免不必要地持有hugeList的引用。
  • 不改变语义——所以ImmutableSet.copyOf(myImmutableSortedSet)会显式地拷贝,因为和基于比较器的ImmutableSortedSet相比,ImmutableSet对hashCode()和equals有不同语义。

Guava 不可变集合通过提供asList方法来方便读取集合元素。

新集合类型

Guava引入了很多JDK没有的、但我们发现明显有用的新集合类型。这些新类型是为了和JDK集合框架共存,而没有往JDK集合抽象中硬塞其他概念。作为一般规则,Guava集合非常精准地遵循了JDK接口契约。

Multiset

当我们往ArrayList添加元素时,我们可以放入很多重复的元素,但是当我们需要统计重复元素出现的次数时,需要遍历集合并且统计每个每个元素数量,当我们使用Multiset替换ArrayList 时我们可以很轻松拿到重复元素个数。我们如何理解Multiset呢?我们可以理解为:

  • 没有元素顺序限制的ArrayList

  • Map<E, Integer>,键为元素,值为计数

当把Multiset看成普通的Collection时,我们可以将其用作ArrayList,支持所有api方法使用:

  • add(E)添加单个给定元素。
  • iterator()返回一个迭代器,包含Multiset的所有元素(包括重复的元素)。
  • size()返回所有元素的总个数(包括重复的元素)。

当我们当把Multiset看作Map<E, Integer>时,它也提供了符合性能期望的查询操作

  • count(Object)返回给定元素的计数。HashMultiset.count的复杂度为O(1),TreeMultiset.count的复杂度为O(log n)。
  • entrySet()返回Set<Multiset.Entry>,和Map的entrySet类似。
  • elementSet()返回所有不重复元素的Set,和Map的keySet()类似。
  • 所有Multiset实现的内存消耗随着不重复元素的个数线性增长。
BiMap

传统上,实现键值对的双向映射需要维护两个单独的map,并保持它们间的同步。但这种方式很容易出错,而且对于值已经在map中的情况,会变得非常混乱。但是BiMap 解决了这个问题,BiMap是特殊的Map:

  • 可以用 inverse()反转BiMap<K, V>的键值映射
  • 保证值是唯一的,因此 values()返回Set而不是普通的Collection
  • 在BiMap中,如果你想把键映射到已经存在的值,会抛出IllegalArgumentException异常。如果对特定值,你想要强制替换它的键,请使用 BiMap.forcePut(key, value)。
Table

通常来说,当你想使用多个键做索引的时候,你可能会用类似Map<FirstName, Map<LastName, Person>>的实现,这种方式很丑陋,使用上也不友好。Guava为此提供了新集合类型Table,它有两个支持所有类型的键:”行”和”列”。Table提供多种视图,以便你从各种角度使用它:

  1. rowMap():用Map<R, Map<C, V>>表现Table<R, C, V>。同样的, rowKeySet()返回”行”的集合Set
  2. row(r) :用Map<C, V>返回给定”行”的所有列,对这个map进行的写操作也将写入Table中。
  3. 类似的列访问方法:columnMap()、columnKeySet()、column(c)。(基于列的访问会比基于的行访问稍微低效点)
  4. cellSet():用元素类型为Table.Cell<R, C, V>的Set表现Table<R, C, V>。Cell类似于Map.Entry,但它是用行和列两个键区分的。

Table有如下几种实现:

  1. HashBasedTable:本质上用HashMap<R, HashMap<C, V>>实现;
  2. TreeBasedTable:本质上用TreeMap<R, TreeMap<C,V>>实现;
  3. ImmutableTable:本质上用ImmutableMap<R, ImmutableMap<C, V>>实现;注:ImmutableTable对稀疏或密集的数据集都有优化
  4. ArrayTable:要求在构造时就指定行和列的大小,本质上由一个二维数组实现,以提升访问速度和密集Table的内存利用率。

集合工具类

Lists类提供的常用方法:

Sets类提供的工具方法:

以上方法都是Guava对操作集合类工具方法封装,让开发者操作集合更加简便。

guava对Map提供的工具方法很多,使Map相关操作变得格外简单,以下是相关API:

很多工具方法通过名字就明白其使用场景。

posted @ 2023-02-07 10:47  年年糕  阅读(66)  评论(0编辑  收藏  举报