1. 概述
本文将深入探讨 Google Guava 库中的核心数据结构——Multimap
。它是一种特殊的键值对集合,与 java.util.Map
类似,但允许一个键关联多个值。这种设计在处理一对多关系时特别实用。
2. 依赖配置
首先在 pom.xml
中添加 Guava 依赖:
<dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
<version>33.3.0-jre</version>
</dependency>
最新版本可在此处获取:Maven Central
3. Multimap 实现原理
Guava Multimap 的核心特性:当向同一个键添加多个值时,新值不会覆盖旧值,而是追加到集合中。看个测试用例:
String key = "a-key";
Multimap<String, String> map = ArrayListMultimap.create();
map.put(key, "firstValue");
map.put(key, "secondValue");
assertEquals(2, map.size());
打印 map
内容会输出:
{a-key=[firstValue, secondValue]}
通过键获取值时,会得到包含所有值的 Collection<String>
:
Collection<String> values = map.get(key);
输出结果:
[firstValue, secondValue]
4. 遍历 Multimap 的方式
Multimap
提供了多种内置遍历方法,我们逐一解析。先准备测试数据:
Multimap<String, String> multiMap = ArrayListMultimap.create();
multiMap.putAll("key1", List.of("value1", "value11", "value111"));
multiMap.putAll("key2", List.of("value2", "value22", "value222"));
multiMap.putAll("key3", List.of("value3", "value33", "value333"));
4.1. 使用 entries() 方法
最直接的方式:entries()
返回所有键值对的集合视图。实现代码:
static void iterateUsingEntries(Multimap<String, String> multiMap) {
multiMap.entries()
.forEach(entry -> LOGGER.info("{} => {}", entry.getKey(), entry.getValue()));
}
执行输出:
key1 => value1
key1 => value11
key1 => value111
key2 => value2
key2 => value22
key2 => value222
key3 => value3
key3 => value33
key3 => value333
每个 entry
都是 Map.Entry
实例,包含完整的键值对信息。
4.2. 使用 asMap() 方法
asMap()
将 Multimap 转换为标准 Map 视图,其中值是原始集合:
static void iterateUsingAsMap(Multimap<String, String> multiMap) {
multiMap.asMap()
.entrySet()
.forEach(entry -> LOGGER.info("{} => {}", entry.getKey(), entry.getValue()));
}
输出结果:
key1 => [value1, value11, value111]
key2 => [value2, value22, value222]
key3 => [value3, value33, value333]
⚠️ 注意:这里每个键对应的值是整个集合,而非单个值。
4.3. 使用 keySet() 方法
仅遍历所有唯一键,返回 Set<String>
:
static void iterateUsingKeySet(Multimap<String, String> multiMap) {
multiMap.keySet()
.forEach(LOGGER::info);
}
输出:
key1
key2
key3
使用方法引用简化代码,适合只需处理键的场景。
4.4. 使用 keys() 方法
keys()
返回包含所有键的集合(含重复),本质是 Multiset
:
static void iterateUsingKeys(Multimap<String, String> multiMap) {
multiMap.keys()
.forEach(LOGGER::info);
}
输出:
key1
key1
key1
key2
key2
key2
key3
key3
key3
✅ 与 keySet()
的区别:每个值对应的键都会出现一次,适合需要统计键频次的场景。
4.5. 使用 values() 方法
直接获取所有值的集合,忽略键信息:
static void iterateUsingValues(Multimap<String, String> multiMap) {
multiMap.values()
.forEach(LOGGER::info);
}
输出:
value1
value11
value111
value2
value22
value222
value3
value33
value333
⚠️ 重要:返回的集合与底层 Multimap 双向绑定,修改会相互影响,但无法直接添加新元素。
5. 与标准 Map 的对比
标准 java.util.Map
不支持一键多值。看个踩坑案例:
String key = "a-key";
Map<String, String> map = new LinkedHashMap<>();
map.put(key, "firstValue");
map.put(key, "secondValue");
assertEquals(1, map.size()); // 实际只有 secondValue
要实现类似 Multimap 的效果,需要手动处理:
String key = "a-key";
Map<String, List<String>> map = new LinkedHashMap<>();
List<String> values = map.get(key);
if(values == null) {
values = new LinkedList<>();
values.add("firstValue");
values.add("secondValue");
}
map.put(key, values);
assertEquals(1, map.size()); // 虽然列表有2个元素,但map.size()=1
这种写法既啰嗦又容易出错,此时 Multimap 就是更优雅的选择。
6. Multimap 的核心优势
Multimap 主要用于替代 Map<K, Collection<V>>
的场景,优势包括:
- ✅ 简化初始化:添加值前无需手动创建空集合
- ✅ 安全获取:
get()
永远返回空集合而非null
,避免 NPE - ✅ 自动清理:当键关联的所有值被移除时,键会自动删除(标准 Map 会保留空集合造成内存浪费)
- ✅ 真实计数:
size()
返回实际存储的值总数,keySet().size()
返回唯一键数 - ✅ 丰富 API:提供多种内置遍历方法,简化开发
7. 总结
本文深入探讨了 Guava Multimap 的使用场景和实现原理,通过对比标准 Map 突出了其在一对多关系处理中的优势。重点解析了五种遍历方式:
entries()
:遍历所有键值对asMap()
:转换为标准 Map 视图keySet()
:遍历唯一键keys()
:遍历所有键(含重复)values()
:遍历所有值
当你的代码中出现 Map<K, List<V>>
或 Map<K, Set<V>>
时,强烈建议考虑使用 Multimap 重构,代码将更简洁高效。