Создание большого файла XML в Ruby - PullRequest
13 голосов
/ 19 сентября 2011

Я хочу записать приблизительно 50 МБ данных в файл XML.

Я обнаружил, что Nokogiri (1.5.0) эффективен для анализа, когда просто читаешь, а не пишешь. Nokogiri не является хорошим вариантом для записи в файл XML, поскольку он хранит полные данные XML в памяти до тех пор, пока, наконец, не запишет их.

Мне показался хорошим вариантом Builder (3.0.0), но я не уверен, что это лучший вариант.

Я попробовал некоторые тесты со следующим простым кодом:

  (1..500000).each do |k|
    xml.products {
      xml.widget {
        xml.id_ k
        xml.name "Awesome widget"
      }
    }
    end

Nokogiri занимает около 143 секунд, а также потребление памяти постепенно увеличивается и заканчивается около 700 МБ.

Builder занял около 123 секунд, а потребление памяти было достаточно стабильным при 10 МБ.

Так есть ли лучшее решение для записи огромных файлов XML (50 МБ) в Ruby?

Вот код, использующий Нокогири:

require 'rubygems'
require 'nokogiri'
a = Time.now
builder = Nokogiri::XML::Builder.new do |xml|
  xml.root {
    (1..500000).each do |k|
    xml.products {
      xml.widget {
        xml.id_ k
        xml.name "Awesome widget"
      }
    }
    end
  }
end
o = File.new("test_noko.xml", "w")
o.write(builder.to_xml)
o.close
puts (Time.now-a).to_s

Вот код, использующий Builder:

require 'rubygems'
require 'builder'
a = Time.now
File.open("test.xml", 'w') {|f|
xml = Builder::XmlMarkup.new(:target => f, :indent => 1)

  (1..500000).each do |k|
    xml.products {
      xml.widget {
        xml.id_ k
        xml.name "Awesome widget"
      }
    }
    end

}
puts (Time.now-a).to_s

1 Ответ

16 голосов
/ 19 сентября 2011

Раствор 1

Если скорость вас беспокоит, я бы просто использовал libxml-ruby напрямую:

$ time ruby test.rb 

real    0m7.352s
user    0m5.867s
sys     0m0.921s

API довольно прост:

require 'rubygems'
require 'xml'
doc = XML::Document.new()
doc.root = XML::Node.new('root_node')
root = doc.root

500000.times do |k|
  root << elem1 = XML::Node.new('products')
  elem1 << elem2 = XML::Node.new('widget')
  elem2['id'] = k.to_s
  elem2['name'] = 'Awesome widget'
end

doc.save('foo.xml', :indent => false, :encoding => XML::Encoding::UTF_8)

Использование :indent => true не имеет большого значения в этом случае, но для более сложных XML-файлов это может быть.

$ time ruby test.rb #(with indent)

real    0m7.395s
user    0m6.050s
sys     0m0.847s

Решение 2

Конечно, самое быстрое решение, которое не основывается на памяти, - это просто написать XML вручную, но это легко сгенерирует другие источники ошибок, такие как, возможно, неверный XML:

$ time ruby test.rb 

real    0m1.131s
user    0m0.873s
sys     0m0.126s

Вот код:

f = File.open("foo.xml", "w")
f.puts('<doc>')
500000.times do |k|
  f.puts "<product><widget id=\"#{k}\" name=\"Awesome widget\" /></product>"
end
f.puts('</doc>')
f.close
...