最新消息:阿啰哈,本人90后,目前单身,欢迎妹子们来撩!.(。→‿←。)

使用XZ Utils获得更高的压缩率

关于 XZ Utils

XZ Utils 是为 POSIX 平台开发具有高压缩率的工具。它使用 LZMA2 压缩算法,生成的压缩文件比 POSIX 平台传统使用的 gzip、bzip2 生成的压缩文件更小,而且解压缩速度也很快。最初 XZ Utils 的是基于 LZMA-SDK 开发,但是 LZMA-SDK 包含了一些 WINDOWS 平台的特性,所以 XZ Utils 为以适应 POSIX 平台作了大幅的修改。XZ Utils 的出现也是为了取代 POSIX 系统中旧的 LZMA Utils。

XZ Utils 主要包含了下列部分:

  1. 命令行程序 xz,用来生成和解压缩 .xz 压缩文件。
  2. 一组实用的脚本工具 (xzcat, xdiff, xzgrep 等)提供浏览,查找以及比较 .xz 文件内容等功能。
  3. liblzma 压缩库,提供算法的实现和近似于 ZLIB 的编程接口。
  4. 提供对 LZMA Utils 的一些兼容

xz 文件格式

XZ Utils 工具生成的压缩文件扩展名为 .xz (MIME 类型为"application/x-xz")。.xz 文件格式具有下列特点:

基于数据流: 易于通过管道 (pipe) 生成压缩文件或解压缩文件。.xz 文件格式与 .gz/.bz2 文件一样,不具备对多个文件进行归档打包的能力。若要处理多个文件,可以和归档工具 tar 结合使用,生成扩展名为 .tar.xz 或 .txz 的压缩文件。

随机读取: 存储的数据被划分为独立的压缩块,并对每个压缩块进行索引,当每个压缩块比较小时,便能够进行有限的随机读取压缩数据。

完整性验证: 可以使用 CRC32、CRC64、SHA-256 来进行数据的完整性验证,也可以增加自定义验证方法。

可连接(concatenation): 类似于 .gz/.bz2 文件,可以把多个压缩数据流连接到一个文件中。解压缩时,就像解压一个正常单压缩流文件一样。

支持多filterfilter: 提供自定义 filter 的能力,也能够将多个 filter 组成 filter 链,对数据进行处理。这点与 Unix 命令间使用的管道 (pipe) 类似。

可填充(padding): 可以在 .xz 文件末尾填充二进制'0'以充满特定大小的空间,比如备份磁带上的一个块 (block)。

LZMA2 压缩算法

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 文件,然后解压缩生成的压缩文件。测试分两组:

  1. gzip、bzip2 和 xz 分别使用参数"-1"(最小压缩率)至"-9"(最大压缩率)压缩文件。
  2. 不设定压缩率参数既使用各自的默认压缩率。gzip 和 xz 默认压缩率参数均为"-6",bzip2 默认相应的为"-9"。

分别记录下生成的压缩文件大小,压缩用时,解压用时数据。将所有收集到的数据进行整理。

处理后,使用 gnuplot 工具分别生成了下面的三个柱状比较图。下列柱状图中 X 轴的 "-" 表示使用默认压缩率。

压缩后文件大小的比较如下图:

图 1. 压缩后文件大小比较

图 1. 压缩后文件大小比较

根据上图,清楚地显示 xz 生成的压缩文件最小,即具有高压缩率。且通过图中比较可以看出,xz 生成的压缩文件大小的确约比 gzip 生成的文件小 30%,比 bzip 生成的文件小 15%。

压缩用时的比较如下图:

图 2. 压缩用时数据比较:

图 2. 压缩用时数据比较:

由此图可以看到 xz 随着压缩率的升高,所用压缩时间也会随之增长。且相对于 gzip/bzip2 所用时间大幅增加。在对压缩时间要求相对苛刻的场合,并不推荐使用 XZ Utils。

解压缩用时的比较如下图:

图 3. 解压缩用时数据比较:

图 3. 解压缩用时数据比较:

由此图可以看到 xz 的解压速度和 gzip 一样有着稳定的表现。大幅少于 bzip2 的解压缩用时,虽比 gzip 用时稍多,但从 xz Utils 在同等条件下比 gzip 拥有更出色高压缩率来看,解压缩用时的表现也是不错的。

从以上的验证数据的对比可以看出:XZ Utils 具有 高压缩率,解压速度快的特点。能够生成更小文件的同时,也能提供稳定快速的解压,在对 数据大小比较敏感的场合,比如说大数据的网络传输,文件的备份,处理能力有限的嵌入系统等场合,有着十分广泛的用途。

使用 xz 命令

