我之前在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中真的很新,并没有遇到问题。
答案 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