xml - 使用 data.zip 在 Clojure 中解析 XML 时出现 OutOfMemoryError

标签 xml clojure out-of-memory

我想使用 Clojure 从 Wiktionary XML 转储中提取标题。

我使用 head -n10000 > out-10000.xml 创建了原始怪物文件的较小版本。然后我用一个文本编辑器修整以使其成为有效的 XML。我根据里面的行数重命名了文件(wc -l):

(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' 尝试过的方法:

(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 解析器,因此单独应该不是问题。

另见:

2013-04-30 更新:

我想分享来自 clojure IRC channel 的一些讨论。我在下面粘贴了一个编辑过的版本。 (我删除了用户名,但如果你想要信用,请告诉我;我会编辑并给你一个链接。)

The entire tag is read into memory at once in xml/parse, long before you even call count. And clojure.xml uses the ~lazy SAX parser to produce an eager concrete collection. Processing XML lazily requires a lot more work than you think - and it would be work you do, not some magic clojure.xml could do for you. Feel free to disprove by calling (count (xml/parse data-whatever)).

总而言之,即使在使用 zip/xml-zip 之前,此 xml/parse 也会导致 OutOfMemoryError 文件足够大:

(count (xml/parse filename))

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

最佳答案

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

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

您正在使用的过滤器函数从节点最左边的子节点开始,然后一个接一个地向右运行,沿途测试谓词。最左边的 child 的 zipper 以其左侧 sibling 的空向量开始(请注意 :l [] 源代码中的 zip/down 部分)。每次向右移动时,它都会将最后访问的节点添加到左侧兄弟节点的向量中(:l (conj l node)zip/right 中)。当你到达最右边的 child 时,你已经建立了树中该级别所有节点的内存向量,对于像你这样的宽树,这可能会导致 OOM 错误。

作为解决方法,如果您知道顶级元素只是 <page> 列表的容器元素,我建议使用 zipper 在页面元素内导航,只需使用 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 结构的第二级再次跳过使用 zipper,等等......

关于xml - 使用 data.zip 在 Clojure 中解析 XML 时出现 OutOfMemoryError,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/16289991/

相关文章:

php - simplexmlelement 上的 json_decode/encode 添加数组而不是空字符串

clojure - Clojure中的惯用语: (drop 1 str) or (rest str)?

clojure - defmethod 捕获所有

android - showAsAction ="never"给菜单项添加图标

xml - 您如何处理 fetchxml 结果数据?

.net - VB6 应用程序调用 .NET DLL OutOfMemory 异常

android - 继续滑动已使用 Glide 将位图加载到其中的 Viewpager 时出现 OutOfMemory 错误

c# - 在 Xamarin.Forms 上加载图像时内存不足

javascript - 我如何创建一个读取 xml 或 json 的 Chrome 扩展程序

maven - 如何将非 Maven 代码添加到 boot-clj/boot 中?