CSV语法的ANTLR4侦听器会导致大文件的OutOfMemoryError

时间:2014-09-25 08:18:48

标签: java parsing csv antlr antlr4

我对csv文件有一个相对简单的ANTLR4语法,它可能包含一个标题行,然后只包含用空格分隔的数据线。 值如下Double Double Int String Date Time,其中Date采用yyyy-mm-dd格式,Time采用hh:mm:ss.xxx格式。

这导致了以下语法:

grammar CSVData;

start       :   (headerline | dataline) (NL dataline)* ;

headerline  :   STRING (' ' STRING)* ;
dataline    :   FLOAT ' ' FLOAT ' ' INT ' ' STRING ' ' DAY ' ' TIME ; //lat lon floor hid day time

NL          :   '\r'? '\n' ;
DAY         :   INT '-' INT '-' INT ; //yyyy-mm-dd
TIME        :   INT ':' INT ':' INT '.' INT ; //hh:mm:ss.xxx
INT         :   DIGIT+ ;
FLOAT       :   '-'? DIGIT* '.' DIGIT+ ;
STRING      :   LETTER (LETTER | DIGIT | SPECIALCHAR)* | (DIGIT | SPECIALCHAR)+ LETTER (LETTER | DIGIT | SPECIALCHAR)* ;

fragment LETTER     :   [A-Za-z] ;
fragment DIGIT      :   [0-9] ;
fragment SPECIALCHAR:   [_:] ;

在我的Java应用程序中,我使用一个扩展CSVDataBaseListener的侦听器,只覆盖enterDataline(CSVDataParser.DatalineContext ctx)方法。在那里,我只需获取标记并为每一行创建一个对象。

加载10 MB的文件时,这一切都按预期工作。但是当我尝试加载110 MB大小的文件时,我的应用程序将导致OutOfMemoryError: GC overhead limit exceeded。 我用1 GB的RAM运行我的应用程序,而且我认为文件大小不应该成为问题。

我还尝试使用String.split(" ")简单地在Java本身编写解析器。此解析器按预期工作,也适用于110 MB输入文件。

为了估计我创建的对象的大小,我只需按照this answer中的建议序列化我的对象。 110 MB输入文件的最终大小为86,513,392字节,远远没有消耗1 GB RAM。

所以我想知道为什么ANTLR需要这么多RAM用于这么简单的语法。有没有办法让我的语法更好,所以ANTLR使用更少的内存?

修改

我通过加载一百万行(磁盘上大约77 MB)的文件进行了更深入的内存分析。对于每一行,我的语法找到12个令牌(每行六个值加上五个空格和一个新行)。如果语法忽略空格,这可以被删除到每行六个标记,但这仍然比自己编写解析器更糟糕。

对于100万输入行,内存转储具有以下大小:

     
  • 我的语法:1,926 MB  
  • 每行发现六个令牌的语法:1,591 MB  
  • 我自编的解析器:415 MB

因此,使用较少的令牌也会减少使用内存,但对于简单的语法,我建议编写自己的解析器,因为它不是那么难以加上你可以节省大量的内存从ANTLR开销中使用。

1 个答案:

答案 0 :(得分:2)

根据你的语法,我将假设你的输入使用ASCII字符。如果您将文件以UTF-8格式存储在磁盘上,那么只需将文件加载到使用UTF-16的ANTLRInputStream中,就会消耗220MB。除此之外,您每CommonToken(我最后检查过)的开销大约为48字节,还有来自DFA缓存和ParserRuleContext实例的开销。

获得Java应用程序使用的内存的准确图片的唯一方法是通过分析器,而在64位模式下,并非所有分析器都能正确地考虑压缩OOP对象存储(但是YouKit会这样做)。首先要尝试的是增加允许的堆大小。一旦您了解了使用内存的特定数据结构,您就可以将该区域作为缩减目标。

相关问题