我对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万输入行,内存转储具有以下大小:
因此,使用较少的令牌也会减少使用内存,但对于简单的语法,我建议编写自己的解析器,因为它不是那么难以加上你可以节省大量的内存从ANTLR开销中使用。
答案 0 :(得分:2)
根据你的语法,我将假设你的输入使用ASCII字符。如果您将文件以UTF-8格式存储在磁盘上,那么只需将文件加载到使用UTF-16的ANTLRInputStream
中,就会消耗220MB。除此之外,您每CommonToken
(我最后检查过)的开销大约为48字节,还有来自DFA缓存和ParserRuleContext
实例的开销。
获得Java应用程序使用的内存的准确图片的唯一方法是通过分析器,而在64位模式下,并非所有分析器都能正确地考虑压缩OOP对象存储(但是YouKit会这样做)。首先要尝试的是增加允许的堆大小。一旦您了解了使用内存的特定数据结构,您就可以将该区域作为缩减目标。