xz 命令的基本用法

  1. xz 命令帮助信息

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    $ 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
     ...

  2. 压缩一个文件 test.txt,压缩成功后生成 test.txt.xz, 原文件会被删除。

    1
    2
    3
    $ xz test.txt
    $ ls test.txt*
    test.txt.xz

  3. 解压 test.txt.xz 文件,并使用参数 -k 保持原文件不被删除,

    1
    2
    3
    $ xz -d -k test.txt.xz
    $ ls test.txt*
    test.txt.xz test.txt

  4. 使用参数 -l 显示 .xz 文件的基本信息。基本信息包括压缩率、数据完整性验证方式等。也可以和参数 -v 或 -vv 配合显示更详尽的信息。

    1
    2
    3
    $ 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

  5. 使用参数 -0, -1, -2, … -6, … -9 或参数 --fast, --best 设定压缩率。xz 命令的默认为 -6 ,对于大多数系统来说,甚至是一些较旧的系统,-4 … -6 压缩率预设值都不错的表现。

    1
    2
    $ xz -k7 xz_pipe_decomp_mini.c
    $ xz -k --fast xz_pipe_decomp_mini.c

  6. 和 tar 命令配合处理多个文件。一般来说,有两种简单的方法将 tar 和 xz 结合起来,一是使用管道,或是使用 tar 命令的参数'J'。两种方法各自有各自的优点,第一种方法可以充分利用 xz 利用丰富的参数。而第二种方法使用起来更简单容易。例子分别如下

    • 使用管道

      1
      2
      $  tar cf -  *.c  | xz --best > src.tar.xz   # compress
      $  xz -d src.tar.xz --stdout | tar -x        # decompress

    • 使用 tar 参数 "J

      1
      2
      $  tar cJf src.txz  *.c                      # compress
      $  tar xJf src.txz                           # decompress


      "

复杂的用法

  1. 使用参数 -H 显示 xz 命令所有 options. 参数 -H 比使用参数 --help 显示的内容更详细。

    1
    $ xz -H  | more

  2. 借助 xargs 命令并行压缩多文件。下面的命令行可以将 /var/log 目录下所有的扩展名为 .log 的文件压缩。通过 xargs 命令同时运行多个 xz 进行压缩。

    1
    # find /var/log -type f -iname "*.log" -print0 | xargs -P4 -n16 xz -T1

    注意:运行此命令须有 root 权限。

  3. 连接 (concatenation) 多个 .xz 文件。可以把多个压缩数据流连接 (concatenation) 到一个文件中。解压缩时,就像解压一个正常单压缩流文件一样。如下例,两个 .xz 文件 concat_1.txt.xz 和 concat_2.txt.xz, 用 cat 命令将他们合并为文件 concat.xz,解压缩 concat.xz 这个文件会发现 concat_1.txt.xz 和 concat_2.txt.xz 中的内容合并在一起了。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    $ 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 ~

  4. 适于 shell 编程的 Robot 模式。xz 命令使用参数 --robot 输出易于 shell 处理的信息。

    1
    2
    3
    4
    5
    6
    7
    $ 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 打印出文件压缩率的一个例子:

    1
    2
    3
    4
    $ 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%

  5. 自定义 filter Chain. 有时需要一些特殊的设定,以满足一些特定需求。xz 提供了很多 参数自定义压缩细节。下面有两个例子

    • 若希望解压缩使用很少内存,比如在嵌入式系统中解压。可以使用使用参数 -e,并设定压缩使用的字典大小为 64KB。参数 -e (--extreme) 是指使用稍慢的压缩速度以尝试稍稍提高一些压缩率。这样便可在解压缩时使用很少的内存空间。命令如下:

      $ xz --check=crc32 --lzma2=preset=6e,dict=64KiB file

    • 若压缩文件中是可执行的二进制数据时,使用的合适的 BCJ(branch/call/jump) filter 可以提高压缩率。例如 libwx_gtk2u_core-2.8.so.0.8.0 是 wxGTK 库的一个共享库文件,它原始的大小为 3980KB. 若采用默认的参数压缩后的大小为 916KB. 通过相应指定 filter Chain, 可以生成更小些的文件 .

      1
      2
      3
      4
      5
      6
      $ 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 算法进行压缩。

使用 xzgrep,xzcat,xzdiff 等实用工具

xzcat

xzcat 命令其相当于 "xz --decompress --stdout" 就是将解压出的数据输出到标准输出 (stdout)。下面的例子用来统计压缩 test.txt.xz 文件所包含数据的行数。

1
$ xzcat test.txt.xz | wc -l  ## count the lines of test.txt

xzgrep

xzgrep 可以用来 grep 压缩文件所包含的数据内容。其内部就是调用 grep 命令,所以参数和 grep 一致。下面的例子打印出 xz_pipe_comp_mini.c.xz 中所有包含以"lzma_"开头单词的行号和行内容。例:

1
2
3
4
5
6
7
$ 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);

xzless 和 xzmore

这两个命令可以分页查看压缩文件中的内容。简单的来说,相当于

1
"xzcat <file.xz> | less" 或 "xzcat <file.xz> | more"

xzdiff 和 xzcmp

这两个命令用于比较两个 .xz 文件。内部分别调用命令 diff 和 cmp 来实现文件内容的比较。例:

1
2
3
4
5
6
7
8
$ 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

关于 liblzma API

liblzma API 提供了 LZMA1/LZMA2 算法的实现,并具有类似于 Zlib 的 API 接口。也和 gzip、bzip2 一样 XZ Utils 是流压缩格式。通过 liblzma API 接口,可以相对简单的编程实现对数据的压缩和解压缩。

