速度之王 — LZ4压缩算法(一)

摘要:
在纵向比较中,LZ4可以很好地支持多线程环境,并实现更高的压缩和解压缩速度。首先使用LZ77算法进行压缩,然后使用霍夫曼编码进行压缩。与gzip相比,压缩比更高,压缩效果更好,但压缩速度较慢。LZW是LZ78算法的变体。采用改进和优化的LZ77算法LZMA和LZMA2,压缩比高于zip。如果LZ4是压缩速度之王,xz是压缩比之王。主要使用LZMA2压缩算法。

LZ4 (Extremely Fast Compression algorithm)

项目:http://code.google.com/p/lz4/

作者:Yann Collet

本文作者:zhangskd @ csdn blog

简介

LZ4 is a very fast lossless compression algorithm, providing compression speed at 400MB/s per core,

scalable with multi-cores CPU. It also features an extremely fast decoder, with speed in multiple GB/s per core,

typically reaching RAM speed limits on multi-core systems.

A high compression derivative, called LZ4_HC, is also provided. It trades CPU time for compression ratio.

(1) 横向对比

Quick comparison: single thread, Core i5-3340M @2.7GHz, using the Open-Source Benchmark by m^2 (v0.14.2)

compiled with GCC v4.6.1 on Linux Ubuntu 64-bits v11.10, using the Silesia Corpus.

对比当前流行的压缩工具,可以看到LZ4具有最快的压缩和解压速度,尽管压缩比一般。

速度之王 — LZ4压缩算法(一)第1张

QuickLZ官网:http://www.quicklz.com/

QuickLZ is the world's fastest compression library, reaching 308MB/s per core.

QuickLZ自称是世界上最快的压缩算法,然而我们看到它和LZ4还是有差距的,特别是解压速度。

速度之王 — LZ4压缩算法(一)第2张

snappy项目:https://code.google.com/p/snappy/

snappy is developed by Google based on ideas from LZ77 and open-sourced in 2011.

It was designed to be very fast and stable, but not to achieve a high compression ratio.

Compression speed is 250MB/s and decompression speed is 500MB/s using a single threaded,

64-bit core i7 processor. The compression ratio is 20-100% lower than gzip.

snappy追求的是速度,压缩比并不高。

(2) 纵向对比

LZ4能很好的支持多线程环境,获得更高的压缩和解压速度。

速度之王 — LZ4压缩算法(一)第3张

(3) 技术背景

多媒体技术 -> 无损压缩 -> 词典编码 -> LZ77算法 -> LZ4

词典编码(Dictionary Encoding),根据的是数据本身包含有重复代码序列这个特性。

主要分为两类:

1. 查找正在压缩的字符序列是否在前面输入数据中出现过,如果是,则用指向早期出现过的字符串的“指针”替代

    重复的字符串。这里的“词典”是指以前处理过的数据。(LZ77、LZSS)。

2. 从输入的数据中创建一个“短语词典”,编码数据过程中当遇到已经在词典中出现过的“短语”时,编码器就输

    出这个词典中的短语的“索引号”,而不是短语本身。(LZ78、LZW)

(4) 其它格式

以下是一些常用的压缩格式。

1. zip

    zlib库,可通过包含zlib.h使用。

    zip原名为Deflate,仅支持一个LZ77的变种算法Deflate。

    zip/unzip,后缀为.zip。zip也是Windows下常见的压缩格式。

2. gzip

    gzip/gunzip是GNU程序,后缀为.gz。Web也常用GZIP压缩技术。

    首先使用LZ77算法进行压缩,对结果再使用huffman编码进行压缩。

    tar中用-z来调用:

         tar -czf pic.tar.gz *.jpg

         tar -xzf pic.tar.gz

3. bzip2

    bzip2/bunzip2,后缀为.bz2。

    相比于gzip,压缩比更高,压缩效果比传统的LZ77/LZ78更好,但压缩速度较慢。

    首先使用Burrows-Wheeler变换(BWT,块排序文本压缩),然后使用哈夫曼编码进行压缩。

    tar中使用-j来调用:

        tar -cjf pic.tar.bz2 *.jpg

        tar -xjf pic.tar.bz2

4. compress

    compress/uncompress,后缀为.Z,现在已经不再流行了。

    使用LZ78算法的变种LZW。

    tar中使用-Z来调用。

6. rar

    rar/unrar,后缀为.rar。

    rar格式较zip格式的压缩比高。

    注意RAR非免费,是Windows下常见压缩格式,也有RAR for Linux。

        rar a pic *.jpg // pic.rar

        rar e pic.rar // pic

7. 7z

    7-Zip,后缀为.7z。

    和rar、zip一样,7z也是Windows下常见的压缩格式。

    使用改良与优化后的LZ77算法LZMA、LZMA2,压缩比高于zip。

8. xz

    xz,后缀为.xz。

    如果说LZ4是压缩速度之王,xz则是压缩比之王。

    一般来说,用xz压缩后的文件,能比用gzip压缩的小30%,比用bzip2压缩的小15%。

    主要使用LZMA2压缩算法。

    tar不支持xz格式,xz / xz -d。

