在ruby中创建大文件xml

 2021-04-09 

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)

在这种情况下,使用:indent => true并没有多大区别,但是对于更复杂的XML文件,可能会有所不同。

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