2012-04-02 12 views
6

Opublikowałem wcześniej na huge XML file - jest to 287GB XML ze zrzutem Wikipedy Chcę ot umieścić w pliku CSV (autorzy wersji i znaczniki czasu). Udało mi się to zrobić do pewnego momentu. Zanim otrzymałem błąd StackOverflow, ale teraz po rozwiązaniu pierwszego problemu otrzymuję: java.lang.OutOfMemoryError: Błąd przestrzeni sterty Java.Ogromny plik w Clojure i Java space space error

Mój kod (częściowo zaczerpnięte z Justinem Kramer odpowiedzi) wygląda tak:

(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)))) 

ja nie wykazują article-title, revision-user i revision-title funkcji, ponieważ po prostu wziąć dane z określonego miejsca na stronie lub hash rewizji. Każdy może mi w tym pomóc - jestem naprawdę nowy w Clojure i nie mam problemu.

Odpowiedz

4

Wystarczy być jasne, (:content (data.xml/parse rdr :coalescing false)) jest leniwy. Sprawdź swoją klasę lub wyciągnij pierwszy element (wróci natychmiast), jeśli nie jesteś przekonany.

To powiedziawszy, kilka rzeczy, na które należy zwrócić uwagę podczas przetwarzania dużych sekwencji: trzymanie głowy i niezrealizowane/zagnieżdżone lenistwo. Myślę, że twój kod cierpi z powodu tego ostatniego.

Oto co polecam:

1) Dodaj (dorun) na końcu łańcucha ->> połączeń. To wymusi pełną realizację sekwencji bez trzymania głowy.

2) Zmień for w process-page na doseq. Plujesz do pliku, który jest efektem ubocznym, i nie chcesz tego robić leniwie tutaj.

Zgodnie z zaleceniami Artura, możesz chcieć otworzyć plik wyjściowy jeden raz i kontynuować pisanie, zamiast otwierać & pisanie (spit) dla każdego wpisu w Wikipedii.

UPDATE:

Oto przepisać który próbuje oddzielić obawy jaśniej:

(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") 

Nie widzę tu nic, że spowodowałoby nadmierne zużycie pamięci.

+1

Punkt o dorun może być nieco jaśniejszy dla kogoś nowego w Clojure: funkcja otwartego pliku, jak pokazano w pytaniu, zwraca sekwencję wyników wywołań do stron procesu, a gdy funkcja jest wywoływana z repliki, drukowanie sekwencja powoduje, że wszystkie wyniki są przechowywane w pamięci w tym samym czasie. Wywołanie doruna na wyniku powoduje, że elementy sekwencji są oceniane, a zero zwracane, dzięki czemu nigdy nie ma potrzeby, aby wszystkie wyniki były w pamięci w tym samym czasie. –

+0

Dziękuję za wyjaśnienie! Rozumiem (mam nadzieję) teraz, jak lenistwo działa w tym fragmencie kodu i zmieniło to, co zaproponowałeś, ale nadal "OutOfMemoryError: Java heap space". Pracuję nad próbką ostatecznego pliku o wielkości 1 GB, ale nadal wykrywa błąd pamięci. Byłbym bardzo wdzięczny za każdą pomoc. – trzewiczek

+0

Zobacz moją najnowszą aktualizację. Jeśli nadal pojawia się błąd OutOfMemory, nie jestem pewien dlaczego. Użyłem kodu bardzo podobnego do tego bez problemów z pamięcią. –

1

Niestety data.xml/parse nie jest leniwy, próbuje odczytać cały plik w pamięci, a następnie przeanalizować go.

Zamiast tego użyj this (lazy) xml library, który przechowuje tylko część, nad którą aktualnie pracuje w pamięci RAM. Będziesz wtedy musiał ponownie skonstruować swój kod, aby zapisać wynik, odczytując dane wejściowe zamiast gromadzenia całego xml, a następnie wyprowadzania go.

linia

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

załaduje cały XML do pamięci, a następnie zażądać od niego klucz zawartości. który wysadzi stertę.

szorstka Zarys leniwe odpowiedź będzie wyglądać mniej więcej tak:

(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)))) 

Miej cierpliwość, pracując z (> data ram) zawsze wymaga czasu :)

+0

On już przy użyciu 'data.xml' z contrib , co, jak pani wskazuje, jest leniwy. –

Powiązane problemy