LZ77

我们看到很多压缩格式都是基于LZ77的,所以先来了解下LZ77算法,这里引用了较多的网上资料:)

1977年,Jacob Ziv和Abraham Lempel描述了一种基于滑动窗口缓存的技术,该缓存用于保存最近刚刚处理的文本。

LZ77编码的核心是查找从前向缓冲器开始的最长的匹配串。

压缩算法使用了两个缓存:

1. 滑动历史缓存,包含了前面处理过的N个源字符。

2. 前向缓存,包含了将要处理的下面L个字符。

压缩过程

算法尝试将前向缓存开始的两个或多个字符与滑动历史缓存中的字符串相匹配。

如果没有发现匹配,前向缓存的第一个字符作为9bit的字符输出并且移入滑动窗口,滑动窗口中最久的字符被移除。

如果找到匹配,算法继续扫描以找到最长的匹配。然后匹配字符串作为三元组输出(选项、指针和长度)。对于K个字符

的字符串,滑动窗口中最久的K个字符被移出,并且被编码的K个字符被移入窗口。

更具体来说:

1. 从当前压缩位置开始,查看未编码的数据,并试图在滑动窗口中找出最长的匹配字符串,如果找到,则进行步骤2,否则进行步骤3。

2. 输出三元符号组(off, len, c)。其中off为窗口中匹配字符串相对窗口边界的偏移,len为可匹配的长度,c为下一个字符。

    然后将窗口向后滑动len + 1个字符,继续步骤1。

3. 输出三元符合组(0, 0, c)。其中c为下一个字符。然后将窗口向后滑动len + 1个字符,继续步骤1。

实例

假设窗口大小为10个字符,我们刚编码过的字符(滑动窗口)是:abcdbbccaa,即将编码的字符(前向缓存)是:abaeaaabaee。

1. 和编码字符匹配的最长串为ab,下一个字符为a,输出三元组(0, 2, a)。

    窗口向后滑动3个字符为:dbbccaaaba,前向缓存为:eaaabaee。

2. 字符e在窗口中无匹配,输出三元组(0, 0, e),窗口向后滑动1个字符为:bbccaaabae,前向缓存为:aaabaee。

3. 前向缓存的最长匹配串为aaabae,下一个字符为e,输出三元组(4, 6, e),完成编码。

解压过程

解压算法必须保存解压输出的最后N个字符(滑动窗口)。

当碰到编码字符串时,使用指针和长度字段,将编码替换成实际的正文字符串。

评价

算法使用了有限的窗口在以前的文本中查找匹配,对于相对于窗口大小来说非常长的文本块,很多可能的匹配就会被丢掉。

窗口大小可以增加,但这会带来两个损失:

1. 算法的处理时间会增加,因为它必须为滑动窗口的每个位置进行一次与前向缓存的字符串匹配的工作。

2. 指针字段必须更长,以允许更长的跳转。

在多数情况下,lz77拥有较高的压缩效率。而在待压缩文件中绝大多数是些超长匹配,并且相同的超长匹配高频率地反复

出现时,lzw更具优势。GIF就是采用了lzw算法来压缩背景单一、图形简单的图片。zip是用来压缩通用文件的,这就是它

采用对大多数文件有更高压缩率的lz77算法的原因。

优化

精心设计三元组(off, len, c)中每个分量的表示方法,才能达到较好的效果。

(1) off

off为窗口内的偏移,通常的经验是,偏移接近窗口尾部的情况要多于接近窗口头部的情况,这是因为字符串在与其接近的

位置容易找到匹配串,但对于普通的窗口大小(如4096字节)来说,偏移值基本还是均匀分布的,我们完全可以用固定的位

数来表示它。

(2) len

len为字符串长度,它在大多数时候不会太大,少数情况下才会发生大字符串的匹配。显然可以使用一种变长编码方式来表

示该长度值。要输出变长的编码,该编码必须满足前缀码的条件。Huffman编码可以用于此处,但不是最好的选择。

Golomb编码应用比较广泛,对于较小的数用较短的编码,对较大的数用较大的编码表示。

(3) c

c为前向缓存中的不匹配字符。直接用8个二进制位编码。

(4) 输出格式

LZ77的原始算法采用三元组输出每一个匹配串及其后续字符,即使没有匹配,仍需要输出一个len=0的三元组来表示单个字符。

实验表明,这种方式对于某些特殊情况(例如同一字符不断重复的情形)有着较好的适应能力。

对一般数据,有一种更有效的输出方式。

将每一个输出分成匹配串和单个字符两种类型,并首先输出一个二进制位对其加以区分。例如,输出0表示下面是一个匹配串,

输出1表示下面是一个单个字符。之后,如果要输出的是单个字符,我们直接输出该字符的字节值,需要8个二进制位。

也就是说,输出一个单个的字符共需要9个二进制位。

