You need to enable JavaScript to run this app.
最新活动
大模型
产品
解决方案
定价
生态与合作
支持与服务
开发者
了解我们

如何快速加载2000万条long型数据至Record类?

优化2000万条Long类型CSV数据加载速度的方案

嘿,我看到你在处理2000万条long类型数据的CSV加载时遇到了棘手的速度问题——每秒仅1000条的效率确实太低了,这完全没发挥出Java处理大数据的潜力。咱们来拆解下问题根源,再给出针对性的优化方案:

先分析可能的性能瓶颈

从你给出的代码片段(推测是用BufferedReader逐行读取+逐行解析+逐条插入Sorting<Record>)来看,慢的原因大概率集中在这几点:

  • 逐行解析字符串转long的开销太大,频繁创建String和Record对象触发GC
  • 如果Sorting<Record>add操作是边插入边维护有序性(比如每次插入都要找位置移动元素),那2000万条数据的时间复杂度会是O(n²),这绝对是性能灾难
  • 原生的BufferedReader+手动解析的效率远不如专门优化过的CSV解析库

具体优化方案

1. 替换为高性能CSV解析库

放弃手动逐行解析,用专门优化过的库来处理CSV,比如Univocity Parsers(它的性能比OpenCSV快好几倍,尤其适合大数据量)。这类库会批量读取字节、减少字符串对象创建,大幅提升解析速度。

示例代码:

private static void loadArray(String filepath, Sorting<Record> orderedArray) throws IOException, SortingException {
    System.out.println("\nLoading data from file...\n");
    
    // 初始化Univocity的CSV解析器,配置为单列、跳过空行
    CsvParserSettings settings = new CsvParserSettings();
    settings.setHeaderExtractionEnabled(false);
    settings.setSkipEmptyLines(true);
    settings.setLineSeparatorDetectionEnabled(true);
    
    CsvParser parser = new CsvParser(settings);
    // 批量读取,每次读10000条
    parser.beginParsing(new FileReader(filepath));
    
    String[] row;
    List<Record> batch = new ArrayList<>(10000);
    while ((row = parser.parseNext()) != null) {
        long value = Long.parseLong(row[0].trim());
        batch.add(new Record(value));
        
        // 批量插入到有序数组
        if (batch.size() >= 10000) {
            orderedArray.addAll(batch); // 确保Sorting有批量add的方法,如果没有就自己实现
            batch.clear();
        }
    }
    
    // 处理剩余的最后一批数据
    if (!batch.isEmpty()) {
        orderedArray.addAll(batch);
    }
    parser.stopParsing();
}

2. 彻底重构"边读边排序"的逻辑(最关键的优化)

如果你的Sorting<Record>是在每次add时都维护数组的有序性(比如插入排序逻辑),那2000万条数据的插入操作会慢到离谱。正确的做法是先把所有数据读到一个普通数组里,一次性排序后再放入Sorting容器

private static void loadArray(String filepath, Sorting<Record> orderedArray) throws IOException, SortingException {
    System.out.println("\nLoading data from file...\n");
    
    // 第一步:批量读取所有数据到普通数组
    List<Long> allValues = new ArrayList<>(20000000); // 预初始化容量,避免扩容开销
    
    CsvParserSettings settings = new CsvParserSettings();
    settings.setHeaderExtractionEnabled(false);
    settings.setSkipEmptyLines(true);
    CsvParser parser = new CsvParser(settings);
    parser.beginParsing(new FileReader(filepath));
    
    String[] row;
    while ((row = parser.parseNext()) != null) {
        allValues.add(Long.parseLong(row[0].trim()));
    }
    parser.stopParsing();
    
    // 第二步:一次性排序
    Collections.sort(allValues);
    
    // 第三步:把排序后的数据批量放入Sorting容器
    for (long val : allValues) {
        orderedArray.add(new Record(val));
        // 或者如果有批量add方法,直接传入转换后的Record列表
    }
}

这样时间复杂度从O(n²)降到O(n log n),速度会有质的飞跃。

3. 减少对象创建开销(进阶优化)

如果Record类只是包装一个long,那频繁创建Record对象会带来GC压力。可以考虑:

  • Sorting直接支持long类型,避免包装成Record对象
  • 或者使用对象池复用Record对象(不过对于2000万条来说,对象池可能意义不大,不如直接用基本类型数组)

4. 直接读取字节解析,跳过字符串转换(极致优化)

如果还想进一步提升速度,可以直接用FileChannel读取文件字节,手动解析字节流中的数字,完全避免String对象的创建:

private static void loadArray(String filepath, Sorting<Record> orderedArray) throws IOException, SortingException {
    System.out.println("\nLoading data from file...\n");
    
    try (FileChannel channel = FileChannel.open(Paths.get(filepath), StandardOpenOption.READ)) {
        ByteBuffer buffer = ByteBuffer.allocate(1024 * 1024); // 1MB缓冲区
        StringBuilder sb = new StringBuilder();
        
        while (channel.read(buffer) != -1) {
            buffer.flip();
            while (buffer.hasRemaining()) {
                byte b = buffer.get();
                if (b == '\n' || b == '\r') {
                    if (sb.length() > 0) {
                        long val = Long.parseLong(sb.toString());
                        orderedArray.add(new Record(val));
                        sb.setLength(0);
                    }
                } else {
                    sb.append((char) b);
                }
            }
            buffer.clear();
        }
        
        // 处理最后一行
        if (sb.length() > 0) {
            long val = Long.parseLong(sb.toString());
            orderedArray.add(new Record(val));
        }
    }
}

这种方式减少了大量String对象的创建,GC压力会小很多,速度也会更快。


最后,一定要先检查Sorting<Record>add方法实现,如果是边插入边排序,那必须改成先批量读取再整体排序,这是提升速度的核心。

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

火山引擎 最新活动