java案例统计长短传比例如何分布?

wen java案例 2

Java 案例:统计长短传比例分布

业务背景

在短视频/直播/内容平台中,"长短传"通常指:

java案例统计长短传比例如何分布?

  • 短传:用户上传的短内容(如短视频、短文本、小文件)
  • 长传:用户上传的长内容(如长视频、长文本、大文件)

统计长短传比例分布,是内容运营、风控、存储规划的重要指标。


需求定义

时长**区分(也可以按文件大小、文本长度):

类型 时长范围
短传 < 60 秒
中传 60 ~ 300 秒
长传 > 300 秒

统计目标:

  1. 各类型数量、占比
  2. 按天/按用户/按渠道的分布
  3. 输出比例结果

数据模型

@Data
@AllArgsConstructor
public class UploadRecord {
    private String id;
    private String userId;
    private String channel;      // 渠道:app/web/mini
    private long durationSec;    // 时长(秒)
    private LocalDate uploadDate;
    private long fileSize;       // 字节
}

核心统计代码

1 枚举定义

public enum UploadType {
    SHORT("短传"),
    MEDIUM("中传"),
    LONG("长传");
    private final String desc;
    UploadType(String desc) { this.desc = desc; }
    public String getDesc() { return desc; }
    public static UploadType of(long durationSec) {
        if (durationSec < 60) return SHORT;
        if (durationSec <= 300) return MEDIUM;
        return LONG;
    }
}

2 统计结果对象

@Data
public class RatioStat {
    private UploadType type;
    private long count;
    private double ratio; // 占比 0~1
    public String getRatioPercent() {
        return String.format("%.2f%%", ratio * 100);
    }
}

3 基础统计(Stream 实现)

public class UploadStatService {
    /**
     * 统计长短传比例分布
     */
    public List<RatioStat> statRatio(List<UploadRecord> records) {
        if (records == null || records.isEmpty()) {
            return Collections.emptyList();
        }
        Map<UploadType, Long> countMap = records.stream()
                .collect(Collectors.groupingBy(
                        r -> UploadType.of(r.getDurationSec()),
                        Collectors.counting()
                ));
        long total = records.size();
        return Arrays.stream(UploadType.values())
                .map(type -> {
                    long cnt = countMap.getOrDefault(type, 0L);
                    RatioStat stat = new RatioStat();
                    stat.setType(type);
                    stat.setCount(cnt);
                    stat.setRatio(total == 0 ? 0 : (double) cnt / total);
                    return stat;
                })
                .collect(Collectors.toList());
    }
}

4 按渠道分组统计

public Map<String, List<RatioStat>> statRatioByChannel(List<UploadRecord> records) {
    Map<String, List<UploadRecord>> byChannel = records.stream()
            .collect(Collectors.groupingBy(UploadRecord::getChannel));
    Map<String, List<RatioStat>> result = new LinkedHashMap<>();
    byChannel.forEach((channel, list) ->
            result.put(channel, statRatio(list))
    );
    return result;
}

5 按天统计分布

public Map<LocalDate, List<RatioStat>> statRatioByDay(List<UploadRecord> records) {
    Map<LocalDate, List<UploadRecord>> byDay = records.stream()
            .collect(Collectors.groupingBy(UploadRecord::getUploadDate));
    Map<LocalDate, List<RatioStat>> result = new TreeMap<>();
    byDay.forEach((day, list) -> result.put(day, statRatio(list)));
    return result;
}

完整示例

public class Main {
    public static void main(String[] args) {
        List<UploadRecord> records = List.of(
            new UploadRecord("1","u1","app", 30, LocalDate.of(2024,1,1), 1024),
            new UploadRecord("2","u1","app", 45, LocalDate.of(2024,1,1), 2048),
            new UploadRecord("3","u2","web", 120, LocalDate.of(2024,1,1), 5000),
            new UploadRecord("4","u3","app", 600, LocalDate.of(2024,1,2), 10000),
            new UploadRecord("5","u4","web", 900, LocalDate.of(2024,1,2), 20000),
            new UploadRecord("6","u5","app", 20, LocalDate.of(2024,1,2), 512)
        );
        UploadStatService service = new UploadStatService();
        // 全局比例
        System.out.println("==== 全局比例 ====");
        service.statRatio(records).forEach(s ->
            System.out.printf("%s: 数量=%d, 占比=%s%n",
                s.getType().getDesc(), s.getCount(), s.getRatioPercent()));
        // 按渠道
        System.out.println("\n==== 按渠道 ====");
        service.statRatioByChannel(records).forEach((ch, stats) -> {
            System.out.println("渠道: " + ch);
            stats.forEach(s ->
                System.out.printf("  %s: %s%n", s.getType().getDesc(), s.getRatioPercent()));
        });
        // 按天
        System.out.println("\n==== 按天 ====");
        service.statRatioByDay(records).forEach((day, stats) -> {
            System.out.println("日期: " + day);
            stats.forEach(s ->
                System.out.printf("  %s: %s%n", s.getType().getDesc(), s.getRatioPercent()));
        });
    }
}

