Как работают кодеки сжатия в Python? - PullRequest
8 голосов
/ 29 сентября 2010

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

Мой код выглядит так:

log_file = codecs.open(archive_file, 'w', 'bz2')
for id, f1, f2, f3 in cursor:
    log_file.write('%s %s %s %s\n' % (id, f1 or 'NULL', f2 or 'NULL', f3))

Однако мой выходной файл имеет размер 1 409 780.Запуск bunzip2 для файла приводит к файлу с размером 943,634, а запуск bzip2 для этого файла дает размер 217,275.Другими словами, несжатый файл значительно меньше, чем файл, сжатый с помощью кодека Python bzip. Есть ли способ исправить это, кроме запуска bzip2 в командной строке?

Я попробовал кодек gzip Python (изменив строку на codecs.open(archive_file, 'a+', 'zip')), чтобы проверить, исправлено ли оноэта проблема.Я по-прежнему получаю большие файлы, но я также получаю ошибку gzip: archive_file: not in gzip format при попытке распаковать файл. Что там происходит?


EDIT : У меня изначально был файл, открытый в режиме добавления, а не в режиме записи.Хотя это может или не может быть проблемой, вопрос все еще остается, если файл открыт в режиме 'w'.

Ответы [ 4 ]

2 голосов
/ 30 сентября 2010

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

Ирония в том, что в Python уже встроен совершенно разумный инкрементный кодер bz2.Нетрудно создать «файловый» класс, который автоматически выполняет правильные действия.

import bz2

class BZ2StreamEncoder(object):
    def __init__(self, filename, mode):
        self.log_file = open(filename, mode)
        self.encoder = bz2.BZ2Compressor()

    def write(self, data):
        self.log_file.write(self.encoder.compress(data))

    def flush(self):
        self.log_file.write(self.encoder.flush())
        self.log_file.flush()

    def close(self):
        self.flush()
        self.log_file.close()

log_file = BZ2StreamEncoder(archive_file, 'ab')

Предупреждение : в этом примере я открыл файл в режиме добавления;добавление нескольких сжатых потоков в один файл прекрасно работает с bunzip2, но сам Python не может с этим справиться (хотя - это патч для него).Если вам нужно прочитать сжатые файлы, которые вы создаете, обратно в Python, придерживайтесь одного потока на файл.

1 голос
/ 30 сентября 2010

Кажется, проблема в том, что вывод записывается на каждый write().Это приводит к тому, что каждая строка сжимается в своем собственном блоке bzip.

Я бы попытался построить в памяти строку намного большего размера (или список строк, если вас беспокоит производительность), прежде чем записывать ее в файл.Хороший размер для съемки будет 900K (или больше), так как это размер блока, который использует bzip2

0 голосов
/ 30 сентября 2010

Я не уверен, насколько это отличается от способа кодеков, но если вы используете GzipFile из модуля gzip, вы можете постепенно добавлять файл, но он не будет сжиматься очень хорошо, если вы не пишете большое количество данные за один раз (может быть> 1 КБ). Это просто природа алгоритмов сжатия. Если данные, которые вы пишете, не очень важны (то есть вы можете потерять их, если ваш процесс умирает), тогда вы можете написать буферизованный класс GzipFile, оборачивающий импортированный класс, который записывает большие куски данных.

0 голосов
/ 29 сентября 2010

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

>>> import codecs
>>> with codecs.open("myfile.zip", "a+", "zip") as f:
>>>     f.write("ABCD")

В моей системе получается файл размером 12 байт. Посмотрим, что в нем содержится:

>>> with codecs.open("myfile.zip", "r", "zip") as f:
>>>     f.read()
'ABCD'

Хорошо, теперь давайте сделаем еще одну запись в режиме добавления:

>>> with codecs.open("myfile.zip", "a+", "zip") as f:
>>>     f.write("EFGH")

Размер файла теперь составляет 24 байта, а его содержимое:

>>> with codecs.open("myfile.zip", "r", "zip") as f:
>>>     f.read()
'ABCD'

Что здесь происходит, так это то, что unzip ожидает один сжатый поток. Вам придется проверить спецификации, чтобы увидеть, каково официальное поведение с несколькими объединенными потоками, но по моему опыту они обрабатывают первый и игнорируют остальные данные. Вот что делает Python.

Я ожидаю, что bunzip2 делает то же самое. Таким образом, в действительности ваш файл сжат и намного меньше, чем содержащиеся в нем данные. Но когда вы запускаете его через bunzip2, вы получаете только первый набор записей, которые вы записали в него; остальное отбрасывается.

...