可能是最通俗的Lempel-Ziv-Welch (LZW)无损压缩算法详述
最近工作正好接触到这一块,试着自己总结了一下,给需要的人提供一点帮助。
一、概述
首先看看百度百科里的一句话介绍:“LZW就是通过建立一个字符串表,用较短的代码来表示较长的字符串来实现压缩。” 简单来说,就是尝试用短的编码代替长的编码达到压缩的目的。LZW其实是三个大牛的名字,这个算法最开始是 Ziv和Lempel这两个人在1977,1978年发明的,在1984年的时候由另一位大牛Terry Welch进行了改进,所以这个算法的名字才叫LZW。它的一个最为人所占的应用是在gif压缩里面的使用。
二、示例
假设我们要通过互联网发送一本英文牛津字典,一个字符在计算机中需要8个bit表示,假设一本子字典里有20万个单词,每个单词平均长度为5个字符,那么这样一个字典需要800万个bit表示完。如果我们换一种思维方式,20万个单词都编上号,20万个单词18个比特就能全部表示完,20万乘以18bit,只需要360万个bit就能表示所有的单词,相对于第一种办法,数据量只有一半不到。但是这种方法有一个重要问题就是每个人都需要一个对应表,用来查每个数字对应哪个单词,这样才能得到正确的信息。LZW算法就是利用这样一种思想并且能够自适应的生成字典并且保存在最后的编码本身之中。
原始的LZW算法的采用4k长的字典(实际上很难用到这么多),最开始的256个字典项就是ASCII码值。后面的字典项在编码过程中根据输入码本身自动的产生。对于这种算法,可以用以下伪码表示其逻辑:
1.string P = 第一个被读入的字符并输出p
2.WHILE(输入字符串未读入结束)
3.{
4. char C= 紧接着的下一个输入字符
5. if(p+c 存在于已产生的字典中)
6. {
7. p = p+c
8. }
9. else
10. {
11. 生成新的字典编号代表pc
12. 输出p的字典项编号
13. p = c
14. }
15.}
16.输出p
为了深入理解算法思想,从一个具体的例子开始,比如对于一个字符串BABAABAAA,如何将这样长的一个字符串编码成更短的字符串呢?当一个字符串被读入时,首先读入B,这时候直接输出B(或者说B的ASCII码,66)。接着读入A,这时p+c没有存在于字典中,生成新的字典编号(256)代表"BA"同时输出A(ASCII码,65)。现在p是"A",接着读入下一个字符,B,现在p+c是"AB",没有存在于字典之中,一样生成新的字典编号(257)代表"AB",同时输出66代表B。然后p=B,接着读入c,c=A,这时p+c是“BA",存在于字典项中(256),暂时不输出,接着读入下一个字符"A”,这时产生了新的不在字典项总的p,“BAA”,用258代替这个字符串之后并且输出256用意代表"BA",看,已经短了一点了。依次类推,最后得到的压缩编码是"66,65,256,257,65,260",在此过程中产生的非ASCII码的字典项是“256-BA","257-AB","258-BAA","259-ABA","260-AA"。这里至少要用9bit代表新产生的压缩码,但是压缩之后只要54bits,相对于原理啊72bits还是减少了,所以在现实使用中,压缩在读入的字节不大于一个特定值(比如100bits)并不进行压缩。
那么得到编码流"66,65,256,257,65,260",怎样解压缩得到原始的数据呢?LZW的解压缩算法用伪码表示是这样的:
1. 读入一个码p
2. 输出p对应字符
3. p = c;
4. while ( 继续读入下一个码c )
5.{
6. entry = 寻找码c的字典对应项
7. 输出entry
8. 将p+entry[0]加入字典项
9. p = entry
10. }
还是从实例中理解下这个解压缩算法(这里我们假设一次读入9个bit的码),首先读入的是66,输出”B",接着读入下一个码“65”,现在entry=A,输入entry,并且将BA加入字典项,p=A,并且知道256对应的是这个字典项“BA”,下一个读进来的是256,找到这个entry是"BA“,并且生成出下一个字典项257为"A(p)B(entry[0])",这时候p=”BA“,下一次是257,那么输出"AB”,并且生成下一个字典项258为“BAA",以此类推,字典项总能在码之前生成,所以每一个9bit的码都能正确的被解析。这样在最后不仅得到了正确的解码序列并且一起自动的生成了字典。
三、实现
既然伪码已经有了,那么实现就不是太困难了。在这里我展示一下我的C#的实现版本,同时因为项目需要,我还实现了一份C/C++实现版本,如果需要的话可以发信给我的邮箱(rogerzhu0710@gmail.com)来索取。
class Compress
{
static System.Int16 ms_nOutputBitCount;
static ulong ms_nOutputBitBuffer;
static int ms_nOutputBufferIdx = ;
static byte[] ms_byOutputBuffer; static byte[] append_character;
static System.UInt16[] prefix_code; static int TABLE_SIZE = ;
static short[] ms_nCodeValue;
static int HASHING_SHIFT = ;
static short ms_nOutput_bit_count = ;
static int m_nCount = ;
static int m_nOffset = ; static long lzw_compress(byte[] input, byte[] output,long size)
{
System.UInt16 next_code;
System.UInt16 character;
System.UInt16 string_code;
System.UInt16 index; System.UInt16 i; next_code = ; ms_nOutputBitCount = ;
ms_nOutput_bit_count = ;
ms_nOutputBitBuffer = 0L;
ms_nOutputBufferIdx = ; append_character = new Byte[TABLE_SIZE];
prefix_code = new ushort[TABLE_SIZE]; ms_nCodeValue = new short[TABLE_SIZE]; m_nCount = ;
for(i = ; i < TABLE_SIZE; i++)
{
ms_nCodeValue[i] = -;
} string_code = input[m_nCount++]; while((--size)!=)
{
character = input[m_nCount++]; index = (System.UInt16)find_match(string_code, character); if (ms_nCodeValue[index] != -)
{
string_code = (ushort)ms_nCodeValue[index];
}
else
{
if (next_code <= MAX_CODE)
{
ms_nCodeValue[index] = (short)next_code++;
prefix_code[index] = string_code;
append_character[index] = Convert.ToByte(character);
}
output_code(string_code);
string_code = character;
}
} output_code(string_code);
output_code(MAX_VALUE);
output_code(); Buffer.BlockCopy(ms_byOutputBuffer, , output, , ms_byOutputBuffer.Length);
return ms_nOutputBufferIdx ;
} static void output_code(ushort code)
{ ms_nOutputBitBuffer |= ((ulong)code << ( - BITS - ms_nOutput_bit_count));
ms_nOutput_bit_count += BITS;
int k = ; while (ms_nOutput_bit_count >= )
{
ms_byOutputBuffer[ms_nOutputBufferIdx++] = (byte)(ms_nOutputBitBuffer >> );
ms_nOutputBitBuffer <<= ;
ms_nOutput_bit_count -= ;
}
} static int find_match(int hash_prefix, uint hash_character)
{
short index;
short offset; index = (short)((hash_character << HASHING_SHIFT) ^ hash_prefix); if(index == )
{
offset = ;
}
else
{
offset = (short)(TABLE_SIZE - index);
} while(true)
{
if (ms_nCodeValue[index] == -)
{
return(index);
} if(prefix_code[index] == hash_prefix && append_character[index] == hash_character)
{
return(index);
} index -= offset;
if(index < )
{
index += (short)TABLE_SIZE;
}
}
}
}
压缩
解压缩的代码如下:
class Decompress
{
static System.Int16 ms_nInputBitCount;
static System.UInt32 ms_nInputBitBuffer;
static int ms_nInputBufferIdx;
static byte[] ms_byInputBuffer; static byte[] decode_stack;
static byte[] append_character;
static System.UInt16[] prefix_code;
static bool ms_bSuppressOutput;
static short ms_nKey; static int LzwExpand(byte[] input, byte[] output)
{
int nOutputIdx = ;
System.UInt16 next_code;
System.UInt16 new_code;
System.UInt16 old_code; System.Int16 character; int nDecodeOffset = ; int count = ;
next_code = ;
ms_nInputBitCount = ;
ms_nInputBitBuffer = ;
ms_nInputBufferIdx = ;
ms_byInputBuffer = input; old_code = (ushort) input_code();
character = (short) old_code; if (output != null && nOutputIdx < output.Length)
output[nOutputIdx++] = (byte) old_code; count++; while((new_code = (ushort) input_code()) != MAX_VALUE)
{
if(new_code >= next_code)
{
decode_stack[] = (byte) character;
nDecodeOffset = decode_string(decode_stack, , old_code);
}
else
{
nDecodeOffset = decode_string(decode_stack, , new_code);
} if(nDecodeOffset < )
return ; character = decode_stack[nDecodeOffset];
while(nDecodeOffset >= )
{
if (output != null && nOutputIdx < output.Length)
output[nOutputIdx++] = decode_stack[nDecodeOffset]; count++; nDecodeOffset--;
} if(next_code <= MAX_CODE)
{
prefix_code[next_code] = old_code;
append_character[next_code] = (byte) character;
next_code++;
}
old_code = new_code;
} return count;
} static int decode_string(byte[] buffer, int nStartIdx, System.UInt16 code)
{
System.Int16 i = ; while(code > )
{
buffer[nStartIdx++] = append_character[code];
code = prefix_code[code]; if (i++ >= )
{ return -;
}
} buffer[nStartIdx] = (byte) code; return nStartIdx;
} static int input_code()
{
System.UInt16 nReturnValue; while (ms_nInputBitCount <= )
{
ms_nInputBitBuffer |= ((System.UInt32)(ms_byInputBuffer[ms_nInputBufferIdx])) << ( - ms_nInputBitCount);
ms_nInputBufferIdx++;
ms_nInputBitCount += ;
} nReturnValue = (System.UInt16) (ms_nInputBitBuffer >> ( - BITS));
ms_nInputBitBuffer <<= BITS;
ms_nInputBitCount -= BITS;
return nReturnValue;
}
}
解压缩
代码比较长,我会在我自己的博客(http://www.richinmemory.com/)来详细解释这些代码,如果有兴趣话,请继续关注吧。
可能是最通俗的Lempel-Ziv-Welch (LZW)无损压缩算法详述的更多相关文章
- [iOS翻译]Cocoa编码规范
简介: 本文整理自Apple文档<Coding Guidelines for Cocoa>.这份文档原意是给Cocoa框架.插件及公共API开发者提供一些编码指导,实质上相当于Ap ...
- [IR] Dictionary Coding
[数据压缩]LZ77算法原理及实现 [数据压缩]LZ78算法原理及实现 Lempel–Ziv–Welch 年发表的论文<A Universal Algorithm for Sequential ...
- 把大象装进冰箱的N种方法
作者:折剑头链接:https://www.zhihu.com/question/49214119/answer/115728034来源:知乎著作权归作者所有.商业转载请联系作者获得授权,非商业转载请注 ...
- 开始读 Go 源码了
原文链接: 开始读 Go 源码了 学完 Go 的基础知识已经有一段时间了,那么接下来应该学什么呢?有几个方向可以考虑,比如说 Web 开发,网络编程等. 在下一阶段的学习之前,写了一个开源项目|Go ...
- [No0000AB]用Visual Studio 2015在 WIN10 64bit 上编译7-zip (32 bit)
1.7-ZIP简介 7-zip 是一款免费的压缩解压软件.ZIP格式的文件默认被苹果和微软支持,完全不需要额外安装其他软件就可以解压.但对于非US-ASCII编码的文件名和大于2GB的ZIP文件,可能 ...
- suffix tree
文章出处:http://www.cnblogs.com/snowberg/archive/2011/10/21/2468588.html 3 What is a Suffix Tree Suf ...
- 变长编码表 ASCII代码等长编码
小结: 1.ASCII编码.GBK编码不是变长编码: 2.数据压缩: 示例: aabacdab → 00100110111010 → |0|0|10|0|110|111|0|10| → aabacda ...
- Regmap 框架:简化慢速IO接口优化性能【转】
1. 简介 Regmap 机制是在 Linux 3.1 加入进来的特性.主要目的是减少慢速 I/O 驱动上的重复逻辑,提供一种通用的接口来操作底层硬件上的寄存器.其实这就是内核做的一次重构.Regma ...
- 7z文件格式及其源码linux/windows编译
7z文件格式及其源码的分析(二) 一. 准备工作: 1. 源码下载: 可以从官方中文主页下载:http://sparanoid.com/lab/7z/. 为了方便, 这里直接给出下载链接: http: ...
随机推荐
- 史上最全的maven的pom.xml文件详解
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/20 ...
- Mac php使用gd库出错 Call to undefined function imagettftext()
第一次在Mac下使用ThinkPHP,用到验证码功能时报如题的错误: Call to undefined function Think\imagettftext() 然后检查自己的GD库,发现安装上了 ...
- CNN初步-2
Pooling 为了解决convolved之后输出维度太大的问题 在convolved的特征基础上采用的不是相交的区域处理 http://www.wildml.com/2015/11/unde ...
- (转) Wp7 list 中列表项多样化的解决方案-Custom DataTemplateSelector
本文转自: http://www.cnblogs.com/sonyye/archive/2012/03/03/2378825.html 在这篇文章中,我将解释如何在Windows Phone 7中创建 ...
- JAVA中MAP值保持顺序不变
今天在进行JAVA开发过程中,因需要使用MAP来存放数据,同时希望MAP中KEY的顺序与放入顺序保持一致. 在使用HashMap之后,发现KEY的顺序是乱序的,每次打印还不太一样.上网查询资料之后发现 ...
- linux安装jdk(非rpm命令)
首先查看当前linux上安装的jdk版本: java -version 复制build 后面的jdk信息 卸载: rpm -e java-1.6.0_22-fcs 或者 yum -y remove j ...
- vaadin学习,重要的网址
比较重要的几个:UI组件及示例程序:http://demo.vaadin.com/samplerBook of Vaadin:https://vaadin.com/bookAPI:https://va ...
- CSS知识图--转载
- 【前端性能】高性能滚动 scroll 及页面渲染优化--转发
本文主要想谈谈页面优化之滚动优化. 主要内容包括了为何需要优化滚动事件,滚动与页面渲染的关系,节流与防抖,pointer-events:none 优化滚动.因为本文涉及了很多很多基础,可以对照上面的知 ...
- js控制刷新后回到页面原来位置
1.document.location.reload(); 2.http://www.jb51.net/article/99749.htm