【问题标题】:Reading a csv file with millions of row via java as fast as possible尽可能快地通过java读取具有数百万行的csv文件
【发布时间】:2016-07-20 08:36:49
【问题描述】:

我想读取一个包含数百万行的 csv 文件,并将这些属性用于我的决策树算法。我的代码如下:

String csvFile = "myfile.csv";
List<String[]> rowList = new ArrayList();
String line = "";
String cvsSplitBy = ",";
String encoding = "UTF-8";
BufferedReader br2 = null;
try {
    int counterRow = 0;
    br2 =  new BufferedReader(new InputStreamReader(new FileInputStream(csvFile), encoding));
    while ((line = br2.readLine()) != null) { 
        line=line.replaceAll(",,", ",NA,");
        String[] object = line.split(cvsSplitBy);
        rowList.add(object); 
        counterRow++;
    }
    System.out.println("counterRow is: "+counterRow);
    for(int i=1;i<rowList.size();i++){
        try{
           //this method includes many if elses only.
           ImplementDecisionTreeRulesFor2012(rowList.get(i)[0],rowList.get(i)[1],rowList.get(i)[2],rowList.get(i)[3],rowList.get(i)[4],rowList.get(i)[5],rowList.get(i)[6]); 
        }
        catch(Exception ex){
           System.out.printlnt("Exception occurred");   
        }
    }
}
catch(Exception ex){
    System.out.println("fix"+ex);
}

当 csv 文件的大小不大时,它可以正常工作。不过,确实很大。因此,我需要另一种更快地读取 csv 的方法。有什么建议吗?欣赏,谢谢。

【问题讨论】:

  • 您连续使用了两个隐藏的正则表达式(replaceAllsplit)。由于您的 CSV 如此简单,我建议您使用拆分器,例如 Guava's Splitter,它确实可以按预期完成工作(不像 split,它有很多问题)。此外,尽管它很简单,但您如何确定您的方法ImplementDecisionTreeRulesFor2012 不是您运行缓慢的根本原因?
  • 也许图书馆可以满足您的需求。我看到这里有一个比较:github.com/uniVocity/csv-parsers-comparison
  • 为什么需要 2 个循环和 rowList 来支持它们?为什么没有一个循环读取一行并将其传递给ImplementDecisionTreeRulesFor2012

标签: java csv


【解决方案1】:

只需使用uniVocity-parsers' CSV 解析器,而不是尝试构建您的自定义解析器。您的实现可能不够快或不够灵活,无法处理所有极端情况。

它非常节省内存,您可以在不到一秒的时间内解析一百万行。 This link 对许多 java CSV 库进行了性能比较,并且 univocity-parsers 名列前茅。

下面是一个简单的使用示例:

CsvParserSettings settings = new CsvParserSettings(); // you'll find many options here, check the tutorial.
CsvParser parser = new CsvParser(settings);

// parses all rows in one go (you should probably use a RowProcessor or iterate row by row if there are many rows)
List<String[]> allRows = parser.parseAll(new File("/path/to/your.csv"));

但是,这会将所有内容加载到内存中。要流式传输所有行,您可以这样做:

String[] row;
parser.beginParsing(csvFile)
while ((row = parser.parseNext()) != null) {
    //process row here.
}

更快的方法是使用RowProcessor,它也提供了更大的灵活性:

settings.setRowProcessor(myChosenRowProcessor);
CsvParser parser = new CsvParser(settings);
parser.parse(csvFile);

最后,它内置了routines,使用解析器执行一些常见任务(迭代java bean、转储ResultSets 等)

这应该涵盖基础知识,请查看文档以找到最适合您的案例的方法。

披露:我是这个库的作者。它是开源免费的(Apache V2.0 许可)。

