Java CSV语法的ANTLR4侦听器导致大文件的OutOfMemoryError
对于csv文件,我有一个相对简单的ANTLR4语法,它可能包含一个标题行,然后只包含用空格分隔的数据行。 值如下Java CSV语法的ANTLR4侦听器导致大文件的OutOfMemoryError,java,parsing,csv,antlr,antlr4,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 (' ' STRI
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)
方法。在那里,我只需获取令牌并为每行创建一个对象
当加载一个10MB的文件时,这一切都能正常工作。但是当我尝试加载一个110MB大小的文件时,我的应用程序将导致一个OutOfMemoryError:GC开销超过了限制。
我用1GB的RAM运行我的应用程序,在我看来,文件大小应该不是问题
我还尝试用Java本身编写一个解析器,它使用String.split(“”
)。该解析器可以按预期工作,也可以用于110MB的输入文件
为了估计我创建的对象的大小,我只需按照中的建议序列化我的对象。110 MB输入文件的结果大小为86513392字节,这远没有消耗1 GB RAM
所以我想知道为什么ANTLR需要如此多的内存来实现如此简单的语法。有没有办法让我的语法更好,这样ANTLR使用的内存更少
编辑
我通过加载一个有100万行(磁盘上约77MB)的文件进行了更深入的内存分析。对于每一行,我的语法会找到12个标记(每行6个值加上5个空格和一个新行)。如果语法忽略了空格,这可以减少到每行6个标记,但这仍然比自己编写解析器糟糕得多
对于100万个输入行,内存转储的大小如下:
- 以上我的语法:1926MB
- 每行查找六个标记的语法:1591MB
- 我自己编写的解析器:415MB
因此,使用更少的令牌也会导致使用更少的内存,但对于简单的语法,我还是建议编写自己的解析器,因为这并不难,而且您可以从ANTLR开销中节省大量内存使用。根据您的语法,我将假设您的输入使用ASCII字符。如果将文件以UTF-8的形式存储在磁盘上,那么只需将文件加载到使用UTF-16的antlInputStream
,将消耗220MB的空间。除此之外,每个CommonToken
(上次我检查过)大约有48个字节的开销,还有来自DFA缓存和parserContext
实例的开销
获取Java应用程序使用的内存的准确图片的唯一方法是通过探查器,并且在64位模式下,并非所有探查器都正确地考虑了压缩OOP对象存储(尽管YourKit这样做)。首先要尝试的是增加允许的堆大小。一旦您知道了使用内存的特定数据结构,就可以针对该区域进行缩减。谢谢您的回答。我更新了我的问题,我基本上认为它已经解决了。如果想用ANTLR解析大文件,只需要一些内存。仅类CommonToken
和TerminalNodeImpl
就占用了应用程序使用的所有内存的50%以上。