如果要输出的是匹配串,则输出off和len。off可以用定长编码,也可以用变长前缀码。len用变长前缀码。有时候我们可以对匹配

长度加以限制,例如,限制最少匹配3个字符。因为对于2个字符的匹配串,我们使用匹配串的输出方式不一定比我们直接输出2

个单个字符(共需18位)节省空间。

这种输出方式的优点是:输出单个字符的时候比较节省空间。另外,因为不强求每次都外带一个后续字符,可以适应一些较长

匹配的情况。

(5) 查找匹配串

在滑动窗口中查找最长的匹配串,是LZ77算法中的核心问题,关系着空间和时间复杂度。

golomb

哥伦布编码。主要针对整数进行编码,对较小的数用较短的编码,对较大的数用较大的编码表示。

假设x为要进行编码的整数,当x趋于较小的取值时候,此时的Golomb编码较短,可以有效的节省空间。

压缩算法

1. 选定参数m,b = 2^m,注意m是要在压缩前指定的。

2. q = <int> ((x - 1) / b),<int>表示取整。

3. r = x - qb - 1,所以x = qb + r + 1。(注意这个1不用存储,只要默认恢复出来的数据加1即可)

4. 这样要编码的x由两部分组成:

    a. 第一部分由q个1加上一个0组成,表示q。

    b. 第二部分由m个位组成,表示r。

恢复算法

如果读入1,则继续往后读,直到读入0,此时读入的1的个数就是q。

之后的m位(m事先约定了)为r。

所以可以计算出x = qb + 1 + r = q * 2^m + r + 1。

Reference

[1]. http://fastcompression.blogspot.com/p/lz4.html

[2]. http://jpkc.zust.edu.cn/2007/dmt/course/MMT03_05_1.htm

[3]. http://jpkc.zust.edu.cn/2007/dmt/course/MMT03_05_2.htm

[4]. http://hi.baidu.com/guoliqiang2006/item/127c8f989b494b4ef14215db

[5]. http://blog.chinaunix.net/uid-17240700-id-3347894.html

[6]. http://jpkc.zust.edu.cn/2007/dmt/course/Mmt03_02_2.htm

[7]. http://tukaani.org/xz/

免责声明:文章转载自《速度之王 — LZ4压缩算法(一)》仅用于学习参考。如对内容有疑问,请及时联系本站处理。

上篇JQuery window.openerOpenCASCADE动画功能2下篇

宿迁高防,2C2G15M,22元/月;香港BGP,2C5G5M,25元/月 雨云优惠码:MjYwNzM=

相关文章

Windows常见窗口样式和控件风格

Windows常见窗口样式和控件风格 一、窗口样式WS_POPUP 弹出式窗口(不能与WS_CHILDWINDOW样式同时使用)WS_CHILDWINDOW 子窗口(不能与WS_POPUP合用)WS_MINIMIZE 创建窗口拥有最小化按钮WS_MINIMIZEBOX 创建窗口拥有最小化按钮,须同时指定WS_SYSTEM样式WS_VISIBLE 可见状态W...

LCA(最近公共祖先)离线算法Tarjan+并查集

本文来自:http://www.cnblogs.com/Findxiaoxun/p/3428516.html 写得很好,一看就懂了。 在这里就复制了一份。 LCA问题: 给出一棵有根树T,对于任意两个结点u,v求出LCA(T, u, v),即离根最远的结点x,使得x同时是u和v的祖先。      把LCA问题看成询问式的:给出一系列询问,程序应当对每一个询...

Akamai在内容分发网络中的算法研究(翻译总结)

BLOOM FILTERS Bloom filters的研究主要用在akamai的CDN中的两个场景:1)索引管理优化;2)内容过滤。 Bloom filters是hash算法的一个变种,有非常优秀的空间效率(使用位数组)和时间效率(插入的时间复杂度稳定为常数),但是会有一定的错误率。直观的说,bloom算法类似一个hash set,用来判断某个元素(ke...

JSP中字符编码转换问题

问题描述:一个input.jsp页面中的参数,传递到另外一个save.jsp页面上,然后存入到数据库中,如果input.jsp页面输入偶数中文没有问题,输入奇数则出现?,存入数据库的也是?。 问题源码:save.jsp中 <%    String fileName=request.getParameter("Name");           Sys...

【文文殿下】后缀自动机(SAM)求最长公共子串的方法

首先,在A 串上建立一个SAM,然后用B串在上面跑。具体跑的方法是: 从根节点开始,建立一个指针 p ,指着B串的开头,同步移动指针,沿着SAM的边移动,如果可以移动(即存在边)那么万事皆好,直接len++就好,但是,如果无法继续转移(失配了),那么,我们考虑跳回其父节点,因为其父节点的Right集是当前状态的真超集,那么其父节点状态所代表的字符串的集合中...

lua 分割字符串

-- 参数:待分割的字符串,分割字符   -- 返回:子串表.(含有空串)   function split(str, split_char)           local sub_str_tab = {}     while true do                   local pos = string.find(str, split_cha...