现在流行的linux发行版经常会提供一个新型的压缩工具,叫做XZ Utils。可以去http://distrowatch.com上查看各个发行版本的配置信息。该工具可以得到更高的压缩率。可以做的比其他常见压缩算法要好。
XZ Utils 是为 POSIX 平台开发具有高压缩率的工具。它使用 LZMA2 压缩算法,生成的压缩文件比 POSIX 平台传统使用的 gzip、bzip2 生成的压缩文件更小,而且解压缩速度也很快。最初 XZ Utils 的是基于 LZMA-SDK 开发,但是 LZMA-SDK 包含了一些 WINDOWS 平台的特性,所以 XZ Utils 为以适应 POSIX 平台作了大幅的修改。XZ Utils 的出现也是为了取代 POSIX 系统中旧的 LZMA Utils。
XZ Utils 主要包含了下列部分:
XZ Utils 工具生成的压缩文件扩展名为 .xz (MIME 类型为"application/x-xz")。.xz 文件格式具有下列特点:
基于数据流: 易于通过管道 (pipe) 生成压缩文件或解压缩文件。.xz 文件格式与 .gz/.bz2 文件一样,不具备对多个文件进行归档打包的能力。若要处理多个文件,可以和归档工具 tar 结合使用,生成扩展名为 .tar.xz 或 .txz 的压缩文件。
随机读取: 存储的数据被划分为独立的压缩块,并对每个压缩块进行索引,当每个压缩块比较小时,便能够进行有限的随机读取压缩数据。
完整性验证: 可以使用 CRC32、CRC64、SHA-256 来进行数据的完整性验证,也可以增加自定义验证方法。
可连接(concatenation): 类似于 .gz/.bz2 文件,可以把多个压缩数据流连接到一个文件中。解压缩时,就像解压一个正常单压缩流文件一样。
支持多filter和filter链: 提供自定义 filter 的能力,也能够将多个 filter 组成 filter 链,对数据进行处理。这点与 Unix 命令间使用的管道 (pipe) 类似。
可填充(padding): 可以在 .xz 文件末尾填充二进制'0'以充满特定大小的空间,比如备份磁带上的一个块 (block)。
LZMA2 是 .xz 文件格式使用的压缩算算法。LZMA(Lempel-Ziv-Markov chain-Algorithm) 原本是 Windows 平台上著名压缩工具 7-Zip 中的 .7z 文件格式的默认压缩算法。LZMA 使用类似于 LZ77 的字典编码机制。在大多数情况下,LZMA 算法都能够提供很高压缩率和较快的压缩速度。LZMA2 是 LZMA 的改进版本,相对于 LZMA,LZMA2 增进了 encoder 和 decoder 的实现,并改善了对多线程的支持。
与 POSIX 系统上传统的压缩工具 gzip、bzip2 相比,XZ Utils 吸引人的地方在于,它能够提供更高的压缩率,生成的文件更小,而且解压数据的速度也很快。XZ Utils 的主页上描述:对于典型文件,XZ Utils 能够生成比使用 gzip 小 30%,比使用 bzip 小 15% 的压缩文件。
下面便针对压缩率、压缩速度和解压速度三个方面进行简单的验证。
测试环境的 CPU 为 AMD Athlon X4 3.0GHz,内存为 8G。操作系统为 64 位版本的 Gentoo Linux。所有的测试均在 /dev/shm(即内存设备)下进行,这样尽可能避免了 I/O 读写对测试结果的影响。
测试内容为 gzip、bzip2、xz 分别压缩原始大小为 445M 的 linux-kernel-3.2.1.tar 文件,然后解压缩生成的压缩文件。测试分两组:
分别记录下生成的压缩文件大小,压缩用时,解压用时数据。将所有收集到的数据进行整理。
处理后,使用 gnuplot 工具分别生成了下面的三个柱状比较图。下列柱状图中 X 轴的 "-" 表示使用默认压缩率。
压缩后文件大小的比较如下图:
根据上图,清楚地显示 xz 生成的压缩文件最小,即具有高压缩率。且通过图中比较可以看出,xz 生成的压缩文件大小的确约比 gzip 生成的文件小 30%,比 bzip 生成的文件小 15%。
压缩用时的比较如下图:
由此图可以看到 xz 的解压速度和 gzip 一样有着稳定的表现。大幅少于 bzip2 的解压缩用时,虽比 gzip 用时稍多,但从 xz Utils 在同等条件下比 gzip 拥有更出色高压缩率来看,解压缩用时的表现也是不错的。
从以上的验证数据的对比可以看出:XZ Utils 具有 高压缩率,解压速度快的特点。能够生成更小文件的同时,也能提供稳定快速的解压,在对数据大小比较敏感的场合,比如说大数据的网络传输,文件的备份,处理能力有限的嵌入系统等场合,有着十分广泛的用途。
$ xz --help Usage: xz [OPTION]... [FILE]... Compress or decompress FILEs in the .xz format. -z, --compress force compression -d, --decompress force decompression -t, --test test compressed file integrity -l, --list list information about .xz files -k, --keep keep (don't delete) input files -f, --force force overwrite of output file and (de)compress links -c, --stdout write to standard output and don't delete input files -0 ... -9 compression preset; default is 6; take compressor *and* decompressor memory usage into account before using 7-9! -e, --extreme try to improve compression ratio by using more CPU time; does not affect decompressor memory requirements ...
$ xz test.txt $ ls test.txt* test.txt.xz
$ xz -d -k test.txt.xz $ ls test.txt* test.txt.xz test.txt
$ xz -l index.txt.xz Strms Blocks Compressed Uncompressed Ratio Check Filename 1 1 768 B 1,240 B 0.619 CRC64 index.txt.xz
$ xz -k7 xz_pipe_decomp_mini.c $ xz -k --fast xz_pipe_decomp_mini.c
$ tar cf - *.c | xz --best > src.tar.xz # compress $ xz -d src.tar.xz --stdout | tar -x # decompress
$ tar cJf src.txz *.c # compress $ tar xJf src.txz # decompress
$ xz -H | more
# find /var/log -type f -iname "*.log" -print0 | xargs -P4 -n16 xz -T1
注意:运行此命令须有 root 权限。
$ xzcat concat_1.txt.xz .xz file concatenation test: ~ file1 ~ $ xzcat concat_2.txt.xz .xz file concatenation test: ~ file2 ~ $ cat concat_1.txt.xz concat_2.txt.xz > concat.txt.xz #concatenation $ xz -d concat.txt.xz #decompress $ cat concat.txt #print file concat.txt .xz file concatenation test: ~ file1 ~ .xz file concatenation test: ~ file2 ~
$ xz --list xz_pipe_mini.exe.xz ## normal output Strms Blocks Compressed Uncompressed Ratio Check Filename 2 2 12.8 KiB 42.7 KiB 0.300 CRC64 xz_pipe_mini.exe.xz $ xz --list --robot xz_pipe_mini.exe.xz ## robot model name xz_pipe_mini.exe.xz file 2 2 13112 43695 0.300 CRC64 0 totals 2 2 13112 43695 0.300 CRC64 0 1
下例是利用 --robot 打印出文件压缩率的一个例子:
$ xz -l --robot xz_pipe_mini.exe.xz | gawk \ '/^name/ {printf "%s =>",$2} \ /^totals/ {printf "compressed radio:%.2f%\n", $6 * 100}' xz_pipe_mini.exe.xz =>compressed radio:30.00%
$ xz --check=crc32 --lzma2=preset=6e,dict=64KiB file
$ xz libwx_gtk2u_core-2.8.so.0.8.0 $ du -k libwx_gtk2u_core-2.8.so.0.8.0.xz 916 libwx_gtk2u_core-2.8.so.0.8.0.xz $ xz --x86 --lzma2 libwx_gtk2u_core-2.8.so.0.8.0 $ du -k libwx_gtk2u_core-2.8.so.0.8.0.xz 856 libwx_gtk2u_core-2.8.so.0.8.0.xz
从上面执行结果来看,利用合适的 filter Chain 最终生成的文件大小为 865KB。参数 --x86 --lzma2 就组成了一个 filter Chain。参数 --x86 是指使用 x86 平台的 BCJ filter,适用于 32 位和 64 位系统。参数 --lzma2 则指使用 lzma2 算法进行压缩。
xzcat 命令其相当于 "xz --decompress --stdout" 就是将解压出的数据输出到标准输出 (stdout)。下面的例子用来统计压缩 test.txt.xz 文件所包含数据的行数。
$ xzcat test.txt.xz | wc -l ## count the lines of test.txt
xzgrep 可以用来 grep 压缩文件所包含的数据内容。其内部就是调用 grep 命令,所以参数和 grep 一致。下面的例子打印出 xz_pipe_comp_mini.c.xz 中所有包含以"lzma_"开头单词的行号和行内容。例:
$ xzgrep -P -n "lzma_\w+" xz_pipe_comp_mini.c.xz 8: lzma_check check = LZMA_CHECK_CRC64; 9: lzma_stream strm = LZMA_STREAM_INIT; /* alloc and init lzma_stream struct */ 15: lzma_easy_encoder (&strm, 6, LZMA_CHECK_CRC64); 28: lzma_action action = in_finished ? LZMA_FINISH : LZMA_RUN; 35: lzma_code (&strm, action); /* compress data */ 42: lzma_end (&strm);
这两个命令可以分页查看压缩文件中的内容。简单的来说,相当于
"xzcat <file.xz> | less" 或 "xzcat <file.xz> | more"
这两个命令用于比较两个 .xz 文件。内部分别调用命令 diff 和 cmp 来实现文件内容的比较。例:
$ xzdiff xz_pipe_comp_mini.c.orig.xz xz_pipe_comp_mini.c.xz 20c20 < if (feof (in_file)) --- > if (feof (in_file)) { 21a22 > } $
liblzma API 提供了 LZMA1/LZMA2 算法的实现,并具有类似于 Zlib 的 API 接口。也和 gzip、bzip2 一样 XZ Utils 是流压缩格式。通过 liblzma API 接口,可以相对简单的编程实现对数据的压缩和解压缩。
使用 liblzma 压缩数据的过程,大致过程如下。
lzma_stream strm = LZMA_STREAM_INIT;
lzma_easy_encoder (&strm, 6, LZMA_CHECK_CRC64);
strm.next_in = in_buf; //input buffer address. strm.avail_in = in_len; //data length.
strm.next_out = out_buf; //output buffer address strm.avail_out = 4096; //output buffer length.
lzma_code (&strm, LZMA_RUN); /* compress data */ lzma_code (&strm, LZMA_FINISH); /* Finish operation.*/
lzma_end(&strm);
下面是一段完整的示例程序。所示程序从标准输入 (stdin) 读入数据,压缩后写到标准输出 (stdout). 为了更便于理解压缩过程,示例代码省略了所有的异常处理和一些其他次要细节。
#include <stdio.h> #include <stdint.h> #include <inttypes.h> #include <stdbool.h> #include <lzma.h> void xz_compress (FILE *in_file, FILE *out_file) { lzma_check check = LZMA_CHECK_CRC64; /* alloc and init lzma_stream struct */ lzma_stream strm = LZMA_STREAM_INIT; uint8_t in_buf [4096]; uint8_t out_buf [4096]; bool in_finished = false; /* initialize xz encoder */ lzma_easy_encoder (&strm, 6, LZMA_CHECK_CRC64); while (! in_finished) { /* read incoming data */ size_t in_len = fread (in_buf, 1, 4096, in_file); if (feof (in_file)) { in_finished = true; } strm.next_in = in_buf; strm.avail_in = in_len; /* if no more data from in_buf, flushes the internal xz buffers and * closes the xz data with LZMA_FINISH */ lzma_action action = in_finished ? LZMA_FINISH : LZMA_RUN; /* loop until there's no pending compressed output */ do { strm.next_out = out_buf; strm.avail_out = 4096; lzma_code (&strm, action); /* compress data */ size_t out_len = 4096 - strm.avail_out; fwrite (out_buf, 1, out_len, out_file); /* write compressed data */ }while (strm.avail_out == 0); } lzma_end (&strm); } int main () { xz_compress (stdin, stdout); return 0; }
编译命令 :
$ gcc -g -o xz_pipe_mini xz_pipe_comp_mini.c -llzma
使用例子 :
$ cat a.txt | xz_pipe_mini > a.txt.xz
使用 liblzma 进行解压缩的过程和压缩数据的过程基本一致。主要的区别是使用 lzma_stream_decoder 函数代替 lzma_easy_decoder 函数来进行 decoder 的初始化。
lzma_stream_decoder (&strm, memory_limit, flags);
lzma_stream_decoder 函数的第一参数是数是 lzma_stream 结构体变量的指针。第二参数则指明了在解压缩过程中使用内存的最大值。若 UINT64_MAX 则为不限制内存使用量。第三参数用来指明一些其他 flags 值:
下面是一段完整的示例程序。所示程序从标准输入 (stdin) 读入数据,解压缩的数据写到标准输出 (stdout). 示例代码省略了所有的异常处理和一些其他次要细节。
#include <stdio.h> #include <stdint.h> #include <inttypes.h> #include <stdbool.h> #include <lzma.h> #define IN_BUF_MAX 4096 #define OUT_BUF_MAX 4096 #define RET_OK 0 #define RET_ERROR 1 int xz_decompress (FILE *in_file, FILE *out_file) { /* alloc and init lzma_stream struct */ lzma_stream strm = LZMA_STREAM_INIT; uint8_t in_buf [IN_BUF_MAX]; uint8_t out_buf [OUT_BUF_MAX]; bool in_finished = false; bool out_finished = false; lzma_action action; lzma_ret ret_xz; int ret; ret = RET_OK; /* initialize xz decoder */ ret_xz = lzma_stream_decoder (&strm, UINT64_MAX, LZMA_TELL_UNSUPPORTED_CHECK|LZMA_CONCATENATED ); if (ret_xz != LZMA_OK) return RET_ERROR; while ((! in_finished) && (! out_finished)) { /* read incoming data */ size_t in_len = fread (in_buf, 1, IN_BUF_MAX, in_file); if (feof (in_file)) in_finished = true; strm.next_in = in_buf; strm.avail_in = in_len; /* if no more data from in_buf, flushes the internal xz buffers and closes the decompressed data with LZMA_FINISH */ action = in_finished ? LZMA_FINISH : LZMA_RUN; /* loop until there's no pending decompressed output */ do { /* out_buf is clean at this point */ strm.next_out = out_buf; strm.avail_out = OUT_BUF_MAX; /* decompress data */ ret_xz = lzma_code (&strm, action); if ((ret_xz != LZMA_OK) && (ret_xz != LZMA_STREAM_END)) { out_finished = true; ret = RET_ERROR; } else { /* write decompressed data */ size_t out_len = OUT_BUF_MAX - strm.avail_out; fwrite (out_buf, 1, out_len, out_file); if (ferror (out_file)) { out_finished = true; ret = RET_ERROR; } } } while (strm.avail_out == 0); } lzma_end (&strm); return ret; } int main () { return xz_decompress (stdin, stdout); }
编译命令:
$ gcc -g -o xz_pipe_decomp_mini xz_pipe_decomp_mini.c -llzma
使用例子:
$ cat a.txt.xz | xz_pipe_decomp_mini > a.txt
本文着重从实用的角度介绍了 XZ Utils 的主要部分,还有一些内容由于篇幅的原因,没有提及。如果您想更深入的研究 XZ Utils,可以访问 XZ Utils 的官方网站,那里可以找到最新的源代码和更详尽的文档。
原文转载自:http://www.ibm.com/developerworks/cn/linux/l-lo-xzutils/