Clojure和Java堆空间中的大型文件错误
我之前在一个网站上发布过——这是一个287GB的XML,带有Wikipedia转储文件,我想把它放到CSV文件中(修订版、作者和时间戳)。我成功地做到了这一点。在我得到StackOverflow错误之前,但是在解决了第一个问题之后,我得到了:java.lang.OutOfMemoryError:java堆空间错误 我的代码(部分取自Justin Kramer的答案)如下所示:Clojure和Java堆空间中的大型文件错误,clojure,heap,inputstream,Clojure,Heap,Inputstream,我之前在一个网站上发布过——这是一个287GB的XML,带有Wikipedia转储文件,我想把它放到CSV文件中(修订版、作者和时间戳)。我成功地做到了这一点。在我得到StackOverflow错误之前,但是在解决了第一个问题之后,我得到了:java.lang.OutOfMemoryError:java堆空间错误 我的代码(部分取自Justin Kramer的答案)如下所示: (defn process-pages [page] (let [title (article-titl
(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))))
我不显示
文章标题
、修订用户
和修订标题
函数,因为它们只是从页面或修订哈希中的特定位置获取数据。任何人都可以帮我-我对Clojure很陌生,不明白这个问题 不幸的是,data.xml/parse
并不懒惰,它试图将整个文件读入内存,然后对其进行解析
相反,使用仅在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))))
要有耐心,使用(>数据ram)
总是需要时间:)要清楚,(:content(data.xml/parse rdr:coalescing false))
是懒惰的。检查它的等级,如果你不确定的话,拉第一个项目(它会立即返回)
也就是说,在处理大型序列时要注意两件事:抓住头部,以及未实现/嵌套的懒惰。我认为您的代码受到后者的影响
以下是我的建议:
1) 将(dorun)
添加到->
调用链的末尾。这将迫使在不抓住头部的情况下完全实现序列
2) 将过程页面中的更改为doseq
。你在向一个文件吐痰,这是一个副作用,你不想在这里懒洋洋地这样做
正如Arthur所建议的,您可能希望打开一个输出文件一次并继续写入,而不是为每个Wikipedia条目打开并写入(spit)
更新:
这里有一个重写,试图更清楚地分离关注点:
(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")
我在这里没有看到任何会导致过度内存使用的东西。我不知道Clojure,但在普通Java中,可以使用基于SAX事件的解析器,如
这不需要将XML加载到RAM中他已经在使用contrib提供的数据。正如您所指出的,它是懒惰的。对于刚接触Clojure的人来说,关于dorun的一点可以更清楚一些:问题中所示的open file函数返回调用处理页面的结果序列,当从repl调用函数时,打印序列会导致所有结果同时保存在内存中。对结果调用dorun会导致对序列的元素求值,并返回nil,这样就不需要同时将所有结果都存储在内存中。谢谢解释!我现在确实理解(希望如此)这个代码片段中的惰性是如何工作的,并改变了您的建议,但仍然OutOfMemoryError:Java堆空间
。我正在处理最终文件的1GB样本,但它仍然会引发内存错误。非常感谢您的帮助。请参阅我的最新更新。若你们仍然摆脱记忆错误,我不知道为什么。我使用了与此非常相似的代码,没有内存问题。解决问题的思路:同一项上的内存是否总是不足?该项目是否与众不同(例如,非常大,修改很多)?您是否尝试过给JVM更多内存?你确定你没有在任何地方保留任何子字符串(JVM不包含仍在使用的子字符串的GC字符串)?基本上-谢谢你的帮助。A花了更多的时间在它上面,对我来说JVM的调整太复杂了,我尝试使用更多的内存选项,我得到的错误也更多。在我能够正确处理这个问题之前,我可能会花更多的时间在Clojure和JVM上。