317. Java Stream API - 使用 groupingBy() 构建直方图并提取最大值
✅ Part 1:构建直方图(Histogram)
我们有一个字符串列表,想要统计每种字符串长度的出现次数。这个任务可以通过 Collectors.groupingBy 配合 Collectors.counting() 来完成:
Collection<String> strings = List.of(
"one", "two", "three", "four", "five", "six", "seven", "eight", "nine",
"ten", "eleven", "twelve"
);
Map<Integer, Long> histogram = strings.stream()
.collect(Collectors.groupingBy(String::length, Collectors.counting()));
histogram.forEach((length, count) -> System.out.println(length + " :: " + count));
🖨 输出:
3 :: 4
4 :: 3
5 :: 3
6 :: 2
🔍 说明:
String::length是分组键:字符串的长度。Collectors.counting()是统计该组中元素的数量。- 最终结果是一个直方图:键是字符串长度,值是该长度出现的次数。
✅ Part 2:提取最大值(提取出现次数最多的字符串长度)
📌 方法一:用 Map.Entry 直接找最大值
我们想要从直方图中找到数量最多的那组。例如上面例子中,3 :: 4 是数量最多的(长度为3的字符串有4个)。
Map.Entry<Integer, Long> maxValue = histogram.entrySet().stream()
.max(Map.Entry.comparingByValue())
.orElseThrow();
System.out.println("maxValue = " + maxValue);
🖨 输出:
maxValue = 3=4
📌 小技巧:
entrySet().stream()是将 Map 转换为流。Map.Entry.comparingByValue()是基于值的比较器。orElseThrow()是保险措施,避免空结果。
⚠️ Part 3:多个最大值时如何处理?
如果出现多个 key 拥有相同的最大 value,那么上面方法只能选出一个,其他会被忽略。例如:
Collection<String> strings = List.of(
"two", "three", "four", "five", "six", "seven", "eight", "nine",
"ten", "eleven", "twelve"
);
输出的直方图:
3 :: 3
4 :: 3
5 :: 3
6 :: 2
👀 问题:有三个长度(3、4、5)的字符串数量都是 3。
如果你用 .max(),只会返回其中之一,可能会遗漏其他候选最大值。
✅ Part 4:如何提取所有最大值(Map 反转技巧)
🎯 思路: 我们可以将直方图 反转,把 "数量" 作为 key,"长度" 作为 value(或 List of value):
Map<Long, List<Integer>> reversed = histogram.entrySet().stream()
.collect(Collectors.groupingBy(
Map.Entry::getValue, // 用 count 作为 key
Collectors.mapping(Map.Entry::getKey, // 收集 length
Collectors.toList())
));
然后找出最大 key,对应的 List 就是所有最大值:
Long maxCount = reversed.keySet().stream()
.max(Long::compare)
.orElseThrow();
List<Integer> lengthsWithMaxCount = reversed.get(maxCount);
System.out.println("最大数量 = " + maxCount + ",对应长度 = " + lengthsWithMaxCount);
🖨 输出:
最大数量 = 3,对应长度 = [3, 4, 5]
✅ 总结
| 操作目标 | 推荐方法 |
|---|---|
| 构建直方图(统计数量) | groupingBy(..., counting()) |
| 提取唯一最大值(value 最大) | entrySet().stream().max(comparingByValue()) |
| 提取多个最大值(存在并列最大) | Map 反转 + groupingBy + max(keySet()) |
| 提升可读性 | 使用 Java 16+ 的 record |