压缩编解码器在Python中是如何工作的?

压缩编解码器在Python中是如何工作的?,python,gzip,python-2.x,bzip2,Python,Gzip,Python 2.x,Bzip2,我正在使用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)) 但是,我的输出文件的大小为1409780。在该文件上运行bunzip2

我正在使用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))
但是,我的输出文件的大小为1409780。在该文件上运行
bunzip2
会生成一个大小为943634的文件,在该文件上运行
bzip2
会生成一个大小为217275的文件。换句话说,未压缩的文件比使用Python的bzip编解码器压缩的文件小得多除了在命令行上运行
bzip2
之外,还有其他方法可以解决此问题吗?

我尝试了Python的gzip编解码器(将行更改为
codecs.open(archive_file,'a+','zip')
),看看它是否解决了这个问题。我仍然得到大文件,但是当我试图解压缩文件时,我也得到了一个
gzip:archive\u file:not in gzip格式的错误那里发生了什么事?



编辑:我最初是在附加模式下打开文件的,而不是在写入模式下。虽然这可能是问题,也可能不是问题,但如果文件是以“w”模式打开的,问题仍然存在

问题是由于使用了附加模式,导致文件包含多个压缩数据块。看看这个例子:

>>> 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'
这里发生的是解压需要一个压缩流。您必须检查规范,以了解多个连接流的官方行为,但根据我的经验,它们处理第一个流,而忽略其余数据。Python就是这样做的


我想bunzip2也在做同样的事情。因此,实际上您的文件是压缩的,并且比它包含的数据小得多。但是当您通过bunzip2运行它时,您只会返回您写入它的第一组记录;其余部分被丢弃。

我不确定这与编解码器的方式有多大区别,但如果您使用gzip模块中的GzipFile,您可以增量附加到该文件,但除非您一次写入大量数据(可能大于1KB),否则压缩效果不会很好。这就是压缩算法的本质。如果您正在写入的数据不是非常重要(即,如果进程死亡,您可以处理丢失数据的问题),那么您可以编写一个缓冲GzipFile类,该类包装导入的类,以写出更大的数据块。

问题似乎是在每个
write()
上都写入了输出。这会导致每一行在其自己的bzip块中被压缩


在将其写入文件之前,我会尝试在内存中构建一个更大的字符串(如果您担心性能的话,也可以创建一个字符串列表)。一个好的拍摄尺寸应该是900K(或更大),因为这是bzip2使用的块大小

正如其他海报所指出的,问题是库没有使用增量编码器来编码数据;相反,它将馈送到
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,请坚持每个文件使用一个流。

为什么要打开文件进行附加?这会逐渐从数据库中修剪记录并将其保存到存档文件中,因此存档文件会逐渐增长,直到从机器上复制下来。首先,大小差异是运行一次程序的结果。使用“w”运行它会生成与“a+”完全相同的文件,比未压缩版本大30%左右。其次,即使Python不读取第一个压缩数据块,但“bunzip2”会读取。