Guide to Guava Multiset – Guava Multiset指南

最后修改: 2019年 4月 18日

中文/混合/英文(键盘快捷键:t)

1. Overview

1.概述

In this tutorial, we’ll explore one of the Guava collections – Multiset. Like a java.util.Set, it allows for efficient storage and retrieval of items without a guaranteed order.

在本教程中,我们将探讨Guava集合之一 – Multiset。像java.util.Set一样,它允许有效地存储和检索没有保证顺序的项目。

However, unlike a Set, it allows for multiple occurrences of the same element by tracking the count of each unique element it contains.

然而,与Set不同的是,它允许同一元素的多次出现通过跟踪其包含的每个独特元素的计数。

2. Maven Dependency

2.Maven的依赖性

First, let’s add the guava dependency:

首先,让我们添加guava依赖

<dependency>
    <groupId>com.google.guava</groupId>
    <artifactId>guava</artifactId>
    <version>31.0.1-jre</version>
</dependency>

3. Using Multiset

3.使用Multiset

Let’s consider a bookstore which has multiple copies of different books. We might want to perform operations like adding a copy, getting the number of copies, and removing one copy when it’s sold. As a Set does not allow for multiple occurrences of the same element, it can’t handle this requirement.

让我们考虑一个书店,它有不同书籍的多个副本。我们可能想执行一些操作,比如添加一个副本,获得副本的数量,以及在卖掉一个副本时删除它。由于Set不允许同一元素的多次出现,它不能处理这个要求。

Let’s get started by adding copies of a book title. The Multiset should return that the title exists and provide us with the correct count:

让我们开始添加一个书名的副本。Multiset应该返回该书名的存在,并为我们提供正确的计数

Multiset<String> bookStore = HashMultiset.create();
bookStore.add("Potter");
bookStore.add("Potter");
bookStore.add("Potter");

assertThat(bookStore.contains("Potter")).isTrue();
assertThat(bookStore.count("Potter")).isEqualTo(3);

Now let’s remove one copy. We expect the count to be updated accordingly:

现在让我们删除一个副本。我们希望计数能得到相应的更新。

bookStore.remove("Potter");
assertThat(bookStore.contains("Potter")).isTrue();
assertThat(bookStore.count("Potter")).isEqualTo(2);

And actually, we can just set the count instead of performing various add operations:

而实际上,我们可以直接设置计数,而不是进行各种添加操作。

bookStore.setCount("Potter", 50); 
assertThat(bookStore.count("Potter")).isEqualTo(50);

Multiset validates the count value. If we set it to negative, an IllegalArgumentException is thrown:

Multiset验证了count值。如果我们把它设置为负值,就会抛出一个IllegalArgumentException

assertThatThrownBy(() -> bookStore.setCount("Potter", -1))
  .isInstanceOf(IllegalArgumentException.class);

4. Comparison with Map

4.与地图的比较

Without access to Multiset, we could achieve all of the operations above by implementing our own logic using java.util.Map:

如果不能访问Multiset,我们可以通过使用java.util.Map:实现我们自己的逻辑来实现上述所有操作。

Map<String, Integer> bookStore = new HashMap<>();
// adding 3 copies
bookStore.put("Potter", 3);

assertThat(bookStore.containsKey("Potter")).isTrue();
assertThat(bookStore.get("Potter")).isEqualTo(3);

// removing 1 copy
bookStore.put("Potter", 2);
assertThat(bookStore.get("Potter")).isEqualTo(2);

When we want to add or remove a copy using a Map, we need to remember the current count and adjust it accordingly. We also need to implement this logic in our calling code every time or construct our own library for this purpose. Our code would also need to control the value argument. If we’re not careful, we could easily set the value to null or negative even though both the values are invalid:

当我们想使用Map添加或删除一个副本时,我们需要记住当前的计数并进行相应的调整。我们还需要每次在我们的调用代码中实现这一逻辑,或者为此目的构建我们自己的库。我们的代码还需要控制value参数。如果我们不小心,我们可以很容易地将值设置为null或负值,尽管这两个值都是无效的。

bookStore.put("Potter", null);
assertThat(bookStore.containsKey("Potter")).isTrue();

bookStore.put("Potter", -1);
assertThat(bookStore.containsKey("Potter")).isTrue();

As we can see, it is a lot more convenient to use Multiset instead of Map.

我们可以看到,使用Multiset而不是Map要方便得多。

5. Concurrency

5.并发

When we want to use Multiset in a concurrent environment, we can use ConcurrentHashMultiset, which is a thread-safe Multiset implementation.

当我们想在并发环境中使用Multiset时,我们可以使用ConcurrentHashMultiset,它是一个线程安全的Multiset实现。

We should note that being thread-safe does not guarantee consistency, though. Using the add or remove methods will work well in a multi-threaded environment, but what if several threads called the setCount method? 

我们应该注意到,虽然是线程安全的,但并不能保证一致性。在多线程环境中,使用addremove方法可以很好地工作,但是如果有几个线程调用setCount方法呢?

If we use the setCount method, the final result would depend on the order of execution across threads, which cannot necessarily be predicted. The add and remove methods are incremental, and the ConcurrentHashMultiset is able to protect their behavior. Setting the count directly is not incremental and so can cause unexpected results when used concurrently.

如果我们使用setCount方法,最终结果将取决于跨线程的执行顺序,这不一定能预测到。addremove方法是增量的,ConcurrentHashMultiset能够保护其行为。直接设置计数不是递增的,因此在并发使用时可能会导致意外的结果。

However, there’s another flavor of the setCount method which updates the count only if its current value matches the passed argument. The method returns true if the operation succeeded, a form of optimistic locking:

然而,还有一种 setCount方法,它只在计数的当前值与传递的参数相匹配时才更新计数。如果操作成功,该方法返回true,这是一种乐观的锁定形式。

Multiset<String> bookStore = HashMultiset.create();
// updates the count to 2 if current count is 0
assertThat(bookStore.setCount("Potter", 0, 2)).isTrue();
// updates the count to 5 if the current value is 50
assertThat(bookStore.setCount("Potter", 50, 5)).isFalse();

If we want to use the setCount method in concurrent code, we should use the above version to guarantee consistency. A multi-threaded client could perform a retry if changing the count failed.

如果我们想在并发代码中使用setCount方法,我们应该使用上述版本以保证一致性。如果改变计数失败,一个多线程的客户端可以执行重试。

6. Conclusion

6.结语

In this short tutorial, we discussed when and how to use a Multiset, compared it with a standard Map and looked at how best to use it in a concurrent application.

在这个简短的教程中,我们讨论了何时以及如何使用Multiset,与标准Map进行了比较,并探讨了如何在一个并发的应用程序中最好地使用它。

As always, the source code for the examples can be found over on GitHub.

一如既往,这些例子的源代码可以在GitHub上找到