2010-01-04 62 views
4

我想在Ruby中使用libxml讀取包含超過一百萬個小型書目記錄(如<article>...</article>)的大型XML文件。我已經試過Reader類與expand方法一起按記錄讀取記錄,但我不確定這是否是正確的方法,因爲我的代碼吃掉了內存。因此,我正在尋找一個配方如何方便地記錄內存使用情況。下面是我的主循環:使用libxml-ruby塊按塊處理大型XML文件

File.open('dblp.xml') do |io| 
     dblp = XML::Reader.io(io, :options => XML::Reader::SUBST_ENTITIES) 
     pubFactory = PubFactory.new 

     i = 0 
     while dblp.read do 
     case dblp.name 
      when 'article', 'inproceedings', 'book': 
      pub = pubFactory.create(dblp.expand) 
      i += 1 
      puts pub 
      pub = nil 
      $stderr.puts i if i % 10000 == 0 
      dblp.next 
      when 'proceedings','incollection', 'phdthesis', 'mastersthesis': 
      # ignore for now 
      dblp.next 
      else 
      # nothing 
     end 
     end 
    end 

這裏的關鍵是,dblp.expand讀取整個子樹(像<article>記錄),並把它作爲參數傳遞給工廠進行進一步的處理。這是正確的方法嗎?

在工廠方法中,我使用高級類XPath表達式來提取元素的內容,如下所示。再次,這是可行的嗎?

def first(root, node) 
    x = root.find(node).first 
    x ? x.content : nil 
end 

pub.pages = first(node,'pages') # node contains expanded node from dblp.expand 
+0

只是一小步:在x86上的OS X 10.6和x86上的Debian Linux上進行了更多的Ruby 1.8.7測試後,我在讀取XML文件時遇到了兩臺機器上的seg故障。我想這個錯誤源於libxml-ruby,但到目前爲止我沒有追蹤它。非常令人失望。 – 2010-01-12 20:10:27

+0

https://github.com/amolpujari/reading-huge-xml – 2012-07-14 06:57:55

回答

5

在處理大的XML文件,你應該使用流解析器,以避免加載在內存中的一切。有兩種常用的方法:

  • 推解析器像SAX,你的反應,你讓他們encoutered標籤(見tadman答案)。
  • 拉出解析器,在那裏你控制,你可以用簡單的原語像移動XML文件在「光標」上去/下去等

我認爲,推解析器很好用,如果你想檢索只是一些領域,但他們通常是混亂的使用複雜的數據提取,並經常實施使用case... when...構造

拉解析器在我看來是基於樹的模型和推解析器之間的一個很好的選擇。您可以在Dobb博士關於使用REXML解析器的雜誌中找到nice article

+0

感謝您的指針。 'XML :: Reader'實際上是一個基於遊標的拉解析器,它使用'next'進行高級操作,並且可以使用'expand'讀取整個子樹。我的代碼正在工作,只是它泄漏了內存,我懷疑這是由於對如何在大文件上使用它的一些基本誤解造成的。任何XML :: Reader專家想評論? – 2010-01-04 21:24:50

1

處理XML時,兩個常用選項是基於樹和基於事件的。基於樹的方法通常會讀取整個XML文檔並可能消耗大量內存。基於事件的方法不使用額外的內存,除非您編寫自己的處理程序邏輯,否則不會執行任何操作。

SAX風格的解析器和派生實現使用基於事件的模型。

例如用REXML:http://www.iro.umontreal.ca/~lapalme/ForestInsteadOfTheTrees/HTML/ch08s01.html

REXML:http://ruby-doc.org/stdlib/libdoc/rexml/rdoc/index.html

+1

我知道基於樹的與基於流的解析。根據API文檔,XML :: Reader分析流並建模一個遊標。後者由'next'和'expand'提前。但是,該文檔沒有一個很好的例子來說明如何將它用於大文件。 – 2010-01-04 21:12:23

+1

例子總是一個問題,是的。我更喜歡基於樹的解析器,它們通常更容易使用,但對於像這樣的實例,您被困在使用更多SAXy的東西。好消息是,圍繞SAX方法構建的很多Java代碼示例對於Ruby來說相當便攜。不過,看起來範型有更好的解決方案。 – tadman 2010-01-05 18:35:25

0

我有同樣的問題,但我想我通過調用Node#remove來解決它!在展開的節點上。在你的情況,我認爲你應該做這樣的事情

 
my_node = dblp.expand 
[do what you have to do with my_node] 
dblp.next 
my_node.remove! 

真的不知道爲什麼這個工程,但如果你看看源的libxml :: XML ::卡片閱讀機#擴大,有關於釋放的節點評論。我在猜測,Reader#展開將節點關聯到Reader,並且您必須調用Node#remove!釋放它。

內存使用率並不是很高,即使是這樣的黑客,但至少它並沒有繼續增長。

+1

謝謝。由於內存不足,它仍然不適用於我。但是,在不使用擴展的情況下在下一個循環中讀取文件是行不通的。我懷疑expand方法中有內存泄漏。 – 2010-03-26 20:36:08