Clojure中的巨大文件和Java堆空间错误

时间:2012-04-02 21:18:40

标签: clojure heap inputstream

我之前在huge XML file发布了 - 这是一个287GB的XML,其中包含维基百科转储我想要放入CSV文件(修改作者和时间戳)。我设法做到这一点,直到某一点。在我得到StackOverflow错误之前,但现在在解决了第一个问题之后我得到了:java.lang.OutOfMemoryError:Java堆空间错误。

我的代码(部分取自Justin Kramer的答案)看起来像这样:

(defn process-pages
  [page]
  (let [title     (article-title page)
        revisions (filter #(= :revision (:tag %)) (:content page))]
    (for [revision revisions]
      (let [user (revision-user revision)
            time (revision-timestamp revision)]
        (spit "files/data.csv"
              (str "\"" time "\";\"" user "\";\"" title "\"\n" )
              :append true)))))

(defn open-file
[file-name]
(let [rdr (BufferedReader. (FileReader. file-name))]
  (->> (:content (data.xml/parse rdr :coalescing false))
       (filter #(= :page (:tag %)))
       (map process-pages))))

我没有显示article-titlerevision-userrevision-title函数,因为它们只是从页面中的特定位置或修订哈希中获取数据。任何人都可以帮助我 - 我在Clojure中真的很新,并没有遇到问题。

3 个答案:

答案 0 :(得分:4)

为了清楚起见,(:content (data.xml/parse rdr :coalescing false))很懒惰。如果您不相信,请检查其课程或拉出第一项(它会立即返回)。

也就是说,在处理大型序列时要注意几件事:抓住头部,以及未实现/嵌套的懒惰。我认为你的代码会受到后者的影响。

以下是我的建议:

1)将(dorun)添加到->>来电链的末尾。这将迫使序列完全实现而不会抓住头部。

2)将for中的process-page更改为doseq。你在吐痰到文件,这是副作用,你不想在这里懒散地做。

正如亚瑟建议的那样,您可能需要打开一次输出文件并继续写入,而不是打开和输出文件。为每个维基百科条目写作(吐)。

<强>更新

这是一个重写,试图更清楚地分离问题:

(defn filter-tag [tag xml]
  (filter #(= tag (:tag %)) xml))

;; lazy
(defn revision-seq [xml]
  (for [page (filter-tag :page (:content xml))
        :let [title (article-title page)]
        revision (filter-tag :revision (:content page))
        :let [user (revision-user revision)
              time (revision-timestamp revision)]]
    [time user title]))

;; eager
(defn transform [in out]
  (with-open [r (io/input-stream in)
              w (io/writer out)]
    (binding [*out* out]
      (let [xml (data.xml/parse r :coalescing false)]
        (doseq [[time user title] (revision-seq xml)]
          (println (str "\"" time "\";\"" user "\";\"" title "\"\n")))))))

(transform "dump.xml" "data.csv")

我在这里看不到会导致过多记忆的事情。

答案 1 :(得分:1)

不幸的是data.xml/parse不是懒惰的,它会尝试将整个文件读入内存然后解析它。

而是使用 this (lazy) xml library,它只包含当前在ram中处理的部分。然后,您需要重新构造代码以在输出读取输入时编写输出,而不是收集所有xml,然后输出它。

你的行

(:content (data.xml/parse rdr :coalescing false)

将所有xml加载到内存中,然后从中请求内容密钥。这将打击堆。

懒惰答案的粗略轮廓看起来像这样:

(with-open [input (java.io.FileInputStream. "/tmp/foo.xml")
            output (java.io.FileInputStream. "/tmp/foo.csv"]
    (map #(write-to-file output %)
        (filter is-the-tag-i-want? (parse input))))

有耐心,使用(> data ram)总是需要时间:)

答案 2 :(得分:0)

我不知道Clojure,但在普通的Java中,可以使用基于SAX事件的解析器,如http://docs.oracle.com/javase/1.4.2/docs/api/org/xml/sax/XMLReader.html 不需要将XML加载到RAM