creating large file xml in ruby
我想将大约50MB的数据写入XML文件。
我发现Nokogiri(1.5.0)在阅读和不书写时解析效率很高。 Nokogiri并不是写XML文件的好选择,因为Nokogiri会将完整的XML数据保存在内存中,直到最终将其写入为止。
我发现Builder(3.0.0)是一个不错的选择,但是我不确定这是否是最好的选择。
我使用以下简单代码尝试了一些基准测试:
1 2 3 4 5 6 7 8 | (1..500000).each do |k| xml.products { xml.widget { xml.id_ k xml.name"Awesome widget" } } end |
Nokogiri大约需要143秒,内存消耗也逐渐增加,最终大约为700 MB。
Builder花费了大约123秒,并且内存消耗足够稳定在10 MB。
那么,有没有更好的解决方案来用Ruby编写巨大的XML文件(50 MB)?
以下是使用Nokogiri的代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | 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的代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | 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 ??
如果速度是您主要关心的问题,那么我将直接使用libxml-ruby:
1 2 3 4 5 | $ time ruby test.rb real 0m7.352s user 0m5.867s sys 0m0.921s |
API非常简单:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | 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) |
在这种情况下,使用
1 2 3 4 5 | $ time ruby test.rb #(with indent) real 0m7.395s user 0m6.050s sys 0m0.847s |
解决方案2
当然,最快的解决方案并不会建立在内存上,而只是手动编写XML,但这会轻易产生其他错误源,例如可能无效的XML:
1 2 3 4 5 | $ time ruby test.rb real 0m1.131s user 0m0.873s sys 0m0.126s |
这是代码:
1 2 3 4 5 6 7 | 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 |