2011-11-12 2 views
1

Я читаю о том, как ленивые последовательности могут вызывать OutOfMemoryError при использовании, скажем, цикла/повтора на больших последовательностях. Я пытаюсь загрузить 3MB-файл из памяти, чтобы обработать его, и я думаю, что это происходит со мной. Но я не знаю, есть ли идиоматический способ исправить это. Я попытался вставить doall's, но тогда моя программа, похоже, не закончилась. Малые входы работают: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) 

Как я уже говорил выше, когда я вставлял команды в кучу мест, он, похоже, не заканчивался. Как я могу заставить это работать для больших файлов, и есть ли способ избавиться от когнитивной нагрузки на то, что мне нужно делать? Некоторые правила?

+0

Обратите внимание, что мне нужно в конечном счете прочитать байты, а не символы. Или, скорее, я в конечном счете пытаюсь получить подписанный номер из каждых 16 бит. Я собирался взять эти пары и превратить их в отдельные числа во время моего следующего прохода с картой. Вероятно, есть лучший способ сделать это ... – MarkL4

+0

Вот подробное обсуждение, которое может иметь значение: (http://programming-puzzler.blogspot.com/2009/01/laziness-in-clojure-traps-workarounds.html) – MarkL4

+0

Как уменьшить когнитивную нагрузку - попробуйте написать как можно меньше своего собственного кода, используя обширные встроенные модули Clojure и библиотеку. 'group-by-twos' действительно большой, но на самом деле это не так.Также '(if (= (count a-list) 2) true false)' является верным способом сказать '(= (count a-list) 2)'. –

ответ

2

Вы читаете файл полностью в памяти, а затем создаете 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)))))) 

Примечание: Я на самом деле не пробовал, но я надеюсь, что это дает вам представление по крайней мере о ленивом файле чтение части

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 - последовательности - дорогостоящие). Если вы можете прочитать все данные в массиве, сделайте это. Затем обработайте его, убедившись, что вы не держите ссылку на любую главу последовательности, чтобы обеспечить сбор мусора во время процесса.

Также строки намного больше, чем примитивы символов. Строка одиночного символа равна 26 байтам, а char - 2 байта. Даже если вам не нравятся массивы, аррайалист в несколько раз меньше последовательности или вектора.