Как я могу обрабатывать огромные файлы JSON в виде потоков в Ruby, не потребляя всю память?
У меня возникли проблемы с обработкой огромного файла JSON в Ruby. То, что я ищу,-это способ обработать его запись за записью, не сохраняя слишком много данных в памяти.
Я думал, что yajl-Рубин джем сделал бы работу, но она поглощает всю мою память. Я также посмотрел на Yajl:: FFI и JSON: поток драгоценных камней, но там четко указано:
для больших документов мы можем использовать объект 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 через любой библиотека?
3 ответов
оба ответа @CodeGnome и @A. Rager помогли мне понять решение.
Я закончил создание драгоценного камня в JSON-стример это предлагает общий подход и избавляет от необходимости вручную определять обратные вызовы для каждого сценария.
ваши решения кажутся в JSON поток и yajl-ffi. Есть пример на обоих, которые довольно похожи (они от одного и того же парня):
def post_init
@parser = Yajl::FFI::Parser.new
@parser.start_document { puts "start document" }
@parser.end_document { puts "end document" }
@parser.start_object { puts "start object" }
@parser.end_object { puts "end object" }
@parser.start_array { puts "start array" }
@parser.end_array { puts "end array" }
@parser.key {|k| puts "key: #{k}" }
@parser.value {|v| puts "value: #{v}" }
end
def receive_data(data)
begin
@parser << data
rescue Yajl::FFI::ParserError => e
close_connection
end
end
там он настраивает обратные вызовы для возможных событий данных, которые может испытывать анализатор потока.
учитывая документ json, который выглядит так:
{
1: {
name: "fred",
color: "red",
dead: true,
},
2: {
name: "tony",
color: "six",
dead: true,
},
...
n: {
name: "erik",
color: "black",
dead: false,
},
}
можно было бы поток разбирать его с yajl-ffi что-то вроде этого:
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