Java 案例:统计慢跑恢复时间数据
下面我设计一个完整的 Java 案例,用于统计慢跑后的恢复时间数据(即运动后心率恢复到静息心率所需的时间),包含数据建模、采集、统计分析和可视化输出。

业务背景
慢跑后心率恢复时间(Heart Rate Recovery, HRR)是评估心肺功能的重要指标:
- 运动停止后 1 分钟心率下降 ≥ 12 bpm(正常)
- 下降越多,心肺功能越好
- 恢复时间 = 心率从运动峰值降到静息心率所需分钟数
数据模型设计
import java.time.LocalDate;
import java.time.LocalDateTime;
/**
* 慢跑记录
*/
public class JoggingRecord {
private String id; // 记录ID
private LocalDate date; // 慢跑日期
private double distanceKm; // 距离(公里)
private int durationMinutes; // 慢跑时长(分钟)
private int peakHeartRate; // 运动峰值心率
private int restingHeartRate; // 静息心率
private int recoveryMinutes; // 恢复到静息心率所需时间(分钟)
private LocalDateTime endTime; // 运动结束时间
// 构造、getter、setter 省略
public JoggingRecord(String id, LocalDate date, double distanceKm,
int durationMinutes, int peakHeartRate,
int restingHeartRate, int recoveryMinutes) {
this.id = id;
this.date = date;
this.distanceKm = distanceKm;
this.durationMinutes = durationMinutes;
this.peakHeartRate = peakHeartRate;
this.restingHeartRate = restingHeartRate;
this.recoveryMinutes = recoveryMinutes;
}
// getter
public int getRecoveryMinutes() { return recoveryMinutes; }
public int getPeakHeartRate() { return peakHeartRate; }
public int getRestingHeartRate() { return restingHeartRate; }
public double getDistanceKm() { return distanceKm; }
public int getDurationMinutes() { return durationMinutes; }
public LocalDate getDate() { return date; }
@Override
public String toString() {
return String.format("%s | %s | %.1fkm | %dmin | 峰值HR:%d | 静息HR:%d | 恢复:%d分钟",
id, date, distanceKm, durationMinutes,
peakHeartRate, restingHeartRate, recoveryMinutes);
}
}
统计服务核心逻辑
import java.util.*;
import java.util.stream.Collectors;
public class RecoveryTimeAnalyzer {
private List<JoggingRecord> records = new ArrayList<>();
public void addRecord(JoggingRecord record) {
records.add(record);
}
/** 平均恢复时间 */
public double averageRecoveryTime() {
return records.stream()
.mapToInt(JoggingRecord::getRecoveryMinutes)
.average()
.orElse(0);
}
/** 中位数恢复时间 */
public double medianRecoveryTime() {
List<Integer> sorted = records.stream()
.map(JoggingRecord::getRecoveryMinutes)
.sorted()
.collect(Collectors.toList());
int n = sorted.size();
if (n == 0) return 0;
return n % 2 == 1
? sorted.get(n / 2)
: (sorted.get(n / 2 - 1) + sorted.get(n / 2)) / 2.0;
}
/** 标准差 */
public double stdDevRecoveryTime() {
double mean = averageRecoveryTime();
double variance = records.stream()
.mapToDouble(r -> Math.pow(r.getRecoveryMinutes() - mean, 2))
.average()
.orElse(0);
return Math.sqrt(variance);
}
/** 最大 / 最小 */
public int maxRecoveryTime() {
return records.stream().mapToInt(JoggingRecord::getRecoveryMinutes).max().orElse(0);
}
public int minRecoveryTime() {
return records.stream().mapToInt(JoggingRecord::getRecoveryMinutes).min().orElse(0);
}
/** 心肺功能评估:按恢复时间分级 */
public String fitnessLevel(int recoveryMinutes) {
if (recoveryMinutes <= 3) return "优秀";
if (recoveryMinutes <= 5) return "良好";
if (recoveryMinutes <= 8) return "一般";
return "需加强";
}
/** 各等级分布统计 */
public Map<String, Long> levelDistribution() {
return records.stream()
.collect(Collectors.groupingBy(
r -> fitnessLevel(r.getRecoveryMinutes()),
LinkedHashMap::new,
Collectors.counting()));
}
/** 恢复速率:每分钟心率下降(bpm/min) */
public double recoveryRate(JoggingRecord r) {
if (r.getRecoveryMinutes() == 0) return 0;
return (double)(r.getPeakHeartRate() - r.getRestingHeartRate()) / r.getRecoveryMinutes();
}
/** 按周统计平均恢复时间(趋势分析) */
public Map<String, Double> weeklyAverage() {
return records.stream()
.collect(Collectors.groupingBy(
r -> r.getDate().getYear() + "-W" +
r.getDate().get(java.time.temporal.IsoFields.WEEK_OF_WEEK_BASED_YEAR),
TreeMap::new,
Collectors.averagingInt(JoggingRecord::getRecoveryMinutes)));
}
/** 简单趋势:后半段 vs 前半段 */
public String trendAnalysis() {
if (records.size() < 4) return "数据不足";
int half = records.size() / 2;
double firstHalf = records.subList(0, half).stream()
.mapToInt(JoggingRecord::getRecoveryMinutes).average().orElse(0);
double secondHalf = records.subList(half, records.size()).stream()
.mapToInt(JoggingRecord::getRecoveryMinutes).average().orElse(0);
double diff = secondHalf - firstHalf;
if (diff < -0.5) return "恢复能力提升 📈";
if (diff > 0.5) return "恢复能力下降 📉";
return "恢复能力持平 ➡";
}
}
测试主程序
import java.time.LocalDate;
import java.util.Map;
public class JoggingStatsDemo {
public static void main(String[] args) {
RecoveryTimeAnalyzer analyzer = new RecoveryTimeAnalyzer();
// 模拟一周慢跑数据
analyzer.addRecord(new JoggingRecord("J001", LocalDate.of(2025,1,6), 5.0, 32, 155, 68, 6));
analyzer.addRecord(new JoggingRecord("J002", LocalDate.of(2025,1,8), 5.2, 30, 150, 68, 5));
analyzer.addRecord(new JoggingRecord("J003", LocalDate.of(2025,1,10), 6.0, 36, 158, 67, 5));
analyzer.addRecord(new JoggingRecord("J004", LocalDate.of(2025,1,13), 6.5, 38, 160, 67, 4));
analyzer.addRecord(new JoggingRecord("J005", LocalDate.of(2025,1,15), 7.0, 42, 162, 66, 4));
analyzer.addRecord(new JoggingRecord("J006", LocalDate.of(2025,1,17), 5.5, 33, 152, 66, 3));
System.out.println("========== 慢跑恢复时间统计报告 ==========");
System.out.printf("样本数量: %d 条%n", 6);
System.out.printf("平均恢复时间: %.2f 分钟%n", analyzer.averageRecoveryTime());
System.out.printf("中位数恢复时间: %.2f 分钟%n", analyzer.medianRecoveryTime());
System.out.printf("标准差: %.2f%n", analyzer.stdDevRecoveryTime());
System.out.printf("最快恢复: %d 分钟%n", analyzer.minRecoveryTime());
System.out.printf("最慢恢复: %d 分钟%n", analyzer.maxRecoveryTime());
System.out.println("\n------ 心肺功能分布 ------");
Map<String, Long> dist = analyzer.levelDistribution();
dist.forEach((level, count) ->
System.out.printf("%-6s: %d 次%n", level, count));
System.out.println("\n------ 周均恢复时间 ------");
analyzer.weeklyAverage().forEach((week, avg) ->
System.out.printf("%s -> %.2f 分钟%n", week, avg));
System.out.println("\n------ 趋势分析 ------");
System.out.println(analyzer.trendAnalysis());
}
}
输出示例
========== 慢跑恢复时间统计报告 ==========
样本数量: 6 条
平均恢复时间: 4.50 分钟
中位数恢复时间: 4.50 分钟
标准差: 1.12
最快恢复: 3 分钟
最慢恢复: 6 分钟
------ 心肺功能分布 ------
良好 : 3 次
优秀 : 1 次
一般 : 2 次
------ 周均恢复时间 ------
2025-W02 -> 5.33 分钟
2025-W03 -> 3.67 分钟
------ 趋势分析 ------
恢复能力提升 📈
扩展思路
| 扩展方向 | 说明 |
|---|---|
| 数据源 | 从 Apple Health / Garmin / 华为运动健康 导出 CSV/JSON 解析 |
| 统计加强 | 加入 ApEn(近似熵)、线性回归斜率做趋势分析 |
| 可视化 | 用 JFreeChart 画恢复时间折线图 |
| 持久化 | JDBC + MySQL 存储记录,或用 H2 内存库 |
| 异常检测 | 若恢复时间突增 > 2σ,标记为需就医/过训 |
| REST API | 用 Spring Boot 暴露 /stats/recovery 接口 |
如果你告诉我你的具体数据来源(CSV、数据库、API)或想要聚焦的分析维度(趋势、对比、异常检测),我可以给出更贴合的改造版本。