我想在Ruby中使用libxml读取包含超过一百万个小书目记录(如<article>...</article>
)的大XML文件。我已经尝试将Reader类与expand
方法结合使用来按记录读取记录,但我不确定这是正确的方法,因为我的代码占用了内存。因此,我正在寻找一个如何方便地处理记录的配方,并且内存使用量不断增加。下面是我的主循环:
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 :(得分:5)
处理大型XML文件时,应使用流解析器来避免将所有内容加载到内存中。有两种常见的方法:
我认为如果你想只检索一些字段,推送解析器很好用,但它们通常很难用于复杂的数据提取,并且通常使用case... when...
构造实现
在我看来,pull解析器是基于树的模型和推送解析器之间的一个很好的选择。您可以在Dr. Dobb的期刊中找到nice article关于使用REXML的拉解析器。
答案 1 :(得分: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
答案 2 :(得分:0)
我遇到了同样的问题,但我想我通过调用Node#remove来解决它!在展开的节点上。在你的情况下,我认为你应该做像
这样的事情my_node = dblp.expand [do what you have to do with my_node] dblp.next my_node.remove!
不确定为什么会这样,但是如果你看一下LibXML :: XML :: Reader#expand的源代码,就会有关于释放节点的评论。我猜测Reader#expand将节点关联到Reader,你必须调用Node #remove!释放它。
即使有这种黑客攻击,内存使用也不是很好,但至少它没有继续增长。