我之前在 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-title
, revision-user
和 revision-title
函数,因为它们只是简单地从页面或修订哈希中的特定位置获取数据。任何人都可以帮我解决这个问题——我是 Clojure 的新手,没有遇到这个问题。
最佳答案
为了清楚起见,(:content (data.xml/parse rdr :coalescing false))
是懒惰。如果您不相信,请检查它的类或拉出第一个项目(它会立即返回)。
也就是说,在处理大序列时需要注意几件事:捕获头部,以及未实现/嵌套的懒惰。我认为您的代码受到后者的影响。
这是我的建议:
1) 添加 (dorun)
到最后->>
调用链。这将迫使序列完全实现,而无需捕获头部。
2) 更改 for
在 process-page
至 doseq
.您正在向文件吐口水,这是一种副作用,您不想在这里懒惰地这样做。
正如 Arthur 所建议的那样,您可能希望打开一个输出文件一次并继续写入,而不是为每个 Wikipedia 条目打开和写入(吐出)。
更新 :
这是一个重写,它试图更清楚地分离关注点:
(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 - Clojure 中的巨大文件和 Java 堆空间错误,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/9983983/