Как обрабатывать большие двоичные данные в Clojure?

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

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

(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 нельзя рассматривать как последовательность, так как это крайне неэффективно.

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

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


person qertoip    schedule 21.08.2010    source источник


Ответы (1)


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

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

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

Следующее может работать (непроверено), но будет зависеть от реализации write-bin-file в ленивой манере:

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

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

person mikera    schedule 21.08.2010
comment
Спасибо! Лень сделала свое дело, как вы предложили. Подводя итог, можно сказать, что обработка бинарных файлов в Clojure: ** Использование ленивости относительно просто и занимает мало памяти, но очень неэффективно для ЦП. Ключ в том, чтобы никогда не осознавать всю последовательность. ** Использование loop/recur + aset/aget/areduce/amap является обязательным, требует меньше памяти и намного быстрее. Вероятно, это правильный способ сделать это. В моем конкретном случае, я думаю, следует реализовать areduce. - person qertoip; 22.08.2010