Огромный файл в Clojure и ошибка пространства кучи Java - PullRequest
6 голосов
/ 03 апреля 2012

Я публиковал ранее в огромном XML-файле - это 287 ГБ XML-файла с дампом из Википедии, который я не хочу помещать в CSV-файл (авторы ревизий и временные метки). Мне удалось сделать это до определенного момента. Раньше я получал ошибку StackOverflow, но теперь, после решения первой проблемы, я получаю: java.lang.OutOfMemoryError: Ошибка пространства кучи Java.

Мой код (частично взят из ответа Джастина Крамера) выглядит так:

(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 и не понимаю проблемы.

Ответы [ 3 ]

4 голосов
/ 03 апреля 2012

Просто чтобы прояснить, (: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 голос
/ 03 апреля 2012

К сожалению data.xml/parse не ленив, он пытается прочитать весь файл в память и затем проанализировать его.

Вместо этого используйте this (lazy) xml library , которая содержит толькочасть это в настоящее время работает в оперативной памяти.Затем вам нужно будет реструктурировать свой код, чтобы записать вывод, когда он читает ввод, вместо того, чтобы собирать весь 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) всегда требует времени:)

0 голосов
/ 03 апреля 2012

Я не знаю о Clojure, но в простой Java можно использовать парсер, основанный на событиях SAX, например http://docs.oracle.com/javase/1.4.2/docs/api/org/xml/sax/XMLReader.html который не должен загружать XML в RAM

...