【问题标题】:Parse/read Large XML file with minimal memory footprint以最小的内存占用解析/读取大型 XML 文件
【发布时间】:2014-02-05 11:43:49
【问题描述】:

我有一个非常大的 XML 文件 (300mb),格式如下:

<data>
 <point>
  <id><![CDATA[1371308]]></id>
  <time><![CDATA[15:36]]></time>
 </point>
 <point>
  <id><![CDATA[1371308]]></id>
  <time><![CDATA[15:36]]></time>
 </point>
 <point>
  <id><![CDATA[1371308]]></id>
  <time><![CDATA[15:36]]></time>
 </point>
</data>

现在我需要阅读它并遍历 point 节点,为每个节点做一些事情。目前我正在像这样使用 Nokogiri:

require 'nokogiri'
xmlfeed = Nokogiri::XML(open("large_file.xml"))
xmlfeed.xpath("./data/point").each do |item|
  save_id(item.xpath("./id").text)
end

但这不是很有效,因为它会解析所有内容,因此会产生巨大的内存占用(几 GB)。

有没有办法分块做呢?如果我没记错的话可能会被称为流媒体?

编辑

使用 nokogiris sax 解析器的建议答案可能没问题,但是当每个 point 中有多个节点我需要从中提取内容并以不同方式处理时,它会变得非常混乱。我宁愿一次访问一个point,处理它,然后继续下一个“忘记”前一个,而不是返回大量条目以供以后处理。

【问题讨论】:

标签: ruby xml-parsing


【解决方案1】:

如果你使用jruby,你可以利用vtd-xml,它是内存模型中效率最高的,比DOM效率高3~5倍..

http://vtd-xml.sf.net

【讨论】:

    【解决方案2】:

    鉴于这个鲜为人知(但很棒)gist 使用 Nokogiri 的阅读器界面,您应该能够做到这一点:

    Xml::Parser.new(Nokogiri::XML::Reader(open(file))) do
      inside_element 'point' do
        for_element 'id' do puts "ID: #{inner_xml}" end
        for_element 'time' do puts "Time: #{inner_xml}" end
      end
    end
    

    应该有人把它变成宝石,也许是我;)

    【讨论】:

    【解决方案3】:

    使用Nokogiri::XML::SAX::Parser(事件驱动解析器)和Nokogiri::XML::SAX::Document

    require 'nokogiri'
    
    class IDCollector < Nokogiri::XML::SAX::Document
      attr :ids
    
      def initialize
        @ids = []
        @inside_id = false
      end
    
      def start_element(name, attrs)
        # NOTE: This is simplified. You need some kind of stack manipulations
        #                           (push in start_element / pop in end_element)
        #    to correctly pick `.//data/point/id` elements.
        @inside_id = true if name == 'id'
      end
      def end_element(name)
        @inside_id = false
      end
    
      def cdata_block string
        @ids << string if @inside_id
      end
    end
    
    collector = IDCollector.new
    parser = Nokogiri::XML::SAX::Parser.new(collector)
    parser.parse(File.open('large_file.xml'))
    p collector.ids # => ["1371308", "1371308", "1371308"]
    

    根据the documentation

    Nokogiri::XML::SAX::Parser: 是一个 SAX 风格的解析器,它读取它的 必要时输入。

    如果您需要对文件输入进行更多控制,也可以使用Nokogiri::XML::SAX::PushParser

    【讨论】:

    • 嗯好吧,这是一种方法——但它真的是最简单的吗?我本来希望事情不那么“复杂”...
    • @NielsKristian,我不知道更简单的方法。我希望其他人提出更好的解决方案。
    • @NielsKristian, end_element 可能会被删除,如果您将start_element 定义替换为def start_element(name, attrs); @inside_id = name == 'id' end
    • 有道理。我想知道是否有任何不错的宝石可以将这种功能包装在更方便的 DSL 中
    • @NielsKristian,如果您不介意使用 Python,请参阅使用 lxml 的 this answer
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2016-10-28
    • 1970-01-01
    • 1970-01-01
    • 2011-05-09
    • 1970-01-01
    • 2012-03-01
    相关资源
    最近更新 更多