2010-08-21 2 views
6

Как обрабатывать большие двоичные файлы данных в Clojure? Предположим, что данные/файлы составляют около 50 МБ - достаточно малы для обработки в памяти (но не с наивной реализацией).Как обрабатывать большие двоичные данные в Clojure?

Следующий код корректно удаляет^M из маленьких файлов, но он бросает OutOfMemoryError для больших файлов (как 6MB):

(defn read-bin-file [file] 
    (to-byte-array (as-file file))) 

(defn remove-cr-from-file [file] 
    (let [dirty-bytes (read-bin-file file) 
     clean-bytes (filter #(not (= 13 %)) dirty-bytes) 
     changed? (< (count clean-bytes) (alength dirty-bytes))] ; OutOfMemoryError 
    (if changed? 
     (write-bin-file file clean-bytes)))) ; writing works fine 

кажется, что Java массивы байтов не может рассматриваться как SEQ как это крайне неэффективна.

С другой стороны, решение с aset, aget и areduce раздутое, некрасиво и необходимо, потому что вы не можете использовать библиотеку последовательности Clojure.

Что мне не хватает? Как обрабатывать большие двоичные файлы данных в Clojure?

ответ

6

Я бы, вероятно, лично использовал aget/aset/areduce здесь - они могут быть настоятельными, но они полезны при работе с массивами, и я не считаю их особенно уродливыми. Если вы хотите обернуть их в хорошую функцию, то, конечно, вы можете :-)

Если вы решили использовать последовательности, то ваша проблема будет заключаться в построении и обходе seq, поскольку для этого потребуется создание и хранение нового объекта seq для каждого байта в массиве. Это, вероятно, ~ 24 байта для каждого байта массива ...

Итак, трюк заключается в том, чтобы заставить его работать лениво, и в этом случае предыдущие объекты будут собираться мусором, прежде чем вы дойдете до конца массив. Однако для выполнения этой работы вам придется избегать ссылки на главу seq, когда вы проходите последовательность (например, с подсчетом).

Следующая может работать (непроверенные), но будет зависеть от записи-бен-файла осуществляется в ленивом-дружески:

(defn remove-cr-from-file [file] 
    (let [dirty-bytes (read-bin-file file) 
     clean-bytes (filter #(not (= 13 %)) dirty-bytes) 
     changed-bytes (count (filter #(not (= 13 %)) dirty-bytes)) 
     changed? (< changed-bytes (alength dirty-bytes))] 
    (if changed? 
     (write-bin-file file clean-bytes)))) 

Примечание это, по существу такой же, как ваш код, но конструирует отдельную ленивую последовательность для подсчета количества измененных байтов.

+1

Спасибо! Лень сделала трюк, как вы предложили. Подводя итог, обработка двоичных файлов в Clojure: ** Использование лени относительно легко и имеет низкий объем памяти, но он очень неэффективен CPU. Ключ должен никогда не реализовывать всю последовательность. ** Использование цикла/recur + aset/aget/areduce/amap является обязательным, имеет низкий объем памяти и намного быстрее. Это, вероятно, «правильный» способ сделать это. В моем конкретном случае, следует реализовать, как я предполагаю. – qertoip

Смежные вопросы