问题
我有一个json文件,它由一个巨大的小json对象数组组成。现在,如果我试图用传统的方法解析它,将文件读取到内存中,然后调用它上的任何json解析(例如json.parse或Oj.parse),它将消耗我所有的系统可用内存,无法完成。
我想要什么
以某种方式通过流解析它,每次它完成一个对象时,它都会用该对象回调一个函数。有了这个,我相信内存使用率会非常低,而且是恒定的。
到目前为止我完成了什么
我检查了两个gem(yajl和json流),并使用yajl找到了以下解决方案:
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
# Parse itself
post_init
connection_complete
@parse << File.read("data.json",2048)
但这种方法仍然存在一个问题,只有在数组关闭后(即json解析完成后)才会触发@parser.on_parse_complete。但另一方面,如果我用每行一个对象来格式化json,它会很好地工作,函数object_parsed会被调用两次,每行一次。
Json样本:
[
{
"v": {
"M0": 2
},
"dims": {
"D371665580_86": "M77",
"D2088848381_86": "M5",
"D372510617_86": "M42"
}
},
{
"v": {
"M0": 2
},
"dims": {
"D371665580_86": "M77",
"D2088848381_86": "M5",
"D372510617_86": "M42"
}
}
]
我会忘记规则,采用以下方法:
#!/usr/bin/env ruby
require 'stringio' # for tests
input = '[{"menu": {
"id": "file",
"value": "File"
}
},
{"menu": {
"id": "file2",
"value": "File2"
}
}]'
io = StringIO.new input # here a file stream is opened
loop.inject(counter: 0, string: '') do |acc|
char = io.getc
break if char.nil? # EOF
next acc if acc[:counter].zero? && char != '{' # between objects
acc[:string] << char
if char == '}' && (acc[:counter] -= 1).zero?
# ⇓⇓⇓ # CALLBACK, feel free to JSON.parse here
puts acc[:string].gsub(/p{Space}+/, ' ')
next {counter: 0, string: ''} # from scratch
end
acc.tap do |result|
result[:counter] += 1 if char == '{'
end
end
#⇒ {"menu": { "id": "file", "value": "File" } }
# {"menu": { "id": "file2", "value": "File2" } }
在这里,我们只是逐字节地读取流,一旦满足非常接近的花括号,我们就会发出puts
。这是高效和防弹的,假设您担心输入是一个由散列组成的数组。
希望能有所帮助。