You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

使用Apache Commons I/O读取23GB大文件时CPU占用100%排查

超大文件读取CPU占满100%问题根因

你的判断是对的,Apache Commons I/O的LineIterator本身是流式逐行读取的成熟实现,不存在导致CPU跑满的缺陷,问题全部出在逐行循环内的业务逻辑——你的循环总执行次数是4.04亿次,哪怕单行逻辑多100纳秒的冗余开销,累计起来都会把CPU资源完全吃满,具体高开销点按影响权重排序如下:

  • 每行执行正则分割+Stream流遍历是最大CPU开销来源
    你使用的lineR.split("[\t ]")每次调用都会触发正则引擎匹配,把当前行全量切割成字符串数组,哪怕你要找的目标字段就在行的前几个位置,也会把整行所有分割片段全部生成完才会进入后续流程。紧接着你用Arrays.stream()包装数组、做filter遍历、findFirst匹配,相当于为了找一个目标值,额外生成了N个临时字符串对象、走了一遍流处理的流水线开销。4亿行累计下来,这部分逻辑会吃掉70%以上的CPU资源,同时制造大量短命对象触发频繁GC,GC运行时还会额外占用CPU。
    额外提一句:超高频循环场景下不要用Java Stream做简单遍历/匹配,Stream的抽象层开销在亿级调用量下会被放得非常大。
  • 无意义的逐行进度计算
    你的进度日志是5%粒度输出的,但你每读一行就会做一次字节累加、百分比计算、条件判断,这部分逻辑单步开销极低,但乘以4亿次的循环量之后,累计开销非常可观。
  • 无效值写入Set的额外开销
    当行内找不到匹配前缀的字段时,你用orElse(" ")生成了默认值,还是会执行substring、往uniqueNameDomainSet里写入无效值,既浪费CPU做字符串切割、Set哈希计算,还会让Set里存入无效数据,进一步拖慢后续add操作的哈希查找速度。另外你用lineR.length()累加字节数本身是逻辑错误:UTF-8编码下多字节字符的length()返回的是字符数不是字节数,算出来的进度完全不准,属于无意义的计算。

优化参考代码

核心思路是砍掉所有不必要的临时对象创建、全量遍历操作,只做最少的字符匹配:

try (LineIterator it = FileUtils.lineIterator(file1, "UTF-8")) {
    long lineCount = 0;
    // 每10万行计算一次进度,误差可以忽略,大幅减少计算次数
    final int PROGRESS_CAL_INTERVAL = 100000;
    while (it.hasNext()) {
        String lineR = it.nextLine();
        lineCount++;

        // 批量计算进度,避免逐行计算
        if (lineCount % PROGRESS_CAL_INTERVAL == 0) {
            int percent = (int) (realBytesRead * 100 / totalBytes);
            if (percent > prePercent && percent % 5 == 0) {
                log.info(percent + "% " + prefix + " read.");
                prePercent = percent;
            }
        }

        // 直接定位前缀位置,不做全量split
        int matchPos = lineR.indexOf(prefix);
        if (matchPos == -1) {
            continue;
        }
        // 向前找最近的分隔符确定字段起点
        int segStart = matchPos;
        while (segStart > 0) {
            char c = lineR.charAt(segStart - 1);
            if (c == '\t' || c == ' ') break;
            segStart--;
        }
        // 向后找最近的分隔符确定字段终点
        int segEnd = matchPos + prefix.length();
        while (segEnd < lineR.length()) {
            char c = lineR.charAt(segEnd);
            if (c == '\t' || c == ' ') break;
            segEnd++;
        }
        // 仅截取目标字段,不生成多余临时字符串
        String targetDomain = lineR.substring(segStart, segEnd - 1);
        uniqueNameDomainSet.add(targetDomain);
    }
}

注:代码中realBytesRead建议直接从底层文件输入流获取累计读取字节数,不要通过字符串长度计算,避免逻辑误差。

内容的提问来源于stack exchange,提问作者Alex

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.09.03 08:31:21