我正在阅读在大型序列上使用loop / recur时,懒惰序列如何导致OutOfMemoryError的问题。我正在尝试从内存加载一个3MB的文件来处理它,我想这发生在我身上。但是,我不知道是否有一种惯用的方法来解决它。我尝试输入doall,但是我的程序似乎没有终止。小投入工作:
小输入(文件内容):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)
正如我上面所说,当我在一堆地方放入doalls时,它似乎没有完成。我怎样才能让它运行大文件,有没有办法摆脱做我需要做的事情的认知负担?一些规则?
答案 0 :(得分:2)
你正在内存中完全读取文件,然后在这个字节数组上创建一个seq,它实际上没有给你任何延迟序列的好处,因为所有需要的数据已经加载到内存中,而惰性序列实际意味着生成/在需要时生成数据。
您可以使用以下内容创建文件内容的seq:
(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))))))
注意:我还没有真正尝试过,但我希望它能给你一个至少关于懒文件阅读部分的想法
答案 1 :(得分: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))
答案 2 :(得分:1)
在处理大量数据时要小心clojure数据结构。 (典型的Clojure应用程序使用的内存是同一Java应用程序的两到三倍 - 序列内存很昂贵)。 如果您可以将整个数据读入数组,请执行此操作。然后处理它,同时确保不保留对任何序列头的引用,以确保在此过程中发生垃圾收集。
字符串也比char原语大得多。单个字符串是26个字节,char是2个字节。 即使您不喜欢使用数组,arraylist也比序列或向量小几倍。