用Java读取大型CSV文件

时间:2017-05-06 12:35:57

标签: java file buffer large-files opencsv

我正在尝试用Java读取 1,000,000 行CSV文件。我正在使用OpenCSV库,它可以在 30,000 行的较小文件上正常工作。在半秒钟内处理它。但是,当我尝试从一百万行文件中读取时,它永远不会完成。

现在我测试看,当它实际停止时,通过使用我自己的二进制搜索版本,我首先尝试读取 500k 行,然后 250k ,等等,我发现很容易读取 145k 行, 0.5-0.7秒,而 150k 甚至没有完成。

我彻底搜索了SO,找到了我在代码中使用的几个解决方案,例如使用BufferedReaderBufferedInputStream等,但没有一个解决了它。仍然在 145-150k 行之间失败。

这是我的代码的相关部分(交换 150000与145000 是导致程序在<1秒内执行的原因):

try {
       // BufferedInputStream bufferedInputStream = new BufferedInputStream(new FileInputStream("myFile.csv"));
        CSVReader csvReader = new CSVReader(new InputStreamReader
                (new BufferedInputStream(new FileInputStream("myFile.csv"), 8192 * 32)));
        try {
            int count = 0;
            String[] line;
            long timeStart = System.nanoTime();
            while((line = csvReader.readNext()) != null){
                count ++;
                if(count >= 150000){
                    break;
                }
            }
            long timeEnd = System.nanoTime();
            System.out.println("Count: " + count);
            System.out.println("Time: " + (timeEnd - timeStart) * 1.0 / 1000000000 + " sec");
        } catch (IOException e) {
            e.printStackTrace();
        }
    } catch (FileNotFoundException e) {
        System.out.println("File not found");
    }

如您所见,我也尝试设置更大的缓冲区大小。我尝试了ReadersInput Streams等的各种组合,没有什么真正有所作为。

我想知道我该怎么办?有没有办法阅读,一次说100k行,然后继续阅读下一个100k?

此外,我对任何其他不一定包含OpenCSV库的解决方案持开放态度。我只是用它来简化解析csv文件。

2 个答案:

答案 0 :(得分:3)

我刚刚看了一下OpenCSV实现,我什么都看不到会解释这种行为只是因为文件很大并且包含很多记录。

但OpenCSV能够从网站处理多行数据:

  

处理带有嵌入式回车的带引号的条目(即跨越多行的条目)。

我认为在你的情况下,有一条记录 - 某个第150k条记录 - 包含一个错误的引用条目。默认引用char为"。这可能是一个记录,如:

value,value,"badvalue,value
value,value,value,value

在这种情况下,OpenCSV使用的解析器设置为挂起状态,这意味着要读取的记录将在下一行继续。对CSVReader.readNext()的调用尝试读取完成csv记录所需的行数。如果错误引用字符没有匹配,它将读取并读取和读取,直到缓冲区耗尽或发生其他错误。

要查找您可以读取文件的记录,请计算记录并打印出当前计数。这将为您提供最后一个有效记录的编号,然后将像现在一样停止/挂起。

然后我会编写一个新程序,只是逐行读取文件(不使用CSVParser,只是简单的行),并跳过你知道的好行数。然后从那里打印大约10行,你有一些数据需要分析。

答案 1 :(得分:0)

P.J. Meisch 在 answer 中讨论了这个问题。但是,我找到了一个很好的解决方案,并没有提出。

在构建解析器时使用 withIgnoreQuotations 方法来解决引号问题。

以下示例使用 CsvToBeanBuilder 将 CSV 文件(位于 filepath)解析为 bean 列表。中的值由制表符('\t')分隔,第一行是标题行,因此被跳过(以免尝试将其解析为 bean 实例)。

List<Bean> beans = new CsvToBeanBuilder<Bean>(new FileReader(filepath))
    .withIgnoreQuotations(true)
    .withSeparator('\t')
    .withSkipLines(1)
    .withType(Bean.class)
    .build()
    .parse();

此示例将保存的 CSV 文件逐行解析为相同的 bean 类型。 注意,bean 有一些n 属性

List<Bean> beans = new ArrayList<Bean>();
Path path = Paths.get(filepath);

String[] line;

CSVParser parser = new CSVParserBuilder()
    .withSeparator('\t')
    .withIgnoreQuotations(true)
    .build();

CSVReader reader = new CSVReaderBuilder(Files.newBufferedReader(path))
    .withCSVParser(parser)
    .build();

try {
    reader.readNext();

    while ((line = reader.readNext()) != null) {
        Bean bean = new Bean();
        bean.setValue1(line[0]);
        bean.setValue2(line[1]);
        ...
        bean.setValueN(line[n]);
    }
} catch (CsvValidationException | IOException e1) {
    e1.printStackTrace();
} catch (CsvDataTypeMismatchException | CsvConstraintViolationException e) {
    e.printStackTrace();
}