我在Ruby中处理一个巨大的JSON文件时遇到问题。我要寻找的是一种在不将太多数据保存在内存中的情况下逐条处理它的方法。
我以为gem会做这个工作,但它会消耗我所有的记忆。我还查看了yajl-ruby和json:stream gems,但其中明确指出:
对于较大的文档,我们可以使用IO对象将其流式传输到
解析器。我们仍然需要空间来存放解析的对象,但是文档
它本身永远不会被完全读取到内存中。
以下是我对Yajl所做的:
file_stream = File.open(file, "r")
json = Yajl::Parser.parse(file_stream)
json.each do |entry|
entry.do_something
end
file_stream.close
内存使用率不断提高,直到进程终止。
我不明白为什么Yajl在内存中保存处理过的条目。我能以某种方式释放它们吗,还是只是误解了yajl解析器的功能?
如果不能使用yajl来完成:在Ruby中有没有通过任何库实现这一点的方法?
最佳答案
问题
json=yajl::parser.parse(文件流)
当您像这样调用yajl::parser时,整个流将被加载到内存中以创建数据结构。别那么做。
解决方案
yajl提供了Parser#parse_chunk、Parser#on_parse_complete和其他相关方法,使您能够在流上触发分析事件,而无需立即分析整个IO流。有关如何使用分块的自述文件。
自述文件中给出的示例是:
或者假设您没有访问包含JSON数据的IO对象,而是一次只能访问其中的块。没问题!
(假设我们在EventMachine::Connection实例中)
def post_init
@parser = Yajl::Parser.new(:symbolize_keys => true)
end
def object_parsed(obj)
puts "Sometimes one pays most for the things one gets for nothing. - Albert Einstein"
puts obj.inspect
end
def connection_completed
# once a full JSON object has been parsed from the stream
# object_parsed will be called, and passed the constructed object
@parser.on_parse_complete = method(:object_parsed)
end
def receive_data(data)
# continue passing chunks
@parser << data
end
或者,如果不需要对其进行流式处理,则在完成后,它只会从解析中返回构建的对象。注意:如果输入中有多个JSON字符串,则必须指定一个块或回调,因为这是Yajl Ruby在解析输入时如何向您(调用方)传递每个对象的方式。
obj = Yajl::Parser.parse(str_or_io)
不管怎样,一次只能解析JSON数据的一个子集。否则,您只需在内存中实例化一个巨大的哈希,这正是您描述的行为。
如果不知道数据的外观以及JSON对象的组成方式,就不可能给出比这更详细的解释;因此,您的里程可能会有所不同。然而,这至少能让你指向正确的方向。