用Collectors.groupingBy按小时汇总业务量,关键是将时间字段截断为小时精度作为分组键;支持LocalDateTime.truncatedTo(HOURS)或DateTimeFormatter格式化;需手动补全缺失小时以保证时间连续;可嵌套分组实现业务类型+小时双重维度;注意时区统一与性能优化。

用 Collectors.groupingBy 按小时汇总业务量,核心是把时间字段“截断”到小时精度,再分组计数或求和。关键不在 groupingBy 本身,而在如何构造合适的分组键。
提取小时级分组键(LocalDateTime / Date)
如果原始数据是 LocalDateTime,推荐用 truncatedTo(HOURS) 直接截断分钟和秒,保留年月日+小时:
Map<localdatetime long> hourlyCount = list.stream()
.collect(Collectors.groupingBy(
record -> record.getOccurTime().truncatedTo(ChronoUnit.HOURS),
Collectors.counting()
));</localdatetime>
若用 Date,可转为 LocalDateTime 再截断,或用 SimpleDateFormat("yyyy-MM-dd HH") 格式化后当 key(注意线程安全,建议用 DateTimeFormatter):
DateTimeFormatter hourFmt = DateTimeFormatter.ofPattern("yyyy-MM-dd HH");
Map<string integer> hourlySum = list.stream()
.collect(Collectors.groupingBy(
r -> r.getOccurTime().format(hourFmt),
Collectors.summingInt(Record::getAmount)
));</string>
补全缺失小时(避免报表断档)
groupingBy 默认只返回有数据的小时,但报表常需连续时间轴(如00–23点全显示)。需手动补零:
- 先获取数据中最小/最大时间,生成完整小时区间(如从当天00:00到23:00)
- 用
Stream.iterate或循环构造所有目标小时键 - 对每个键,从 groupBy 结果中
getOrDefault(key, 0L)
按业务类型 + 小时双重分组
若还需区分业务类型(如“支付”“退款”),可用 Map 或嵌套 groupingBy:
Map<string map long>> typeHourly = list.stream()
.collect(Collectors.groupingBy(
Record::getBizType,
Collectors.groupingBy(
r -> r.getOccurTime().truncatedTo(ChronoUnit.HOURS),
Collectors.counting()
)
));</string>
这样得到的是 “类型 → 小时→数量” 的二级结构,适合多维度下钻分析。
性能与可读性兼顾的小技巧
避免在 groupingBy lambda 中重复计算;提前提取并缓存分组键:
- 定义静态 formatter 或重写 record 的
getHourKey()方法 - 若数据量大(>10万),考虑先用
TreeMap保证 key 有序,省去后续排序 - 聚合逻辑复杂时(如同时要 count、sum、avg),用
Collectors.collectingAndThen+ 自定义 collector 更清晰
不复杂但容易忽略:时区必须统一。确保所有时间字段已转为业务所在时区(如东八区),否则跨天或夏令时会导致错位。










