前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >使用Guava的Multimap实现多键值映射

使用Guava的Multimap实现多键值映射

原创
作者头像
编程思维
发布2024-05-31 16:52:55
790
发布2024-05-31 16:52:55
举报

简介

Guava是一组来自Google的核心Java库,在实际应用中非常广泛,熟练掌握guava可以让同学们在开发中如虎添翼,节省开发时间,提高工作效率。

Guava 的好处:

  • 标准化 - Guava 库是由谷歌托管。
  • 高效 - 可靠,快速和有效的扩展 JAVA 标准库
  • 优化 -Guava 库经过高度的优化。
  • 函数式编程 - 增加 JAVA 功能和处理能力。
  • 实用程序 - 提供了经常需要在应用程序开发的许多实用程序类。
  • 验证 - 提供标准的故障安全验证机制。
  • 最佳实践 - 强调最佳的做法。

本文将详细介绍与MultiSet相关的类,即guava中的MultiMap。

MultiMap

首先在maven工程中需要引入guava包,

代码语言:javascript
复制
<dependency>
    <groupId>com.google.guava</groupId>
    <artifactId>guava</artifactId>
    <version>31.0.1-jre</version>
</dependency>

MultiMap是一个映射,但它可以根据一个键映射多个值。当我们遇到具有多个值的场景时,我们必须编写大量代码来维护列表。

示例:我们在列表中有一个水果名称列表。我们想要构造一个指向水果名称列表的字符映射,以将字符映射到以该字符开头的水果列表。通常我们是这样做的:

代码语言:javascript
复制
List<String> fruits = Arrays.asList("apple", "banana", "orange", "avocado");
Map<Character, List<String>> map = new HashMap<>();
for (String fruit : fruits) {
    char firstChar = fruit.charAt(0);
    if (!map.containsKey(firstChar)) {
        map.put(firstChar, new ArrayList<>());
    }
    map.get(firstChar).add(fruit);
}
System.out.println(map); //{a=[apple, avocado], b=[banana], o=[orange]}

与上述方案相比,我们可以做得更好。我们可以使用 computeIfAbsent 方法来初始化每个新字符的空列表。

代码语言:javascript
复制
for (String fruit : fruits) {
    char firstChar = fruit.charAt(0);
    map.computeIfAbsent(firstChar, c -> new ArrayList<>())
            .add(fruit);
}

Google Guava提供的MultiMap能让我们通过简化且功能强大的API做到这一点,

MultiMap接口有两个类型参数 K 和 V。 该值并未显示的写入为集合。 当我们编写 Multimap<Integer, String> 时,值的类型是一个字符串集合。

本文将使用HashMultimap来演示 Multimap 的 API。当我们进入本文的最后一部分时,我们会看到Multimap的其他实现类。

MultiMap 添加数据

MultiMap接口提供了两种向MultiMap添加项目的方法 - put 和 putAll 方法。

代码语言:javascript
复制
SetMultimap<String, String> multimap = HashMultimap.create();
multimap.put("1", "a");
multimap.put("2", "b");
multimap.put("2", "c");
System.out.println(multimap);

输出:

代码语言:javascript
复制
{1=[a], 2=[b, c]}

键 1 具有值 a,键 2 具有值 b 和 c。我们可以通过传递可迭代的值来使用 putAll 为一个键添加多个值。

代码语言:javascript
复制
multimap.putAll("3", Arrays.asList("d", "e"));
System.out.println(multimap);
//{1=[a], 2=[b, c], 3=[d, e]}

MultiMap 遍历数据

我们可以使用 forEach 方法打印MultiMap。采用一个BiConsumer函数来接受键值和值。

代码语言:javascript
复制
multimap.forEach((k, v) -> System.out.println(k + " = " + v));

输出:

代码语言:javascript
复制
1 = a
2 = b
2 = c
3 = d
3 = e

查询方法

get 方法获取映射到键的值, 对应类型为 Set

