【问题标题】:opencsv vs java split commaopencsv vs java拆分逗号
【发布时间】:2013-11-05 08:07:46
【问题描述】:

多年来,我一直在使用以下逻辑在夜间批处理作业中解析 csv 文件,没有问题。无论如何,我正在对应用程序进行全面重写,现在我想知道使用 opencsv 之类的东西是否有任何性能/质量提升?我对其他图书馆没有经验,所以我希望其他有经验的人能加入。

while ((line = br.readLine()) != null) {
    String[] items = line.split(",");


        for (int i = 0; i < items.length; ++i) {
            // Remove extra quote
            if (items[i].length > 2) {
                items[i] = items[i].replaceAll("\"", "");
            }

            // Replace blank items with nulls
            if (items[i].matches("^\\s*$")) {
                items[i] = null;
            }

        }

        String item0 = item[0];
        String item1 = item[1];
}

【问题讨论】:

    标签: java parsing csv opencsv


    【解决方案1】:

    您不会获得任何性能,但库将帮助您处理嵌入逗号的字段。 Microsoft 使用双引号而不是转义逗号的令人讨厌的解决方案是手动处理的痛苦,而 opencsv 将为您处理所有这些。

    【讨论】:

    • 非常好的一点,双引号确实很难处理。我还注意到,在阅读了 api 之后,您可以将属性变量的字符串数组传递到 csv 解析器,然后将解析的对象转换为您的实体对象。我真的很喜欢这个功能,我认为它有助于创建单个动态解析器​​。
    • 读取 Csv 将是“IO Bound”,因此运行时间可能不会有太大改善。但是您当前的方法涉及每行多次传递(并使用正则表达式);一个编写良好的 Csv 解析器应该只需要通过每一行(并且希望更少的 CPU)。减少 CPU 使用率永远不会造成任何伤害。
    【解决方案2】:

    chrylis 给出的答案是正确的,您可能不会获得性能,但是 opencsv 会为您处理所有情况。
    但是,如果您真的担心性能,那么对您的代码稍作调整可以帮助您提高性能,
    在分析 String.Split 的代码后,

        public String[] split(String regex) {
           return split(regex, 0);
        }
        public String[] split(String regex, int limit) {
               return Pattern.compile(regex).split(this, limit);
        }
    

    为您的每个字符串编译一个新模式,Pattern.compile 的代码是

    public static Pattern compile(String regex, int flags) {
         return new Pattern(regex, flags);
     }
    

    上面创建Pattern对象的代码再次重复,

    items[i].matches("^\\s*$")
    

    因此,如果您的文件有数百万行,那么创建数百万个 Pattern 对象可能会产生开销,因此您可以将代码更改为,

        Pattern pat = Pattern.compile(","); 
        Pattern regexPattern = Pattern.compile("^\\s*$");       
        while ((line = br.readLine()) != null) 
        {
            String[] items = pat.split(line, 0);
            for (int i = 0; i < items.length; ++i) 
            {           
                if (items[i] != null && items.length > 2) // I think it should be items[i].length() > 2 
                { //We can also remove this null check as splitted strings will never be null
                    items[i] = items[i].replaceAll("\"", "");
                }               
                if (regexPattern.matcher(items[i]) .matches()) {
                    items[i] = null;
                }
            }           
        }
    

    性能提升在小文件中不可见,但对于大文件,如果对数百万个文件执行相同的代码,您将看到显着的性能提升。

    【讨论】:

    • 感谢 dbw,是的,我处理了数百万行,所以任何改进都会有所帮助。目前,我能够使用 8 个内核每两分钟处理大约 100 万行。我知道目前这纯粹是学术性的,但考虑到我正在完全重写,现在是看看还有什么可以减少开销的好时机。
    • @George 我相信 line items.length > 2 应该是 items[i].length() > 2
    • @George 我们还可以从 if (items[i] != null && ...) 中删除空检查,因为拆分后的字符串永远不会为空,
    • 我刚刚在我的问题中修复了该代码,以防万一有人复制它。我不想在他们的项目中引入一个错误。过去一个小时我一直在玩 opencsv,我必须说到目前为止我真的很喜欢它。特别是这里提供的标题答案stackoverflow.com/questions/13505653/… 我只关心它的效率,但我真的很喜欢我能够将解析器库移动到数据库而不是 20 / 30 个单独文件的事实。对性能有什么想法吗?
    • 我刚刚使用包括插入和更新、生产文件、数据库连接、1031936 条记录以及在我的笔记本电脑上通过 i7 在 104772 上进行多线程处理的近生产数据对 opencsv 进行了基准测试。它看起来 opencsv 使用相同的测试快了大约 15 秒。我相信这只不过是你指出的我的低效代码。
    【解决方案3】:

    要添加到您的选项,请考虑使用 Jackson CsvMapper。

    我在 macbook pro 上使用 jackson CsvMapper 在 12 分钟内从大约 4k 个文件中解析出 3600 万行。那是在某些地方使用它直接映射到 POJO,并使用它在其他地方每行读取 Object[],并应用大量辅助处理来规范化输入。

    它也很容易使用:

    作为对象[]

        CsvMapper mapper = new CsvMapper();
        mapper.enable(CsvParser.Feature.WRAP_AS_ARRAY);
        File csvFile = new File("input.csv"); // or from String, URL etc
        MappingIterator<Object[]> it = mapper.reader(Object[].class).readValues(csvFile);
    

    作为 POJO

        public class CSVPerson{
          public String firstname;
          public String lastname;
          //etc
        }
    
        CsvMapper mapper = new CsvMapper();
        CsvSchema schema = CsvSchema.emptySchema().withHeader().withColumnSeparator(delimiter);
        MappingIterator<CSVPerson> it = = mapper.reader(CSVPerson).with(schema).readValues(input);
        while (it.hasNext()){
          CSVPerson row = it.next();
        }
    

    我一直在歌颂这个图书馆,它很棒。它也非常灵活。

    【讨论】:

    • 我从来没有考虑过杰克逊,我将不得不更深入地研究它。我假设你是多线程来实现这个数字?我用的是同一台机器。
    • 不,但这个数字不包括持久化到数据库,这是我的(到目前为止未解决的)瓶颈所在。值得尝试针对您的其他解决方案 imo。这个关于 split() 的答案也可能对您有所帮助:stackoverflow.com/questions/19356021/…
    • 我刚刚检查过。我可以使用 CsvMapper 将行解析为 POJO,比使用 Split() 解析的速度更快
    • 昨天在 mac book pro 上使用 opencsv 和 8 个线程时,我能够在大约 20 分钟内将超过 1000 万行保存到数据库中。该表由大约 20 列以及几个需要查找的连接表组成。我遇到的唯一问题是与多线程相关的数据问题,与本主题完全无关。
    • 我认为我的问题是我将行粘贴到 Mongo 的单个实例中。不幸的是,多线程在那里无济于事,对我来说,解析是快速的,即使使用 SSD 写入磁盘也是缓慢的。无论如何,如果您有兴趣,我写了标记器基准:demeranville.com/…
    猜你喜欢
    • 1970-01-01
    • 2017-08-24
    • 1970-01-01
    • 2015-03-07
    • 2010-12-17
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多