使用 liblzma API 压缩数据

使用 liblzma 压缩数据的过程,大致过程如下。

  1. 定义 lzma_stream 结构体变量并使用 LZMA_STREAM_INIT 初始化,这个 lzma_stream 结构体变量会在整个数据压缩过程中被使用。有点类似于 C 中标准库中文件处理使用的结构体 FILE 。

    1
    lzma_stream strm = LZMA_STREAM_INIT;

  2. 调用 lzma_easy_encoder 函数做压缩准备工作。函数的第一参数是 lzma_stream. 结构体变量的指针,第二参数则指明了期望压缩率大小。有效值为 [0,9], 数字越高压缩率越高。第三参数是指明数据完整性检查方法,LZMA_CHECK_CRC64 可以满足大多数情况需要。当然,还有 LZMA_CHECK_CRC32, LZMA_CHECK_SH256 可供选择,或者使用 LZMA_CHECK_NONE 不进行 数据完整性检查。

    lzma_easy_encoder (&strm, 6, LZMA_CHECK_CRC64);

  3. 通过设定 lzma_stream 结构体变量中的 next_in 和 avail_in 字段,指明待压缩的数据开始 地址和长度。

    1
    2
    strm.next_in  = in_buf;  //input buffer address.
    strm.avail_in = in_len;  //data length.

  4. 通过设定 lzma_stream 结构体变量中的 next_out 和 avail_out 字段,指明存放压缩结果 buffer 地址和长度。

    1
    2
    strm.next_out = out_buf;  //output buffer address
    strm.avail_out = 4096;    //output buffer length.

  5. 然后通过调用 lzma_code 函数来压缩数据,或结束压缩数据。lzma_code 函数有两个参数,第一个参数是 lzma_stream 指针。而第二个参数用来指明 lzma_code 的动作: LZMA_RUN: 进行数据处理; LZMA_FINISH: 结束数据处理。

    1
    2
    lzma_code (&strm, LZMA_RUN);    /* compress data */
    lzma_code (&strm, LZMA_FINISH); /* Finish operation.*/

  6. 最后要调用 lzma_end 函数释放资源,退出。

    1
    lzma_end(&strm);

下面是一段完整的示例程序。所示程序从标准输入 (stdin) 读入数据,压缩后写到标准输出 (stdout). 为了更便于理解压缩过程,示例代码省略了所有的异常处理和一些其他次要细节。

清单 1. 压缩数据的代码示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
#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;
}

编译命令 :

1
$ gcc -g -o xz_pipe_mini xz_pipe_comp_mini.c -llzma

使用例子 :

1
$ cat a.txt | xz_pipe_mini > a.txt.xz

使用 liblzma API 解压数据

使用 liblzma 进行解压缩的过程和压缩数据的过程基本一致。主要的区别是使用 lzma_stream_decoder 函数代替 lzma_easy_decoder 函数来进行 decoder 的初始化。

1
lzma_stream_decoder (&strm, memory_limit, flags);

lzma_stream_decoder 函数的第一参数是数是 lzma_stream 结构体变量的指针。第二参数则指明了在解压缩过程中使用内存的最大值。若 UINT64_MAX 则为不限制内存使用量。第三参数用来指明一些其他 flags 值:

  • LZMA_TELL_NO_CHECK: 如果压缩数据流中未指明数据完整性检查方式,则函数 lzma_code 返回 LZMA_TELL_NO_CHECK 。
  • LZMA_TELL_UNSUPPORTED_CHECK: 如果压缩数据流使用了不被支持的数据完整性检查方式,则函数 lzma_code 会返回 LZMA_TELL_UNSUPPORTED_CHECK 。
  • LZMA_CONCATENATED: 支持多个压缩流连接到一个 xz 文件内。

下面是一段完整的示例程序。所示程序从标准输入 (stdin) 读入数据,解压缩的数据写到标准输出 (stdout). 示例代码省略了所有的异常处理和一些其他次要细节。

清单 2. 解压缩数据的代码示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
#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);
}

编译命令:

1
$ gcc -g -o xz_pipe_decomp_mini xz_pipe_decomp_mini.c -llzma

使用例子:

1
$ cat a.txt.xz | xz_pipe_decomp_mini > a.txt

结束语

本文着重从实用的角度介绍了 XZ Utils 的主要部分,还有一些内容由于篇幅的原因,没有提及。如果您想更深入的研究 XZ Utils,可以访问 XZ Utils 的官方网站,那里可以找到最新的源代码和更详尽的文档。

相关主题

  • 通过 XZ Utils 项目主页,可以得到最新的源代码和文档。
  • 通过 LZMA-SDK 主页,了解 windows 平台上出色的压缩工具 7-zip 和它的编程接口 LZMA-SDK。

via: https://www.ibm.com/developerworks/cn/linux/l-lo-xzutils/index.html

转载请注明:林志斌 » 使用XZ Utils获得更高的压缩率

发表评论
取消评论
表情

Hi,您需要填写昵称和邮箱!

  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址