Java

Guava常用数据结构

2020-11-10  本文已影响0人  王勇1024

Guava是一种基于开源的Java库,其中包含谷歌正在由他们很多项目使用的很多核心库。这个库是为了方便编码,并减少编码错误。这个库提供用于集合,缓存,支持原语,并发性,常见注解,字符串处理,I/O和验证的实用方法。

Guava 的好处:

今天我们就一起来学习一下Guava吧。

不可变集合

范例

public static final ImmutableSet<String> COLOR_NAMES = ImmutableSet.of(
    "red",
    "orange",
    "yellow",
    "green",
    "blue",
    "purple");

class Foo {
    Set<Bar> bars;
    Foo(Set<Bar> bars) {
        this.bars = ImmutableSet.copyOf(bars); // defensive copy!
    }
}

为什么要使用不可变集合
不可变对象有很多优点,包括:

创建对象的不可变拷贝是一项很好的防御性编程技巧。Guava 为所有 JDK 标准集合类型和 Guava 新集合类型都提供了简单易用的不可变版本。
JDK 也提供了 Collections.unmodifiableXXX 方法把集合包装为不可变形式,但我们认为不够好:

如果你没有修改某个集合的需求,或者希望某个集合保持不变时,把它防御性地拷贝到不可变集合是个很好的实践。

重要提示:所有 Guava 不可变集合的实现都不接受 null 值。我们对 Google 内部的代码库做过详细研究,发现只有 5%的情况需要在集合中允许 null 元素,剩下的 95%场景都是遇到 null 值就快速失败。如果你需要在不可变集合中使用 null,请使用 JDK 中的Collections.unmodifiableXXX 方法。更多细节建议请参考“使用和避免null”。

怎么使用不可变集合

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

public static final ImmutableSet<Color> GOOGLE_COLORS =
ImmutableSet.<Color>builder()
.addAll(WEBSAFE_COLORS)
.add(new Color(0, 191, 255))
.build();

此外,对有序不可变集合来说,排序是在构造集合的时候完成的,如:

ImmutableSortedSet.of("a", "b", "c", "a", "d", "b");

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

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

新集合类型

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

Multiset

Guava 提供了一个新集合类型 Multiset,它可以多次添加相等的元素。Multiset继承自 JDK 中的 Collection 接口,而不是 Set 接口,所以包含重复元素并没有违反原有的接口契约。
可以用两种方式看待 Multiset:

Guava 的 Multiset API 也结合考虑了这两种方式:
当把 Multiset 看成普通的 Collection 时,它表现得就像无序的 ArrayList:

特别注意,Multi set.addAll(Collection)可以添加 Collection 中的所有元素并进行计数,这比用 for 循环往 Map 添加元素和计数 方便多了。

方法 描述
count(E) 给定元素在 Multiset 中的计数
elementSet() Multiset 中不重复元素的集合,类型为 Set<E>
entrySet() 和 Map 的 entrySet 类似,返回 Set<Multiset.Entry<E>>,其中包含的 Entry 支持 getElement()和 getCount()方法
add(E, int) 增加给定元素在 Multiset 中的计数
remove(E, int) 减少给定元素在 Multiset 中的计数
setCount(E, int) 设置给定元素在 Multiset 中的计数,不可以为负数
size() 返回集合元素的总个数(包括重复的元素)

Multiset 不是 Map

请注意,Multiset不是 Map<E, Integer>,虽然 Map 可能是某些 Multiset 实现的一部分。准确来说 Multiset 是一种 Collection 类型,并履行了 Collection 接口相关的契约。关于 Multiset 和 Map 的显著区别还包括:

Multiset 的各种实现

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

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

Multimap

每个有经验的 Java 程序员都在某处实现过 Map<K, List>或 Map<K, Set>,并且要忍受这个结构的笨拙。例如,Map<K, Set>通常用来表示非标定有向图。Guava 的 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 接口,更多时候你会用 ListMultimapSetMultimap接口,它们分别把键映射到 List 或 Set。

修改 Multimap

Multimap.get(key)以集合形式返回键所对应的值视图,即使没有任何对应的值,也会返回空集合。ListMultimap.get(key)返回 List,SetMultimap.get(key)返回 Set。
对值视图集合进行的修改最终都会反映到底层的 Multimap。例如:

Set<Person> aliceChildren = childrenMultimap.get(alice);
aliceChildren.clear();
aliceChildren.add(bob);
aliceChildren.add(carol);

其他(更直接地)修改 Multimap 的方法有:

方法签名 描述 等价于
put(K, V) 添加键到单个值的映射 multimap.get(key).add(value)
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)

Multimap 的视图

Multimap 还支持若干强大的视图:

Multimap 的各种实现

Multimap 提供了多种形式的实现。在大多数要使用 Map<K, Collection>的地方,你都可以使用它们:

实现 键行为类似 值行为类似
ArrayListMultimap HashMap ArrayList
HashMultimap HashMap HashSet
LinkedListMultimap* LinkedHashMap* LinkedList*
LinkedHashMultimap** LinkedHashMap LinkedHashMap
TreeMultimap TreeMap TreeSet
ImmutableListMultimap ImmutableMap ImmutableList
ImmutableSetMultimap ImmutableMap ImmutableSet

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

BiMap

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

Map<String, Integer> nameToId = Maps.newHashMap();
Map<Integer, String> idToName = Maps.newHashMap();
nameToId.put("Bob", 42);
idToName.put(42, "Bob");
//如果"Bob"和42已经在map中了,会发生什么?
//如果我们忘了同步两个map,会有诡异的bug发生...

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

在 BiMap 中,如果你想把键映射到已经存在的值,会抛出 IllegalArgumentException 异常。如果对特定值,你想要强制替换它的键,请使用 BiMap.forcePut(key, value)

BiMap<String, Integer> userId = HashBiMap.create();
...
String userForId = userId.inverse().get(id);

BiMap 的各种实现

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

注:Maps 类中还有一些诸如 synchronizedBiMap 的 BiMap 工具方法.

Table

Table<Vertex, Vertex, Double> weightedGraph = HashBasedTable.create();
weightedGraph.put(v1, v2, 4);
weightedGraph.put(v1, v3, 20);
weightedGraph.put(v2, v3, 5);
weightedGraph.row(v1); // returns a Map mapping v2 to 4, v3 to 20
weightedGraph.column(v3); // returns a Map mapping v1 to 20, v2 to 5

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

上一篇 下一篇

猜你喜欢

热点阅读