2012-04-02 8 views
6

Ich gepostet zuvor auf eine huge XML file - es ist ein 287GB XML mit Wikipedia-Dump ich möchte ot in CSV-Datei (Revisionen Autoren und Zeitstempel). Das habe ich bis zu einem gewissen Punkt geschafft. Bevor ich den StackOverflow Error bekomme, bekomme ich nun nach dem Lösen des ersten Problems: java.lang.OutOfMemoryError: Java Heap Space Fehler.Riesige Datei in Clojure und Java Heap-Platz Fehler

Mein Code (zum Teil von Justin Kramer Antwort genommen) sieht so aus:

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

ich nicht zeigen article-title, revision-user und revision-title Funktionen, weil sie einfach nur Daten aus einem bestimmten Ort in der Seite nehmen oder Revisions-Hash. Jeder könnte mir dabei helfen - ich bin wirklich neu in Clojure und bekomme das Problem nicht.

Antwort

4

Nur klar zu sein, (:content (data.xml/parse rdr :coalescing false)) faul. Überprüfen Sie seine Klasse oder ziehen Sie das erste Element (es wird sofort zurück), wenn Sie nicht überzeugt sind.

Das sagte, ein paar Dinge zu beachten, wenn große Sequenzen verarbeitet: Halten Sie auf den Kopf und unrealisierte/verschachtelte Faulheit. Ich denke, dass dein Code darunter leidet.

Hier ist, was ich empfehlen:

1) In (dorun) bis zum Ende der ->> Kette von Anrufen. Dadurch wird die Sequenz vollständig ausgeführt, ohne den Kopf zu halten.

2) Ändern Sie for in process-page zu doseq. Du spuckst auf eine Datei, was ein Nebeneffekt ist, und du willst das hier nicht träge machen.

Wie Arthur empfiehlt, möchten Sie vielleicht einmal eine Ausgabedatei öffnen und weiterschreiben, anstatt & für jeden Wikipedia-Eintrag zu öffnen.

UPDATE:

Hier ist ein Umschreiben, die Bedenken deutlicher zu trennen versucht:

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

Ich sehe hier nichts, die übermäßige Speichernutzung verursachen würde.

+1

Der Punkt über Dorun könnte für jemanden, der neu in Clojure ist, ein wenig klarer gemacht werden: Die Funktion open-file, wie in der Frage gezeigt, gibt die Reihenfolge der Aufrufe von process-pages zurück und beim Aufruf der Funktion repl Die Sequenz bewirkt, dass alle Ergebnisse gleichzeitig im Speicher gehalten werden. Der Aufruf von dorun für das Ergebnis bewirkt, dass die Elemente der Sequenz ausgewertet werden und nil zurückgegeben wird, so dass niemals alle Ergebnisse gleichzeitig im Speicher vorhanden sein müssen. –

+0

Danke für die Erklärung! Ich verstehe (hoffentlich) jetzt, wie die Faulheit in diesem Code-Snippet funktioniert und änderte, was Sie vorgeschlagen haben, aber immer noch "OutOfMemoryError: Java-Heap-Space". Ich arbeite an einem 1 GB-Beispiel der endgültigen Datei, aber es tritt immer noch der Speicherfehler auf. Wäre wirklich dankbar für jede Hilfe. – trzewiczek

+0

Siehe mein letztes Update. Wenn Sie immer noch einen OutOfMemory-Fehler erhalten, bin ich mir nicht sicher warum. Ich habe Code sehr ähnlich verwendet, ohne Speicherprobleme. –

1

Leider data.xml/parse ist nicht faul, es versucht, die gesamte Datei in den Speicher zu lesen und dann zu analysieren.

Verwenden Sie stattdessen die this (lazy) xml library, die nur den Teil enthält, an dem gerade gearbeitet wird. Sie müssen dann Ihren Code neu strukturieren, um die Ausgabe zu schreiben, während sie die Eingabe liest, anstatt alle xml zu sammeln und dann auszugeben.

Ihre Linie

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

werden alle XML-Daten in den Speicher laden und fordern Sie dann den Inhaltsschlüssel von ihm. was den Haufen sprengen wird.

eine grobe Skizze eines faulen Antwort würde wie folgt aussehen:

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

Geduld hat, die Arbeit mit (> data ram) nimmt immer Zeit :)

+0

Er hat schon mit 'data.xml' von contrib , was, wie du bemerkst, faul ist. –