用Python可视化Gzip压缩

2020-09-23 15:40:51

斯蒂芬·布伦南·2020年9月22日不久前,我发现自己想了解gzip。我不一定要学习实现算法,而是只想了解它在特定文件上的执行情况。更具体地说,我想了解文件的哪些部分压缩得很好,哪些不压缩。

可能有现成的工具可以将其可视化,但我没有找到任何东西。因为我知道gzip是在Python标准库中实现的,并且我熟悉Python绘图库,所以我想我应该尝试实现自己的可视化。这篇博客帖子(实际上只是一个木星笔记本)就是结果。

有时,数据分析问题最困难的部分就是找出您想要测量的内容。数据都在那里,而且你有一台可以随意使用的计算机,所以可能性是无穷的。知道要计算什么是很棘手的。在我的例子中,我想了解文件的哪些部分压缩得很好。因此,无论我看到什么,都应该包括文件中沿X轴的位置和沿Y轴的压缩大小,这是有意义的。未压缩的文件只是一条对角线。压缩效果越好,此行就越会停留在未压缩文件的对角线下。

由于Python在标准库中支持gzip,让我们看看如何测量这些X和Y坐标。首先,让我们创建一个包含一些压缩数据的文件。在这种情况下,我最喜欢使用的是从古腾堡计划下载的“爱丽丝漫游仙境”。为简单起见,我们将在命令行上压缩它。

(请注意,代码以‘!’为前缀。通过bash执行-其他所有内容都在Python中执行)。

-rw-r--r--1 Stephen Stephen 171K Sep 22 20:25 alice.txt-rw-r--r--1 Stephen 60K Sep 22 20:25 alice.txt.gz。

Gzip在压缩这方面做得相当不错,比我自己做的要好得多。现在,让我们使用Python只解压缩其中的一小部分,以及在执行过程中消耗了多少原始文件。

在上面的代码中,我们将打开的文件对象保存为压缩,然后将其交给GzipFile。这样,当我们从gzip_file中读取解压缩的数据时,我们将能够使用ell()方法来查看我们在压缩文件中走了多远。

这让人感到失望。我们只读取了100字节,但却需要8212字节的gzip才能给我们提供这些数据?嗯,我们必须考虑到压缩算法需要存储一些数据表来帮助解压缩文件的其余部分,所以我们应该让gzip松弛一些。让我们再这样做几次。

B';是供任何人在任何地方免费使用的,\r\n几乎没有任何限制。你可以';

这感觉不对劲。在读取前100个字节的8212个字节的压缩数据后,需要零字节才能获得下一个100?

根据附带的古腾堡项目许可证条款复制、赠送或\r\n重新使用\r\n随附';

显然,这里正在进行一些缓冲。8212可疑地接近于2的幂的8192(20字节距),因此很可能是普通的缓冲区大小。Python的文件I/O机制负责缓冲,但是我们实际上可以通过禁用缓冲来消除它。

嗯。我们将压缩设置为无缓冲文件,但GzipFile可能有自己的内部缓冲。为了避免这种情况,让我们做一件坏事。我们可以通过修改io.DEFAULT_BUFFER_SIZE来实际设置所有I/O操作的缓冲区大小。如果我们将其设置为一个较小的值,则可以减少缓冲对测量的影响。只是为了好玩,让我们试着将其设置为1。

这似乎更可信。要读取100字节的解压缩数据,gzip必须读取205字节的压缩数据(同样,这可能是由于表和其他头信息)。让我们继续一会儿:

BYTES_UNC=100 for_in range(5):gzip_file。READ(100)BYES_UNC+=100 BYTES_CMP=COMPRESSED。Tell()打印(f';未压缩:{bytes_UNC}/压缩:{bytes_cmp}';)

解压缩:200/压缩:279解压缩:300/压缩:335解压缩:400/压缩:376解压缩:500/压缩:449解压缩:600/压缩:541。

我们可以看到,在读取400个未压缩字节之后,gzip压缩已经赶上了!需要读取376个压缩字节才能得到这400个字节。随着我们的继续,差距继续拉大。

既然我们确信这种方法将为我们提供有趣的数据,那么让我们创建一些函数来获取特定文件的所有这些数据,这样我们就可以将其可视化!

那只是一些清理工作。由于修改缓冲区大小可能会影响我们运行的其他代码,因此最好仅在需要时修改缓冲区大小,并在修改完成后将其重置回其原始值。这可以通过上下文管理器来完成。

Import [email protected] def buffer_size(NewSize=1):old_buffer_size=io。Default_buffer_size io。DEFAULT_BUFFER_SIZE=NewSize尝试:最终屈服:IO。DEFAULT_BUFFER_SIZE=OLD_BUFFER_SIZE with BUFFER_SIZE():print(f';size:{io.DEFAULT_BUFFER_SIZE}';)print(f';size:{io.DEFAULT_BUFFER_SIZE}';)。

现在来看一个检索压缩和未压缩大小的函数。我们可以使用“块大小”作为参数来实现这一点。我们的区块越大,我们拥有的数据点就越少,但代码运行速度会更快。我们使用100作为上面的块大小,这似乎已经足够好了,但是我确实更喜欢一个好的整数,所以我会把它改成64。

将熊猫作为pd def create_compression_curve(filename,chunksize=64)导入:使用buffer_size(1),打开(filename,';rb';)作为fileobj:gf=gzip。GzipFile(fileobj=fileobj)记录=[]读取=0,而True:Data=gf。Read(Chunksize)if len(Data)==0:Break#end of file否则:Read+=len(数据)记录。追加((读取,文件对象。Tell())df=pd。DataFrame(记录,列=[';未压缩';,文件名])返回DF。Set_index(';未压缩)ccurve=创建压缩曲线(';alice.txt.gz';)ccurve。

