Java的优秀和高效CSV/TSV读取器

15

我正在尝试读取大型的CSVTSV(以制表符分隔)文件,其中包含大约1000000行或更多。现在我尝试使用opencsv读取一个包含~2500000行的TSV文件,但它抛出了一个java.lang.NullPointerException异常。它可以处理较小的TSV文件,其中包含~250000行。所以我想知道是否有其他支持读取巨大的CSVTSV文件。你有什么想法吗?

对于所有对我的代码感兴趣的人(我缩短了它,因此Try-Catch显然无效):

InputStreamReader in = null;
CSVReader reader = null;
try {
    in = this.replaceBackSlashes();
    reader = new CSVReader(in, this.seperator, '\"', this.offset);
    ret = reader.readAll();
} finally {
    try {
        reader.close();
    } 
}

编辑:这是我构建InputStreamReader的方法:

private InputStreamReader replaceBackSlashes() throws Exception {
        FileInputStream fis = null;
        Scanner in = null;
        try {
            fis = new FileInputStream(this.csvFile);
            in = new Scanner(fis, this.encoding);
            ByteArrayOutputStream out = new ByteArrayOutputStream();

            while (in.hasNext()) {
                String nextLine = in.nextLine().replace("\\", "/");
                // nextLine = nextLine.replaceAll(" ", "");
                nextLine = nextLine.replaceAll("'", "");
                out.write(nextLine.getBytes());
                out.write("\n".getBytes());
            }

            return new InputStreamReader(new ByteArrayInputStream(out.toByteArray()));
        } catch (Exception e) {
            in.close();
            fis.close();
            this.logger.error("Problem at replaceBackSlashes", e);
        }
        throw new Exception();
    }

2
为什么不使用 BufferedReader 自己读取呢?谢谢。 - Jean Logeart
实际上,我想要一个精心制作的、常用的代码库,我不想重复造轮子,这也是每个人都使用库的原因。但如果没有可用的库,我会自己动手实现。 - Robin
2
如果有那么多行,我会考虑分批处理文件:从文件中读取n行,使用csv进行处理,然后读取下一批等等。 - opi
@opi 这可能是一个解决方案,谢谢。 - Robin
4个回答

16

不要使用CSV解析器来解析TSV输入。如果TSV具有带引号字符的字段,它将会出错。

uniVocity-parsers带有一个TSV解析器。您可以轻松地解析数十亿行数据。

解析TSV输入的示例:

TsvParserSettings settings = new TsvParserSettings();
TsvParser parser = new TsvParser(settings);

// parses all rows in one go.
List<String[]> allRows = parser.parseAll(new FileReader(yourFile));

如果您的输入太大而无法保存在内存中,请执行以下操作:

TsvParserSettings settings = new TsvParserSettings();

// all rows parsed from your input will be sent to this processor
ObjectRowProcessor rowProcessor = new ObjectRowProcessor() {
    @Override
    public void rowProcessed(Object[] row, ParsingContext context) {
        //here is the row. Let's just print it.
        System.out.println(Arrays.toString(row));
    }
};
// the ObjectRowProcessor supports conversions from String to whatever you need:
// converts values in columns 2 and 5 to BigDecimal
rowProcessor.convertIndexes(Conversions.toBigDecimal()).set(2, 5);

// converts the values in columns "Description" and "Model". Applies trim and to lowercase to the values in these columns.
rowProcessor.convertFields(Conversions.trim(), Conversions.toLowerCase()).set("Description", "Model");

//configures to use the RowProcessor
settings.setRowProcessor(rowProcessor);

TsvParser parser = new TsvParser(settings);
//parses everything. All rows will be pumped into your RowProcessor.
parser.parse(new FileReader(yourFile));

声明:我是这个库的作者。它是开源的并且免费(使用Apache V2.0许可证)。


1
你还没有执行 settings.setRowProcessor(rowProcessor) 的设置。 - Saurabh

7

谢谢,我会查看这个库。 - Robin
谢谢。supercsv 处理 2 500 000 行非常不错。 - Robin
3
作为Super CSV开发人员的Robin,我很高兴听到这个消息,不过要公平地对待opencsv,如果你使用reader.readAll()而不是逐行读取并进行一些处理,那么你肯定会遇到(内存)问题。你的replaceBackslashes()方法也可能会遇到问题,因为它将整个文件写入了内存。你的NPE是在关闭其中一个流/读者时发生的吗? - James Bassett
@HoundDog,现在我正在从openCsv转换到superCsv,我对我的决定感到非常满意,因为superCsv似乎有很好的文档和广泛的使用,所以我认为这是正确的决定。你对我的replaceBackslashes()有什么建议?是的,当我试图关闭读取器时发生了NPE。 - Robin
Supercsv也支持TSV文件吗? - andresp
CSVListReader在读取包含单引号或双引号的tsv文件时会出现错误,是否需要进行任何更改? - Saurabh

1

按照 Satish 的建议尝试切换库。如果这没什么帮助,您需要将整个文件拆分为标记并进行处理。

考虑您的 CSV 没有逗号的转义字符。

// r is the BufferedReader pointed at your file
String line;
StringBuilder file = new StringBuilder();
// load each line and append it to file.
while ((line=r.readLine())!=null){
    file.append(line);
}
// Make them to an array
String[] tokens = file.toString().split(",");

然后您可以处理它。在使用令牌之前不要忘记修剪它。


1

我不知道那个问题是否仍然有效,但这是我成功使用的方法。可能还需要实现更多的接口,比如Stream或Iterable:

import java.io.Closeable;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.util.Scanner;

/** Reader for the tab separated values format (a basic table format without escapings or anything where the rows are separated by tabulators).**/
public class TSVReader implements Closeable 
{
    final Scanner in;
    String peekLine = null;

    public TSVReader(InputStream stream) throws FileNotFoundException
    {
        in = new Scanner(stream);
    }

    /**Constructs a new TSVReader which produces values scanned from the specified input stream.*/
    public TSVReader(File f) throws FileNotFoundException {in = new Scanner(f);}

    public boolean hasNextTokens()
    {
        if(peekLine!=null) return true;
        if(!in.hasNextLine()) {return false;}
        String line = in.nextLine().trim();
        if(line.isEmpty())  {return hasNextTokens();}
        this.peekLine = line;       
        return true;        
    }

    public String[] nextTokens()
    {
        if(!hasNextTokens()) return null;       
        String[] tokens = peekLine.split("[\\s\t]+");
//      System.out.println(Arrays.toString(tokens));
        peekLine=null;      
        return tokens;
    }

    @Override public void close() throws IOException {in.close();}
}

实际上,我对SuperCSV非常满意。不过还是谢谢你提供这样一种自然的实现方式。 - Robin

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接