【讨论】:

  • 我试过用这个,加载 csv 文件需要很多时间。
  • parseAll 方法将所有内容加载到内存中。您实际上应该使用beginParsing,然后使用parseNext 来迭代结果,或者简单地定义一个RowProcessor 然后调用parse。这不会将所有内容加载到内存中。我将更新我的答案以显示一个示例。
  • 通过增加堆大小,univocity 可以正常工作。但是,当我们尝试同时解析多个 csv 文件时,我认为univocity 没有多大帮助。如果我在这里错了,请告诉我。
  • @sskumar 您可以解析小于 50MB 堆大小的 5TB 文件。您肯定做错了什么,例如将大块数据存储在内存中,或者使用便利功能(例如获取行列表),这些功能本质上将所有内容存储在内存中,但仅适用于小型数据集。
  • 我很想在解析完成后遍历行。我应该能够控制每一行,我的软件需要处理它。仅仅解析 csv 文件少于到目前为止,我可以说 50MB 堆大小是不可能的。
【解决方案2】:

在这个 sn-p 中,我看到两个问题会大大减慢您的速度:

while ((line = br2.readLine()) != null) { 
    line=line.replaceAll(",,", ",NA,");
    String[] object = line.split(cvsSplitBy);
    rowList.add(object); 
    counterRow++;
}

首先,rowList 以默认容量开始,并且必须多次增加,总是导致旧的底层数组复制到新的。

然而,更糟糕的是,数据过度膨胀到 String[] 对象中。仅当您为该行调用 ImplementDecisionTreeRulesFor2012 时才需要列/单元格 - 而不是在您读取该文件并处理所有其他行时始终如此。将拆分(或 cmets 建议的更好的东西)移动到第二行。

(创建许多对象是不好的,即使你能负担得起内存。)

也许在阅读“百万”时调用 ImplementDecisionTreeRulesFor2012 会更好?它将完全避免 rowList ArrayList。

稍后 推迟拆分可减少 1000 万行的执行时间 从 1m8.262s(程序用完堆空间时)到 13.067s。

如果在调用 Implp...2012 之前没有强制您读取所有行,则时间会减少到 4.902 秒。

最后手写拆分和替换:

String[] object = new String[7];
//...read...
    String x = line + ",";
    int iPos = 0;
    int iStr = 0; 
    int iNext = -1;
    while( (iNext = x.indexOf( ',', iPos )) != -1 && iStr < 7 ){
        if( iNext == iPos ){
            object[iStr++] = "NA";
        } else {
             object[iStr++] = x.substring( iPos, iNext );
        }
        iPos = iNext + 1;
    }
    // add more "NA" if rows can have less than 7 cells

将时间减少到 1.983 秒。这比原始代码快了大约 30 倍,无论如何都会运行到 OutOfMemory。

【讨论】:

  • 你的计时是怎么做的?如果您正在从磁盘读取,则很有可能在您第一次运行它之后,该文件可能位于操作系统的文件缓存中
  • 我忘了补充说,original 代码在超过一分钟后耗尽了堆空间(见编辑)。这不是读取文件所需时间的问题。
  • 您对此完全确定吗?我可能想在运行之间刷新缓存只是为了完全确定
  • 您可能希望将new String[7] 带出线路循环。如果一行有超过 7 个字段,可能会在 iStr 上添加一个检查。
  • @tddmonkey 你可以自己尝试一下,使用 2000000 行这样你就会看到一些效果。
【解决方案3】:

除了前面提到的单义性之外,还值得一试

其中 3 个将作为评论时间最快的 csv 解析器。

编写自己的解析器可能会更慢且有问题。

【讨论】:

【解决方案4】:

如果您的目标是对象(即数据绑定),我编写了一个高性能库sesseltjonna-csv,您可能会觉得有趣。与 SimpleFlatMapper 和 uniVocity here 的基准比较。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2016-07-25
    • 2016-08-22
    • 2014-05-18
    • 2016-08-22
    • 2016-06-12
    • 2013-10-29
    • 2014-07-18
    • 1970-01-01
    相关资源
    最近更新 更多