上面的函数只是以块的形式读取gzip文件,测量我们每次通过压缩文件的距离,并将其添加到“记录”列表中。该列表被转换为Pandas Dataframe,通常用于保存这样的表格数据。我们将“未压缩”列设置为“索引”,因为这就是我们认为的X轴。

结果看起来令人兴奋!我们甚至可以直接从这里开始计划。

#一些样式更改以使绘图更美观将matplotlib.pylot作为PLT导入matplotlib作为MPL PLT。风格。使用(#39;ggploy&39;)mpl。RcParams[';figure.figsize;]=[16,8]ccurve。打印()。

嗯,我得把它给gzip-这是相当一致的。我在这个图中看不到什么有趣的东西,除了gzip数据比未压缩版本(Duh)小。我们可以添加以下内容以使其更明确:

因此,这里的结果似乎平淡无奇得令人眼花缭乱。Gzip压缩得相当好,显然比未压缩的要好。

好吧,让我们试着让事情变得不那么平凡。首先,浏览一下gzip(1)手册页,您会发现它有不同的压缩级别1-9。我会让手册来解释:

-#--FAST--使用指定的数字#最佳调整压缩速度,其中-1或--FAST表示最快的压缩方法(压缩较少),-9或--Best表示最慢的压缩方法(最佳压缩)。默认压缩级别为-6(即,以牺牲速度为代价偏向高压缩)。

导入OS文件=[]作为范围(1,10)中的级别:OS。系统(f';gzip-k-S.gz.{level}-{level}alice.txt';)文件。追加(f';alice.txt.gz.{level}';)Print(f';Created{files[-1]}';)。

在上面,我继续创建了所有不同的压缩级别。现在,我们可以获得所有这些对象的压缩曲线并绘制它们:

这似乎稍微有趣一些。默认压缩级别6似乎选择得很好。超过级别6,文件大小的减少似乎很难注意到。但是,与未压缩行相比,压缩比之间的差异非常小:

好的,我们刚刚看到gzip的压缩级别确实如手册页所描述的那样工作。但归根结底,每件事看起来都像一条线。这并不令人惊讶。人类语言是一个很容易压缩的教科书上的数据例子,而且它是相当一致的。如果我们创建了一个不是那样的文件呢?

我们可以将随机数据(难以压缩)与人类语言相结合来创建文件的区域,这些区域以不同的比率压缩,看看我们会得到什么!

下面,我将使用一系列dd命令创建一个文件,对其进行压缩和打印。

好了!DD IF=爱丽丝。Txt of=特殊。数据bs=4096计数=10!DD IF=/dev/urandom of=Special。数据bs=4096计数=10 Oflag=append conv=notrunc!DD IF=爱丽丝。Txt of=特殊。DATA BS=4096 COUNT=10 OFLAG=APPEND CONV=notrunc SKIP=10!DD IF=/dev/urandom of=Special。数据bs=4096计数=10 Oflag=append conv=notrunc!DD IF=爱丽丝。Txt of=特殊。DATA BS=4096 COUNT=10 OFLAG=APPEND CONV=notrunc SKIP=20!DD IF=/dev/urandom of=Special。数据bs=4096计数=10 Oflag=append conv=notrunc!DD IF=爱丽丝。Txt of=特殊。DATA BS=4096 COUNT=10 OFLAG=APPEND CONV=notrunc SKIP=30!DD IF=/dev/urandom of=Special。数据bs=4096计数=10 Oflag=append conv=notrunc!Gzip-k特价。数据。

10+0记录中的10+0记录输出40960字节(41 kB,40 KiB)复制,0.000172505 s,237MB/S10+0记录输出10+0记录输出40960字节(41 kB,40 KiB)复制,0.000732935 s,55.9MB/S10+0记录输出10+0记录输出40960字节(41 kB,40 KiB)复制,0.000242876 s,169MB/S10+0记录输出40960字节(41 kB,40 KiB)复制,0.000810064 s,50.6 MB/S10+0记录10+0记录输出40960字节(41 kB,40 KiB)拷贝,0.000156087 s,262 MB/S10+0记录输出40960字节(41 kB,40 KiB)。40 KiB)复制,0.000727009秒,56.3MB/S10+0记录,共10+0条记录,共40960字节(41 kB,40 KiB)复制,0.000379906秒,108MB/S10+0记录,10+0记录,共40960字节(41 kB,40 KiB)已复制,0.00074872秒,54.7MB/s。

这里有一些有趣的数据!我知道上面的曲线图看起来只有几个数据点,但它实际上沿着X轴每64个字节就有一个点。该图的分段性质正是由于gzip对两种不同类型的数据执行的明显不同。第一段是文本数据,因此坡度不是很陡。第二段是随机数据,压缩不好,所以坡度很陡。这对所有线段都是交替的。

Initramfs是一个小文件系统,它在操作系统引导后立即加载。它包含必要的驱动程序和配置数据,使您的计算机能够挂载真正的文件系统。它碰巧也是(在我的机器上)gzip压缩的。

谁知道呢。出于好奇心,我做这件事并没有真正的目的。正如你所看到的,除了一些曲折的线条之外,并没有太多的东西来自它。

在Python标准库中还实现了一些其他压缩算法。我可以尝试比较同一文件上的压缩算法,这可能很有趣。

我还可以尝试找到更令人兴奋的压缩文件。一定有什么东西可以创造一条很酷的压缩曲线!

在任何情况下,我都鼓励读者破解这段代码,为自己找到一些有趣的东西。

法律·RSS斯蒂芬·布伦南(Stephen Brennan)的博客采用知识共享署名-ShareAlike 4.0国际许可