用Ruby创建大文件XML

13

我想将大约50MB的数据写入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 MB。
Builder花费了约123秒,内存消耗足够稳定,大约为10MB。
那么,在Ruby中有更好的解决方案来编写大型XML文件(50 MB)吗?
这是使用Nokogiri的代码:
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

重新解析:Nokogiri非常用户友好,但当速度至关重要时,我会选择编写一个SAX解析器(在nogokiri中也可用)。我有一个方便的实用程序类,可以使用它来快速构建我从xml中需要的内容数组(前提是xml相当简单)https://gist.github.com/854726,否则我可能需要编写自定义的saxparser。 - sunkencity
你误解了我的意思。我想要从数组(活动记录)构建XML。 - Gaurav Shah
这是对“我发现nokogiri(1.5.0)宝石是最有效的解析”的评论,我的观点是解析的最有效方法是直接使用saxparser api。 - sunkencity
1个回答

16

解决方案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

但是随着内存的增加,它会达到600 MB..这太不正常了,不是吗? - Gaurav Shah
因为如果你想让它运行得更快,你需要让它做更少的事情。像构建器这样使用嵌套块会有很多性能开销和各种魔法。在解决方案2中要运行的代码非常少,因此它比较快100倍。 - sunkencity
4
另一种处理 XML 的方法是使用 erb 模板 products.xml.erb,并在其中循环。 - sunkencity
我的XML结构根据请求变化很大,所以ERB似乎不是一个好的选择,但是没错,你是对的。 - Gaurav Shah
将500000增加到5000000时,libxml崩溃了...即使系统有6GB的内存..只是提供一些信息。 - Gaurav Shah
显示剩余2条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接