问题
我有一个由大量小json对象组成的json文件。现在,如果我尝试通过常规方法解析它,将文件读取到内存然后调用它上面的任何json解析(例如json.parse或Oj.parse)它将消耗我所有的系统可用内存并且不会完成。
我想要什么?
通过流解析它的某种方式,每次完成一个对象时,它都会使用该对象调用一个函数。有了这个,我相信内存使用率会非常低且不变。
我迄今为止取得的成就
我检查了两个宝石(yajl和json-stream)并使用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)
但是这种方法仍然存在问题,@ parser.on_parse_complete仅在数组关闭后触发(因此在解析完json之后)。但另一方面,如果我用每行一个对象格式化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"
}
}
]
答案 0 :(得分:0)
我会忘记规则并采用以下方法:
#!/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
。这是有效且防弹的,假设你担心输入是一个由哈希组成的数组。
希望它有所帮助。