使用data.zip解析Clojure中的XML时发生OutOfMemoryError
我想使用Clojure从wiktionaryxml转储中提取标题 我使用使用data.zip解析Clojure中的XML时发生OutOfMemoryError,xml,clojure,out-of-memory,Xml,Clojure,Out Of Memory,我想使用Clojure从wiktionaryxml转储中提取标题 我使用head-n10000>out-10000.xml创建原始monster文件的较小版本。然后我用一个文本编辑器进行修剪,使其成为有效的XML。我根据里面的行数重命名了文件(wc-l): 以下是XML结构的概述: <mediawiki> <page> <title>dictionary</title> <revision> <id
head-n10000>out-10000.xml
创建原始monster文件的较小版本。然后我用一个文本编辑器进行修剪,使其成为有效的XML。我根据里面的行数重命名了文件(wc-l
):
以下是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>
我的代码是否有问题?或者这可能是我正在使用的库中的一个bug或限制?基于REPL实验,我使用的代码似乎很懒。在底层,Clojure使用一个SAXXML解析器,因此这本身不应该是问题所在
另见:
xml/parse
中一次读入内存,
早在你打电话给伯爵之前。并且clojure.xml
使用~lazy-SAX
解析器生成一个渴望的具体集合。惰性地处理XML
需要做的工作比你想象的要多得多——这将是你的工作
但是,不要用任何魔法clojure.xml
来帮助你。请随便反驳
通过调用(count(xml/parse data which))
总之,即使在使用zip/xml-zip
之前,此xml/parse
会导致文件足够大的outofmemory错误
:
(count (xml/parse filename))
目前,我正在探索其他XML处理选项。在我的列表顶部,如中所述。看一下,它似乎并不完全是懒惰的:
注意(应用向量子项)
,它将子项
序列具体化为一个向量(尽管它没有具体化整个子代树,所以它仍然是惰性的)。如果一个节点有大量的子节点(例如,
的子节点),那么即使是这一级别的惰性也不够--:内容也需要是一个seq
我对拉链的了解非常有限,所以我不确定这里为什么使用vector
;查看将(关联节点:内容(和子节点(应用向量子节点)))
替换为(关联节点:内容子节点)
是否有效,这样应该将子节点保持为正常序列,而不会具体化它
(就此而言,我不知道为什么(应用向量子项)
而不是(向量子项)
。)
看起来它正在构建*contents*
中的所有内容元素,因此OOM的源可能在内容处理程序本身中
我不确定我们如何协调拉链接口(树状)与您想要的流媒体。它适用于大型xml,但不适用于大型xml
在其他语言(例如Python)中的类似方法中,树是像zipper一样迭代构建的。不同之处在于,在成功处理元素后,树将被修剪
例如,在使用iterparse的Python中,您将在页面
上侦听endElement事件(即当XML中出现
时)。此时,您知道您有一个完整的页面元素,可以将其作为树进行处理。完成后,删除刚刚处理的元素和控制内存使用的同级分支
也许您也可以在这里采用这种方法。xml拉链提供的节点是xml/element
的变量。内容处理程序可以返回一个函数,该函数在调用时对其*当前*
变量进行清理。然后你可以叫它来修剪这棵树
或者,您可以在clojure中将SAX“手动”用于根元素,并在遇到每个页面元素时为其创建一个拉链。这是拉链数据结构的一个限制。拉链设计用于高效导航各种树,支持在树层次结构中上/下/左/右移动,并在几乎恒定的时间内进行就地编辑
从树中的任何位置,拉链都需要能够重新构造原始树(应用编辑)。为此,它将跟踪当前节点、父节点以及树中当前节点左右两侧的所有同级节点,大量使用持久数据结构
您正在使用的过滤器函数从节点最左边的子节点开始,一个接一个地工作到右边,同时测试谓词。最左边的子对象的拉链从其左边的同级对象的空向量开始(请注意源代码中的:l[]
部分)。每次向右移动时,它都会将最后访问的节点添加到左侧同级的向量中(:l(conj l node)
in)。当您到达最右边的子级时,您已经建立了树中该级别中所有节点的内存向量,对于像您这样的宽树,这可能会导致OOM错误
作为一种解决方法,如果您知道顶级元素只是一个包含
元素列表的容器,我建议使用拉链在页面元素中导航,只需使用map
来处理页面:
(defn titles
"Extract titles from +filename+"
[filename]
(let [xml (xml/parse filename)]
(map #(xml-> (zip/xml-zip %) :title text)
(:content xml))))
因此,基本上,我们避免对整个xml输入的顶层使用zip抽象,从而避免将整个xml保存在内存中。这意味着,对于更大的xml,其中每个第一级的子级都是巨大的,我们可能不得不在xml结构的第二级中再次跳过拉链,以此类推……不确定向量是否严格必要,但我认为这不是OOM错误的原因。向量用于生成节点函数,该函数仅在以某种方式编辑拉链时调用。这里似乎不是这样。啊,是的。应该发现
(count (xml/parse filename))
(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))
(defn titles
"Extract titles from +filename+"
[filename]
(let [xml (xml/parse filename)]
(map #(xml-> (zip/xml-zip %) :title text)
(:content xml))))