代码语言:javascript
复制
System.out.println(multimap.get("1")); //[a]
System.out.println(multimap.get("2")); //[b, v]
System.out.println(multimap.get("22")); //[]

当查询不存在的键时,它将返回一个空集(而不是 null)。

如果multimap包含了值,则 containsValue 方法返回 true。换句话说,如果多映射包含至少一个具有此值的键值对,则返回 true。

代码语言:javascript
复制
System.out.println(multimap.containsValue("c")); //true
System.out.println(multimap.containsValue("f")); //false

如果multimap至少有一个具有传递的键和值的键值对,则 containsEntry 方法返回 true。

代码语言:javascript
复制
System.out.println(multimap.containsEntry("2", "c")); //true
System.out.println(multimap.containsEntry("2", "a")); //false

移除方法

为了演示删除方法,这里将从原始内容创建一个新的Multimap,并从中删除元素以防止原始数据发生变化。

remove 方法采用一个键和一个值,并删除单个键值对。如果有多个这样的键值对,它将删除其中一个(无法指定删除哪一个)。

removeAll 采用一个键,并删除映射到该键的所有值。除此之外,它还返回删除的值的列表。

代码语言:javascript
复制
SetMultimap copy = HashMultimap.create(multimap);
copy.remove("1", "a");
System.out.println(copy); //{2=[b, c], 3=[d, e]}
copy.removeAll("2");
System.out.println(copy); //{3=[d, e]}

替换值

此方法(replaceValues)允许我们替换键的值。它返回该键的旧/现有值。

代码语言:javascript
复制
System.out.println(multimap.replaceValues("3", Arrays.asList("d1", "e1"))); //[d, e]
System.out.println(multimap); //{1=[a], 2=[b, c], 3=[e1, d1]}

首先,键 3 具有值 d 和 e。我们将其替换为 d1 和 e1。下一个打印语句确认当前内容.

查看方法

multimap提供了多种查看方法。

keys: 将multimap中每个键值对中的键作为多集返回。因此,它可以使一个键重复多次。其大小与multimap的大小相同。

代码语言:javascript
复制
System.out.println(multimap.keys()); //[1, 2 x 2, 3 x 2]

如果它存在多次(映射多个值),则显示为 <键> x <映射数>

keySet: 它返回所有不同键的视图集合。因此,返回类型是一个集合。

代码语言:javascript
复制
System.out.println(multimap.keySet()); //[1, 2, 3]

values: 它返回所有键值对的所有值的集合的视图。返回的集合的大小与multimap的大小相同。

代码语言:javascript
复制
System.out.println(multimap.values()); //[a, b, c, e1, d1]

asMap: 它将multimap的视图作为传统map返回。

代码语言:javascript
复制
System.out.println(multimap.asMap());//{1=[a], 2=[b, c], 3=[e1, d1]}

Guava中的Multimaps

在本节中,我们将介绍 Multimaps 类中的静态方法。

创建不可修改的multimap

我们可以通过调用unmodifiableMultimap方法使现有的 multimap 不可修改。multimap 子接口有特定的方法,如 SetMultimapListMultimapSortedSetMultimap multimap。

如果我们有一个SetMultimap,我们可以创建一个不可修改的multimap。,

代码语言:javascript
复制
SetMultimap<String, String> multimap = HashMultimap.create();
multimap.put("1", "a");
multimap.put("2", "b");
multimap.put("3", "c");
SetMultimap<String, String> unmodifiableMultimap = Multimaps.unmodifiableSetMultimap(multimap);

尝试对返回的multimap添加数据将引发 UnsupportedOperationException,因为它是不可变的。

转换值和条目

multimap有一种方便的方法,即transformValues,将值转换为其他值。例如,使用上面创建的multimap ,让我们通过将值作为后缀添加的方式来转换值。

代码语言:javascript
复制
Multimap<String, String> transformedMultimap = Multimaps.transformValues(multimap, value -> value + "-" + value);
System.out.println(transformedMultimap);//{1=[a-a], 2=[b-b], 3=[c-c]}

