2011-11-12 35 views
1

我正在閱讀關於如何在大序列上使用循環/循環時惰性序列如何導致OutOfMemoryError的問題。我試圖從內存中加載一個3MB的文件來處理它,我認爲這發生在我身上。但是,我不知道是否有一種慣用的方法來解決它。我試圖把doall的,但我的程序似乎並沒有終止。小輸入工作:Clojure OutOfMemoryError

小輸入(文件的內容):AAABBBCCC 正確的輸出:((65 65)(65 66)(66 66)(67 67)(67 67))

代碼:

(def file-path "/Users/me/Desktop/temp/bob.txt") 
;(def file-path "/Users/me/Downloads/3MB_song.m4a") 

(def group-by-twos 
    (fn [a-list] 
    (let [first-two (fn [a-list] (list (take 2 a-list))) 
      the-rest-after-two (fn [a-list] (rest (rest a-list))) 
      only-two-left? (fn [a-list] (if (= (count a-list) 2) true false))] 
     (loop [result '() rest-of-list a-list] 
     (if (nil? rest-of-list) 
      result 
      (if (only-two-left? rest-of-list) 
      (concat result (list rest-of-list)) 
      (recur (concat result (first-two rest-of-list)) 
        (the-rest-after-two rest-of-list)))))))) 

(def get-the-file 
    (fn [file-name-and-path] 
    (let [the-file-pointer 
      (new java.io.RandomAccessFile (new java.io.File file-name-and-path) "r") 
     intermediate-array (byte-array (.length the-file-pointer))] ;reserve space for final length 
     (.readFully the-file-pointer intermediate-array) 
     (group-by-twos (seq intermediate-array))))) 

(get-the-file file-path) 

正如我上面所說的,當我在一堆地方放入doall時,它似乎沒有完成。我如何才能讓它運行大型文件,並且有辦法擺脫我需要做的任何事情的認知負擔?一些規則?

+0

請注意,我確實需要最終讀取字節而不是字符。或者,相反,我最終試圖從每16位中得到一個有符號的數字。在我的下一次地圖傳球中,我打算將這些雙轉成單數。有可能有更好的方法來做到這一點... – MarkL4

+0

這是一個可能相關的徹底討論:(http://programming-puzzler.blogspot.com/2009/01/laziness-in-clojure-traps-workarounds.html) – MarkL4

+0

至於如何減少認知負擔 - 儘量使用Clojure廣泛的內置函數和庫來儘可能少地編寫自己的代碼。 「一羣一羣」真的很大,但它並沒有那麼重要。另外'(if(=(count a-list)2)true false)'是一種詳細的說法(=(count a-list)2)'。 –

回答

2

你正在讀取完全在內存中的文件,然後在這個字節數組上創建一個seq,這並沒有給你帶來懶惰序列的好處,因爲所有需要的數據都已經加載到內存中,而lazy sequence真的意味着產生/需要時生成數據。

你可以做的是建立在使用類似文件內容的序列:

(def get-the-file 
    (fn [file-name-and-path] 
    (let [the-file-pointer 
      (new java.io.RandomAccessFile (new java.io.File file-name-and-path) "r") 
     file-len (.length the-file-pointer)] ;get file len 
     (partition­ 2 (map (fn [_] (.readByte the-file-pointer)) (range file-len)))))) 

注:我還沒有真正嘗試過,但我希望它至少讓你的想法有關懶文件閱讀部分

2

我猜慣用的解決辦法是:

(partition 2 (map int (slurp "/Users/me/Desktop/temp/bob.txt"))) 

這爲完整的文件被加載到內存中不完全懶,但它不應該對問題的工作不太大的文件。然而,分區和映射是懶惰的,所以如果你用緩衝讀取器代替slurp,你會得到一個完全懶惰的版本。

注意:如果文件的大小是奇數,將會吞下最後一個字符。目前還不清楚,如果規模很奇怪,你會期望什麼。如果你想在自己的列表中的最後一個值,你可以使用(partition 2 2 [] ...)

user=> (partition 2 (map int "ABCDE")) 
((65 66) (67 68)) 
user=> (partition 2 2 [] (map int "ABCDE")) 
((65 66) (67 68) (69)) 
+0

啊,分區,謝謝。我應該知道會有內置的東西。我不認爲我可以使用slurp,因爲我的真實文件是二進制的,對吧? – MarkL4

1

大量數據的處理時,請注意Clojure的數據結構。 (典型的Clojure應用比相同的Java應用使用兩到三倍的內存 - 序列是內存昂貴的)。 如果您可以將整個數據讀入數組中,請執行此操作。然後處理它,同時確保您不保留任何序列頭的引用,以確保在此過程中進行垃圾回收。

字符串也比char基元大得多。單個字符串是26個字節,char是2個字節。 即使您不喜歡使用數組,ArrayList也比序列或矢量小几倍。