通过java尽可能快地读取具有数百万行的csv文件

2022-09-03 01:21:01

我想读取包含数百万行的 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。有什么建议吗?谢谢,谢谢。


答案 1

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

它的内存效率非常高,您可以在不到一秒钟的时间内解析一百万行。此链接具有许多java CSV库的性能比较,并且单声解析器位于顶部。

以下是如何使用它的简单示例:

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);

最后,它具有内置的例程,这些例程使用解析器来执行一些常见任务(迭代java bean,dump s等)。ResultSet

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

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


答案 2

在这个片段中,我看到两个问题会大大减慢你的速度:

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

首先,rowList 从默认容量开始,并且必须增加很多次,这总是导致旧基础阵列的拷贝成为新的。

然而,更糟糕的是,将数据过度放大到String[]对象中。仅当为该行调用 ImplementDecisionTreeRulesFor2012 时,才需要列/单元格 - 而不是在读取该文件并处理所有其他行时始终需要列/单元格。将拆分(或更好的内容,如注释所示)移到第二行。

(创建许多对象是不好的,即使您可以负担得起内存。

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

推迟拆分可将 1000 万行的执行时间从 1m8.262 秒(当程序的堆空间不足时)减少到 13.067 秒。

如果您没有被迫读取所有行,然后才能调用 Implp...2012年,时间减少到4.902s。

最后用手写拆分和替换:

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.983s。这比原始代码快30倍,无论如何都会运行到OutOfMemory。