杰克逊能够解析不同行具有不同模式的CSV文件吗?

3
使用Jackson jackson-dataformat-csv库(CsvSchema、CsvMapper等)有效地解析CSV文件是否可能,即使该文件的不同行具有不同的模式?
我强调“有效”是因为我有非常大的文件(> 100,000,000行)需要解析,应用程序对性能敏感。 如果每一行中的每个列都实例化一个新的Object/String,GC将放弃我。 我希望尽可能使用原语,例如,31作为int返回。
如果可以,建议采取什么方法?
FYI,文件模式如下:ROW_TYPE | ...。 即,每一行的第一列表示列类型,并且对于给定的列类型,模式始终相同。 然后其余列在行之间不同,具体取决于它们的列类型。 例如:
1|"text1a"|2|3|4|true|"text2a"
2|3|"text"
1|"text1b"|5|6|7|false|"text2b"

目前我使用neo4j-csv库。

<dependency>
    <groupId>org.neo4j</groupId>
    <artifactId>neo4j-csv</artifactId>
    <version>2.2-SNAPSHOT</version>
</dependency>

它具有极高的性能并且产生非常少的垃圾。此外,它支持逐列读取条目,并在每次读取时指定类型-虽然更为复杂,但也更加灵活。参考用法如下:

// do once per file
CharSeeker charSeeker = new BufferedCharSeeker(...), bufferSize);
int columnDelimiter = '|';
Extractors extractors = new Extractors();
Mark mark = new Mark();

// do repeatedly while parsing
charSeeker.seek(mark, columnDelimiters))
int eventType = charSeeker.extract(mark, extractors.int_()).intValue();

switch (eventType) {
    case 1: // parse row type 1
            break;
    case 2: // parse row type 2
            break;
...
...
}

我考虑转换的原因是希望减少项目的依赖关系,而且由于我已经使用Jackson进行JSON处理,所以在CSV处理方面也使用它是有意义的(性能/功能等待进一步检验)。

1个回答

1
尽管Jackson没有自动支持按每行基础切换CsvSchema的功能(这意味着您需要进行两阶段处理;首先将其读取或绑定为String[],然后使用ObjectMapper.convertValue()),但可能可以使用现有的多态反序列化支持。这将依赖于列命名上的某些共同点,因此我不知道它是否现实。 假设它能够工作,您需要一个具有与第一列逻辑名称匹配的属性的基类;然后是具有类似匹配属性名称的子类型。 您将在基类上使用@JsonTypeInfo,并使用“name”作为类型ID;并且要么在子类上使用@JsonTypeName,要么使用@JsonSubTypes注释从基类引用。 也就是说,使用通常的Jackson配置。 如果这不起作用,则两阶段处理可能不是一个坏选择。它将导致所有单元格值被读取为不同的对象,但只要它们不被保留(也就是说,您只在内存中保留了一个完整的数据行),短期垃圾通常对GC不会造成太大问题(长期垃圾是昂贵的)。

感谢@StaxMan,但我不确定这是否适合。使用我现在使用的CharSeeker解析器,我在1阶段进行基于行的解析。我将此方法与正则表达式split()进行了比较,即返回一个字符串数组,然后解析这些字符串。生成的垃圾数量约为100倍。每个列的1个字符串加上一个string[]和任何已解析的字符串。结果性能差了近6倍,GC行为也无法预测 - 当您在>> 1,000,000个对象/秒的速度下生成>> 100,000,000个对象时,GC需要对它们进行处理,即使它们是短暂的。 - Alex Averbuch
正则表达式可能会有显著的开销。但我想我只是建议检查每行基本读取为String[]的速度快慢。根据我所做的基准测试,CSV后端似乎与JSON相比没有不合理的开销,这就是为什么我会感到惊讶,如果看到超过2倍的差异。 - StaxMan
您IP地址为143.198.54.68,由于运营成本限制,当前对于免费用户的使用频率限制为每个IP每72小时10次对话,如需解除限制,请点击左下角设置图标按钮(手机用户先点击左上角菜单按钮)。 - StaxMan
“构建大量行的数组”绝对不是我正在做的事情。我正在进行流式处理。但是,由于每次调用MappingIterator.nextValue()都会返回一个的对象,因此会产生大量的对象分配。即使是短暂存在的对象也需要被收集,而收集并不是免费的。此外,通过基准测试和分析,我发现当我以高速解析大型文件时,堆栈非常快地增长(带有垃圾、未使用的对象)。最小化分配可以最小化GC,这就是我想要的。 - Alex Averbuch
1
谢谢!总是很有趣看到高性能的实现,以及解决类似问题的各种方法。 - StaxMan
显示剩余3条评论

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