所以现在,值 a 变为 a-a。

方法transformValues将multimap作为第一个参数,将函数作为第二个参数。该函数将通过传递multimap中的每个值来调用,并且函数的输出将用作新值。

请注意,此方法返回传递的multimap的视图,因此延迟应用该函数。因此,在执行任何查询操作(如包含值)时,可以多次应用该函数。如果我们需要经常使用结果并希望避免函数计算,我们可以将结果复制到新的多映射中。

如果我们想使用键值对的键进行值转换,我们可以使用transformEntries。第二个参数的类型为EntryTransformer,它采用键值对并返回新值。同样,返回的multimap只是一个视图。

代码语言:javascript
复制
transformedMultimap = Multimaps.transformEntries(multimap, (k, v) -> k + "-" + v);
System.out.println(transformedMultimap);//{1=[1-a], 2=[2-b], 3=[3-c]}

使用原始的multimap,我们将值设置为键和值的串联。

索引方法

索引方法采用 Iterable 和函数,用于构造新的multimap(而不是视图)。该函数将传递列表中的每个值,该函数的结果将成为multimap的键。传递给函数的元素本身将是值。

示例:我们有一个水果清单。我们想要构建一个以水果的名称首字母为key的multimap,我们可以这样做,如下所示:

代码语言:javascript
复制
List<String> list = Arrays.asList("apple", "banana", "orange", "avocado");
Multimap<Character, String> constructedMultimap = Multimaps.index(list, string -> string.charAt(0));
System.out.println(constructedMultimap);//{a=[apple, avocado], b=[banana], o=[orange]}

从注释中显示的输出中,水果苹果和avacado映射到键a。请记住,我们必须编写5-6行代码(在本文开头)才能做同样的事情,

让我们看另一个例子。我们将创建从单词长度到具有该长度的水果的映射。

代码语言:javascript
复制
Multimap<Integer, String> lengthToFruitMultimap = Multimaps.index(list, String::length);
System.out.println(lengthToFruitMultimap);//{5=[apple], 6=[banana, orange], 7=[avocado]}

过滤

我们可以从满足谓词的多地图过滤条目中创建视图。有三种方法 - filterKeys, filterValues and filterEntries。这使我们能够分别指定键,值或条目(键和值)的过滤条件。 让我们看一个例子。我们将使用上面构建的lengthToFruitMultimap 。首先,让我们过滤并仅获取奇数长度的条目。

代码语言:javascript
复制
Multimap<Integer, String> oddLengthFruits = Multimaps.filterKeys(lengthToFruitMultimap, k -> k % 2 == 1);
System.out.println(oddLengthFruits); //{5=[apple], 7=[avocado]}

现在,让我们获取名字以元音开头的水果。

代码语言:javascript
复制
private static Predicate<String> startsWithVowel() {
    List<String> vowels = Arrays.asList("a", "e", "i", "o", "u");
    return str -> vowels.stream().anyMatch(vowelCharacter -> str.startsWith(vowelCharacter));
}
Multimap<Integer, String> fruitsStartingWithAVowelMultimap =
        Multimaps.filterValues(lengthToFruitMultimap, startsWithVowel());
System.out.println(fruitsStartingWithAVowelMultimap);//{5=[apple], 6=[orange], 7=[avocado]}

请注意,有两个水果映射到键 6(orange and banana)。但只有一个(orange)以元音开头。 让我们将这两者结合起来,并过滤其键长度为奇数且值以元音开头的条目。

代码语言:javascript
复制
Multimap<Integer, String> resultMultimap =
        Multimaps.filterEntries(lengthToFruitMultimap, (entry) -> entry.getKey() % 2 == 1
                && startsWithVowel().test(entry.getValue()));
System.out.println(resultMultimap);//{5=[apple], 7=[avocado]}

MultiMap 实现类

