2012-04-02 27 views
6

Tôi đã đăng trước đây trên huge XML file - đó là một XML 287GB với Wikipedia dump Tôi muốn ot đưa vào tệp CSV (sửa đổi tác giả và dấu thời gian). Tôi đã làm được điều đó cho đến một thời điểm nào đó. Trước khi tôi nhận được lỗi StackOverflow, nhưng bây giờ sau khi giải quyết vấn đề đầu tiên tôi nhận được: java.lang.OutOfMemoryError: lỗi vùng nhớ Java.Tệp lớn trong Clojure và lỗi không gian heap Java

Mã của tôi (một phần lấy từ Justin Kramer câu trả lời) trông như thế:

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

Tôi không hiển thị article-title, revision-userrevision-title chức năng, bởi vì họ chỉ đơn giản là lấy dữ liệu từ một nơi cụ thể trong trang hoặc băm sửa đổi. Bất cứ ai cũng có thể giúp tôi với điều này - Tôi thực sự mới trong Clojure và không nhận được vấn đề.

Trả lời

4

Chỉ cần được rõ ràng, (:content (data.xml/parse rdr :coalescing false)) LÀ lười biếng. Kiểm tra lớp học của nó hoặc kéo mục đầu tiên (nó sẽ trở lại ngay lập tức) nếu bạn không bị thuyết phục.

Điều đó nói rằng, một vài điều cần lưu ý khi xử lý chuỗi lớn: giữ đầu, và không thực hiện/lồng nhau lười biếng. Tôi nghĩ rằng mã của bạn bị hậu quả.

Đây là những gì tôi khuyên bạn nên:

1) Thêm (dorun) đến cuối của chuỗi ->> của cuộc gọi. Điều này sẽ buộc các chuỗi được thực hiện đầy đủ mà không cần giữ lên đầu.

2) Thay đổi for trong process-page thành doseq. Bạn đang nhổ vào một tập tin, đó là một tác dụng phụ, và bạn không muốn làm điều đó một cách uể oải ở đây.

Như Arthur đề xuất, bạn có thể muốn mở một tệp đầu ra một lần và tiếp tục viết cho nó, thay vì mở & viết (nhổ) cho mỗi mục nhập Wikipedia.

CẬP NHẬT:

Dưới đây là một viết lại mà cố gắng để tách mối quan tâm rõ ràng hơn:

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

tôi không thấy bất cứ điều gì ở đây có thể gây ra sử dụng quá nhiều bộ nhớ.

+1

Điểm về dorun có thể được làm rõ hơn một chút đối với người mới dùng Clojure: chức năng mở tệp như được hiển thị trong câu hỏi trả về chuỗi kết quả cuộc gọi tới trang xử lý và khi chức năng được gọi từ repl, in trình tự làm cho tất cả các kết quả được giữ trong bộ nhớ cùng một lúc. Gọi dorun trên kết quả làm cho các phần tử của chuỗi được đánh giá và không được trả về, do đó không bao giờ cần phải có tất cả các kết quả trong bộ nhớ cùng một lúc. –

+0

Thanx để giải thích! Tôi hiểu (hy vọng) bây giờ làm thế nào sự lười biếng hoạt động trong đoạn mã này và thay đổi những gì bạn đề xuất, nhưng vẫn 'OutOfMemoryError: không gian heap Java'. Tôi đang làm việc trên một mẫu 1GB của tập tin cuối cùng, nhưng nó vẫn đá lỗi bộ nhớ. Sẽ thực sự biết ơn vì sự giúp đỡ nào. – trzewiczek

+0

Xem cập nhật mới nhất của tôi. Nếu bạn vẫn gặp lỗi OutOfMemory, tôi không chắc tại sao. Tôi đã sử dụng mã rất giống với điều này mà không có vấn đề về bộ nhớ. –

1

Thật không may data.xml/parse không phải là lười biếng, nó cố đọc toàn bộ tệp vào bộ nhớ và sau đó phân tích cú pháp.

Thay vào đó hãy sử dụng this (lazy) xml library chỉ giữ phần hiện đang hoạt động trong ram. Sau đó bạn sẽ cần phải cấu trúc lại mã của bạn để viết đầu ra khi nó đọc đầu vào thay vì thu thập tất cả xml, sau đó xuất nó ra.

dòng của bạn

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

sẽ tải tất cả xml vào bộ nhớ và sau đó yêu cầu chìa khóa nội dung từ nó. mà sẽ thổi đống.

một phác thảo thô của một câu trả lời lười biếng sẽ giống như thế này:

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

Có sự kiên nhẫn, làm việc với (> data ram) luôn cần có thời gian :)

+0

Ông ấy đã sử dụng 'data.xml' từ contrib , như bạn chỉ ra, là lười biếng. –

Các vấn đề liên quan