2013-04-29 30 views
2

我想使用Clojure从Wiktionary XML转储中提取标题。使用data.zip解析Clojure中的XML时发生OutOfMemoryError

我使用head -n10000 > out-10000.xml来创建较小版本的原始怪物文件。然后,我用文本编辑器修剪它,使其成为有效的XML。

(def data-9764 "data/wiktionary-en-9764.xml") ; 354K 
(def data-99224 "data/wiktionary-en-99224.xml") ; 4.1M 
(def data-995066 "data/wiktionary-en-995066.xml") ; 34M 
(def data-7999931 "data/wiktionary-en-7999931.xml") ; 222M 

下面是XML结构的概述:

<mediawiki> 
    <page> 
    <title>dictionary</title> 
    <revision> 
     <id>20100608</id> 
     <parentid>20056528</parentid> 
     <timestamp>2013-04-06T01:14:29Z</timestamp> 
     <text xml:space="preserve"> 
     ... 
     </text> 
    </revision> 
    </page> 
</mediawiki> 

这里是我试过的基础上,this answer to 'Clojure XML Parsing'我根据内部行(wc -l)数重命名的文件:

(ns example.core 
    (:use [clojure.data.zip.xml :only (attr text xml->)]) 
    (:require [clojure.xml :as xml] 
      [clojure.zip :as zip])) 

(defn titles 
    "Extract titles from +filename+" 
    [filename] 
    (let [xml (xml/parse filename) 
     zipped (zip/xml-zip xml)] 
    (xml-> zipped :page :title text))) 

(count (titles data-9764)) 
; 38 

(count (titles data-99224)) 
; 779 

(count (titles data-995066)) 
; 5172 

(count (titles data-7999931)) 
; OutOfMemoryError Java heap space java.util.Arrays.copyOfRange (Arrays.java:3209) 

我在做我的代码错了吗?或者,这可能是我正在使用的库中的一个错误或限制?基于REPL实验,似乎我使用的代码是懒惰的。在下面,Clojure使用SAX XML解析器,因此单独应该不是问题。

参见:

更新二零一三年四月三十零日:

我想从Clojure的IRC频道分享一些讨论。我在下面粘贴了一个修改后的版本。 (我删除了用户名,但如果你想贷款,只是让我知道,我会编辑,并给你一个链接)

整个标签在xml/parse一次读入内存, 没过多久你甚至打电话给伯爵。而clojure.xml使用〜懒惰的SAX 解析器来产生一个渴望的具体集合。懒洋洋地处理XML 需要比你想象的多得多的工作 - 并且它会工作 这样做,而不是一些神奇的clojure.xml可以为你做。通过致电(count (xml/parse data-whatever))随时反驳 。

总之,即使使用zip/xml-zip之前,这xml/parse会导致一个足够大的文件的OutOfMemoryError

(count (xml/parse filename)) 

目前,我正在探索其他的XML处理选项。在我的列表顶部是clojure.data.xml,如https://stackoverflow.com/a/9946054/109618所述。

+1

啊,是啊。应该早点发现。你一定要'clojure.data.xml'而不是'clojure.xml' - 过渡应该是很容易的。 – Alex 2013-04-30 15:57:38

回答

3

这是拉链数据结构的限制。拉链设计用于高效地导航各种树木,支持树状层级中的向上/向下/向左/向右移动,并且在接近恒定的时间内进行就地编辑。

从树中的任何位置,拉链需要能够重新构建原始树(应用了编辑)。为此,它会跟踪树中当前节点,父节点以及当前节点左右两侧的所有兄弟,从而大量使用持久数据结构。

您正在使用的过滤器函数从节点的最左侧子节点开始,逐个向右边工作,沿途测试谓词。左侧儿童的拉链从其左手兄弟姐妹的空矢量开始(请注意源zip/down中的:l []部分)。每次向右移动时,它都会将最后一个访问的节点添加到左侧兄弟节点的向量中(:l (conj l node),位于zip/right)。当你到达最右边的孩子时,你已经在树中建立了该层中所有节点的内存中向量,对于像你这样的宽树,可能会导致OOM错误。

作为一种解决办法,如果你知道,顶级元素只是为了<page>元素列表的容器,我建议使用拉链的页面元素内进行导航,只需使用map来处理页面:

(defn titles 
    "Extract titles from +filename+" 
    [filename] 
    (let [xml (xml/parse filename)] 
    (map #(xml-> (zip/xml-zip %) :title text) 
     (:content xml)))) 
1

望着source for xml-zip,它似乎并不像它完全懒:

(defn xml-zip 
    "Returns a zipper for xml elements (as from xml/parse), 
    given a root element" 
    {:added "1.0"} 
    [root] 
    (zipper (complement string?) 
      (comp seq :content) 
      (fn [node children] 
       (assoc node :content (and children (apply vector children)))) 
      root)) 

注意(apply vector children),这是物化children以次为矢量(虽然它并未出现整个后裔树,所以它仍然是懒惰的)。如果您有一个节点的孩子数量非常大(例如,,<mediawiki>的孩子),那么即使这种懒惰水平是不够的 - :content也需要是一个seq。

我的拉链知识是非常有限的,所以我不知道为什么vector在这里被使用;看看是否替换(assoc node :content (and children (apply vector children))))(assoc node :content children)作品,这应该保持children作为一个正常的序列没有实现它。

(对于这个问题,我不知道为什么(apply vector children)代替(vec children) ...)

content-handler看起来是建立全部内容要素,以及在*contents*,所以OOM的来源可能是在内容处理程序本身。

我不确定我们如何协调拉链界面(树状)与您想要的流媒体。它将适用于大型XML,但不是巨大的 xml。

在其他语言的类似方法中(例如,Python的iterparse),像使用拉链一样迭代地构建树。不同之处在于树会在元素处理成功后被修剪。

例如,在使用iterparse的Python中,您将在page(即</page>发生在XML中)上监听endElement事件。此时,您知道您有一个完整的页面元素,您可以将其作为树进行处理。完成后,您将删除刚处理的元素和控制内存使用情况的兄弟分支。

也许你也可以在这里采用这种方法。由xml zipper提供的节点是一个var到xml/element。内容处理程序可能会返回一个函数,该函数在调用时会在其变量*current*上进行清理。然后你可以调用它来修剪树。

或者,您可以在根元素的clojure中“手动”使用SAX,并在您遇到它时为每个元素创建一个拉链。

+0

矢量不知道是否是绝对必要的,但我不认为这是OOM错误的原因。将载体在make-节点功能,当拉链以某种方式被编辑时,其仅称为使用。这似乎并非如此。 – Alex 2013-04-30 14:42:36

相关问题