建议不要直接使用Multimap, 而是使用其中一个子接口(如SetMultimapListMultimap)。现在,我们将看一些实现Multimap的类。

HashMultimap

它使用哈希表(哈希映射)实现Multimap。因此,它不保证按键或映射到键的值之间的顺序。它也不允许键的重复值(重复的键值对)。换句话说,我们只能为一个键添加一次值。

代码语言:javascript
复制
SetMultimap<String, String> hashMultimap = HashMultimap.create();
hashMultimap.put("2", "d");
hashMultimap.put("1", "a");
hashMultimap.put("1", "c");
hashMultimap.put("1", "b");
hashMultimap.put("1", "b");
System.out.println(hashMultimap);

输出:

代码语言:javascript
复制
{1=[a, b, c], 2=[d]}

尽管我们将键 2 添加到 1 之前,但由于它使用的是 HashMap,因此输出不保证排序。在这里,我们在键2之前获得键1的条目。将值 b 相加两次不会将其相加两次,因为它使用 HashSet 作为值。

LinkedHashMultimap

此multimap的实现基于键的已链接哈希映射和值的已链接哈希集。因此,它保留了插入顺序,但不允许重复的键值对。

代码语言:javascript
复制
SetMultimap<String, String> linkedHashMultimap = LinkedHashMultimap.create();
linkedHashMultimap.put("2", "d");
linkedHashMultimap.put("1", "a");
linkedHashMultimap.put("1", "c");
linkedHashMultimap.put("1", "b");
linkedHashMultimap.put("1", "b");
System.out.println(linkedHashMultimap);//{2=[d], 1=[a, c, b]}

我们可以看到为键(2 在 1 之前)和值(a,c,b)保留的插入顺序。

ArrayListMultimap

它对键使用HashMap,对值使用ArrayList。因此,它不维护键的插入顺序,但由于它对值使用 ArrayList,因此它保证插入顺序并且可以有重复项。

代码语言:javascript
复制
ListMultimap<String, String> arrayListMultimap = ArrayListMultimap.create();
arrayListMultimap.put("2", "d");
arrayListMultimap.put("1", "a");
arrayListMultimap.put("1", "c");
arrayListMultimap.put("1", "b");
arrayListMultimap.put("1", "b");
System.out.println(arrayListMultimap);//{1=[a, c, b, b], 2=[d]}

LinkedListMultimap

对键使用LinkedHashMap映射,对值使用 LinkedList。它按插入顺序存储数据(键和值),并且可以有重复项。

代码语言:javascript
复制
ListMultimap<String, String> linkedListMultimap = LinkedListMultimap.create();
linkedListMultimap.put("2", "d");
linkedListMultimap.put("1", "a");
linkedListMultimap.put("1", "c");
linkedListMultimap.put("1", "b");
linkedListMultimap.put("1", "b");
System.out.println(linkedListMultimap);//{2=[d], 1=[a, c, b, b]}

TreeMultimap

分别对键和值使用TreeMap和TreeSet。因此,它使用自然排序来对键进行排序,并使用映射到键的值进行排序。

代码语言:javascript
复制
SortedSetMultimap<String, String> treeMultimap = TreeMultimap.create();
treeMultimap.put("2", "d");
treeMultimap.put("1", "a");
treeMultimap.put("1", "c");
treeMultimap.put("1", "b");
treeMultimap.put("1", "b");
System.out.println(treeMultimap);//{1=[a, b, c], 2=[d]}

小结

本文详细介绍了谷歌番Google Guava中的MultiMap。带大家学习了MultiMap接口的 API 或方法。然后学习了Multimaps类的一些实用方法。最后,我们看到了一些带有示例的Multimap实现类。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 简介
  • MultiMap
  • Guava中的Multimaps
  • MultiMap 实现类
    • HashMultimap
      • LinkedHashMultimap
        • ArrayListMultimap
          • LinkedListMultimap
            • TreeMultimap
            • 小结
            领券
            问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档