输出示例:

==== 全局比例 ====
短传: 数量=3, 占比=50.00%
中传: 数量=1, 占比=16.67%
长传: 数量=2, 占比=33.33%
==== 按渠道 ====
渠道: app
  短传: 66.67%
  中传: 0.00%
  长传: 33.33%
渠道: web
  短传: 0.00%
  中传: 50.00%
  长传: 50.00%

大数据量优化方案

当记录量达到百万级,建议:

1 使用并行流

Map<UploadType, Long> countMap = records.parallelStream()
    .collect(Collectors.groupingByConcurrent(
        r -> UploadType.of(r.getDurationSec()),
        Collectors.counting()
    ));

2 单次遍历(避免多次 Stream)

public List<RatioStat> statRatioFast(List<UploadRecord> records) {
    long[] counts = new long[3]; // SHORT, MEDIUM, LONG
    for (UploadRecord r : records) {
        counts[UploadType.of(r.getDurationSec()).ordinal()]++;
    }
    long total = records.size();
    List<RatioStat> result = new ArrayList<>(3);
    for (UploadType type : UploadType.values()) {
        RatioStat s = new RatioStat();
        s.setType(type);
        s.setCount(counts[type.ordinal()]);
        s.setRatio(total == 0 ? 0 : (double) counts[type.ordinal()] / total);
        result.add(s);
    }
    return result;
}

3 数据库聚合(推荐生产用)

直接 SQL 聚合,避免全量拉取:

SELECT
    CASE
        WHEN duration_sec < 60 THEN 'SHORT'
        WHEN duration_sec <= 300 THEN 'MEDIUM'
        ELSE 'LONG'
    END AS type,
    COUNT(*) AS cnt,
    ROUND(COUNT(*) * 100.0 / SUM(COUNT(*)) OVER (), 2) AS ratio_pct
FROM upload_record
WHERE upload_date = '2024-01-01'
GROUP BY type;

Java 侧只需映射结果:

@Mapper
public interface UploadMapper {
    @Select("... 上面 SQL ...")
    List<RatioStat> statRatioByDate(@Param("date") LocalDate date);
}

4 缓存 + 定时计算

@Scheduled(cron = "0 5 * * * ?") // 每小时5分
public void refreshRatioCache() {
    LocalDate today = LocalDate.now();
    Map<String, List<RatioStat>> result = statRatioByDayForDate(today);
    redisTemplate.opsForValue().set("upload:ratio:" + today, result, 2, TimeUnit.HOURS);
}

扩展建议

场景 扩展方式
按用户分层 groupingBy(UploadRecord::getUserId) 后再统计
加权比例 按播放量/曝光量加权,而非条数
风控指标 长传占比突增可触发告警
分位数分析 用 long[] durationArr + 排序求 P50/P90/P99
实时统计 Flink/Spark Streaming 按窗口聚合

  • 核心逻辑:分类枚举 + groupingBy + 计算占比
  • 小数据:Stream 一行搞定
  • 大数据:单次遍历或 SQL 聚合
  • 业务分层:全局 → 渠道 → 天 → 用户,逐层下钻
  • 注意:占比要保证分母一致,空数据要防除零

如果你的"长短传"是按文件大小或文本长度区分,只需把 UploadType.of() 里的判断条件替换即可,其余统计逻辑完全复用。

抱歉,评论功能暂时关闭!

友情链接攻略窝 · 礼包窝 · 配置窝 · 换算窝 · 算法窝 · 查文窝 · 手册窝 · 问答窝 · 全文搜 · 手册库
相关站点礼包窝 · 查文窝