【问题标题】:Recursion between different methods of the same multimethod同一多方法的不同方法之间的递归
【发布时间】:2011-02-01 19:22:46
【问题描述】:

我正在编写一个 Clojure 库来解析 Mac OS X 的基于 XML 的 property list files。代码可以正常工作,除非你给它一个大的输入文件,此时你会得到java.lang.OutOfMemoryError: Java heap space

这是一个示例输入文件(小到可以正常工作):

<plist version="1.0">
<dict>
    <key>Integer example</key>
    <integer>5</integer>
    <key>Array example</key>
    <array>
        <integer>2</integer>
        <real>3.14159</real>
    </array>
    <key>Dictionary example</key>
    <dict>
        <key>Number</key>
        <integer>8675309</integer>
    </dict>
</dict>
</plist>

clojure.xml/parse 把它变成:

{:tag :plist, :attrs {:version "1.0"}, :content [
    {:tag :dict, :attrs nil, :content [
        {:tag :key, :attrs nil, :content ["Integer example"]}
        {:tag :integer, :attrs nil, :content ["5"]}
        {:tag :key, :attrs nil, :content ["Array example"]}
        {:tag :array, :attrs nil, :content [
            {:tag :integer, :attrs nil, :content ["2"]}
            {:tag :real, :attrs nil, :content ["3.14159"]}
        ]}
        {:tag :key, :attrs nil, :content ["Dictionary example"]}
        {:tag :dict, :attrs nil, :content [
            {:tag :key, :attrs nil, :content ["Number"]}
            {:tag :integer, :attrs nil, :content ["8675309"]}
        ]}
    ]}
]}

我的代码把它变成了 Clojure 数据结构

{"Dictionary example" {"Number" 8675309},
 "Array example" [2 3.14159],
 "Integer example" 5}

我的代码的相关部分看起来像

; extract the content contained within e.g. <integer>...</integer>
(defn- first-content
  [c]
  (first (c :content)))

; return a parsed version of the given tag
(defmulti content (fn [c] (c :tag)))

(defmethod content :array
  [c]
  (apply vector (for [item (c :content)] (content item))))

(defmethod content :dict
  [c]
  (apply hash-map (for [item (c :content)] (content item))))

(defmethod content :integer
  [c]
  (Long. (first-content c)))

(defmethod content :key
  [c]
  (first-content c))

(defmethod content :real
  [c]
  (Double. (first-content c)))

; take a java.io.File (or similar) and return the parsed version
(defn parse-plist
  [source]
  (content (first-content (clojure.xml/parse source))))

代码的核心是content 函数,这是一个在:tag(XML 标记的名称)上调度的多方法。我想知道是否应该做一些不同的事情来使这种递归更好地工作。我尝试用trampoline content 替换对content 的所有三个调用,但这不起作用。我应该做些什么来让这种相互递归更有效地工作吗?还是我采取了根本错误的方法?

编辑:顺便说一句,这个代码是available on GitHub,它可能更容易使用。

【问题讨论】:

    标签: xml recursion clojure


    【解决方案1】:

    您有多个(每个孩子一个)来自单个方法的递归调用,因此您的代码不是(也不能没有繁重的重组)尾递归。 trampoline 用于相互 tail-recursive 函数。

    您的大型 XML 文件有多深、多长?我问是因为你得到的是 OoM 而不是 SO。

    无论如何,要解决递归问题(这不太可能是导致异常的问题),您必须遍历 XML 数据结构(例如使用 xml-zip),同时维护代表结果树的堆栈(向量或列表) ?正在施工?。 具有讽刺意味的是,XML 数据结构的遍历在某种程度上等同于用于构建结构的 sax 事件。

    【讨论】:

    • 我没有听说过 xml-zip,但我会调查一下。谢谢!
    【解决方案2】:

    重递归将导致StackOverflowException,而不是OutOfMemoryError。此外,这里的递归似乎不是很深(根据您的示例中的 XML 文件只有 3 个级别)。

    我的猜测是,OutOfMemoryError 被抛出是因为您的大型 XML 文件被解析成的数据结构太大而无法放入 JVM 堆中。您可以尝试使用-Xms-Xmx 选项增加堆大小。但是,解析大型 XML 文件的正确方法是使用 SAX 事件而不是构建树(DOM 或 Clojure 数据结构)。

    【讨论】:

    • 实际文件当然要大得多,但在示例中,递归仍然没有那么深。我将研究 SAX 事件和 xml-zip,看看什么对这个库最有意义。谢谢!
    猜你喜欢
    • 2013-02-17
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2020-08-31
    • 2010-09-28
    • 1970-01-01
    相关资源
    最近更新 更多