我在Ruby中处理一个巨大的JSON文件时遇到了麻烦。我正在寻找的是一种逐个处理它的方法,而不会在内存中保留太多数据。
我认为yajl-ruby gem会做这项工作,但它会消耗我所有的记忆。我也看了Yajl::FFI和JSON:Stream宝石,但有明确说明:
对于较大的文档,我们可以使用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中使用它?
答案 0 :(得分:5)
json = Yajl :: Parser.parse(file_stream)
当你像这样调用Yajl :: Parser时,整个流被加载到内存中以创建数据结构。不要那样做。
Yajl提供Parser#parse_chunk,Parser#on_parse_complete以及其他相关方法,使您能够在流上触发解析事件,而无需一次解析整个IO流。有关如何使用分块的自述文件contains an example。
自述文件中给出的示例是:
或者假设您无法访问包含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对象是如何组成的情况下,不可能提供比这更详细的解释;因此,您的里程可能会有所不同。但是,这至少应该指向正确的方向。
答案 1 :(得分:3)
@ CodeGnome和@A。 Rager的回答帮助我理解了解决方案。
我最终创建了一个gem json-streamer,它提供了一种通用的方法,并且不需要为每个场景手动定义回调。
答案 2 :(得分:2)
您的解决方案似乎是json-stream和yajl-ffi。这两个例子非常相似(他们来自同一个人):
{
1: {
name: "fred",
color: "red",
dead: true,
},
2: {
name: "tony",
color: "six",
dead: true,
},
...
n: {
name: "erik",
color: "black",
dead: false,
},
}
在那里,他设置了流解析器可以体验的可能数据事件的回调。
给出一个类似的json文档:
def parse_dudes file_io, chunk_size
parser = Yajl::FFI::Parser.new
object_nesting_level = 0
current_row = {}
current_key = nil
parser.start_object { object_nesting_level += 1 }
parser.end_object do
if object_nesting_level.eql? 2
yield current_row #here, we yield the fully collected record to the passed block
current_row = {}
end
object_nesting_level -= 1
end
parser.key do |k|
if object_nesting_level.eql? 2
current_key = k
elsif object_nesting_level.eql? 1
current_row["id"] = k
end
end
parser.value { |v| current_row[current_key] = v }
file_io.each(chunk_size) { |chunk| parser << chunk }
end
File.open('dudes.json') do |f|
parse_dudes f, 1024 do |dude|
pp dude
end
end
&#13;
可以使用yajl-ffi来解析它:
{{1}}