最近工作正好接触到这一块,试着自己总结了一下,给需要的人提供一点帮助。

一、概述

首先看看百度百科里的一句话介绍:“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)无损压缩算法详述的更多相关文章

  1. [iOS翻译]Cocoa编码规范

        简介: 本文整理自Apple文档<Coding Guidelines for Cocoa>.这份文档原意是给Cocoa框架.插件及公共API开发者提供一些编码指导,实质上相当于Ap ...

  2. [IR] Dictionary Coding

    [数据压缩]LZ77算法原理及实现 [数据压缩]LZ78算法原理及实现 Lempel–Ziv–Welch 年发表的论文<A Universal Algorithm for Sequential ...

  3. 把大象装进冰箱的N种方法

    作者:折剑头链接:https://www.zhihu.com/question/49214119/answer/115728034来源:知乎著作权归作者所有.商业转载请联系作者获得授权,非商业转载请注 ...

  4. 开始读 Go 源码了

    原文链接: 开始读 Go 源码了 学完 Go 的基础知识已经有一段时间了,那么接下来应该学什么呢?有几个方向可以考虑,比如说 Web 开发,网络编程等. 在下一阶段的学习之前,写了一个开源项目|Go ...

  5. [No0000AB]用Visual Studio 2015在 WIN10 64bit 上编译7-zip (32 bit)

    1.7-ZIP简介 7-zip 是一款免费的压缩解压软件.ZIP格式的文件默认被苹果和微软支持,完全不需要额外安装其他软件就可以解压.但对于非US-ASCII编码的文件名和大于2GB的ZIP文件,可能 ...

  6. suffix tree

    文章出处:http://www.cnblogs.com/snowberg/archive/2011/10/21/2468588.html   3   What is a Suffix Tree Suf ...

  7. 变长编码表 ASCII代码等长编码

    小结: 1.ASCII编码.GBK编码不是变长编码: 2.数据压缩: 示例: aabacdab → 00100110111010 → |0|0|10|0|110|111|0|10| → aabacda ...

  8. Regmap 框架:简化慢速IO接口优化性能【转】

    1. 简介 Regmap 机制是在 Linux 3.1 加入进来的特性.主要目的是减少慢速 I/O 驱动上的重复逻辑,提供一种通用的接口来操作底层硬件上的寄存器.其实这就是内核做的一次重构.Regma ...

  9. 7z文件格式及其源码linux/windows编译

    7z文件格式及其源码的分析(二) 一. 准备工作: 1. 源码下载: 可以从官方中文主页下载:http://sparanoid.com/lab/7z/. 为了方便, 这里直接给出下载链接: http: ...

随机推荐

  1. git bash操作

    1. GIT说明 1> git是分布式,或者说是去中心化的.表现为: 开发者的可以在本地使用git并完美的控制自己的版本,而无需与服务端交互: 开发者可以将本地库在某个服务端备份,这种情况类似S ...

  2. 各种webservice调用地址

    http://www.webxml.com.cn/zh_cn/web_services.aspx

  3. GroupBy(..)的四种声明方式的理解及调用

    这里我们以 List<Student> studs作为 source,但是注意,studs中的学生可以是分别属于不同的班级和年级 先看GroupBy的第一种声明: public stati ...

  4. 登陆Oracle,报oracle initializationg or shutdown in progress 错误提示

    前两天,登陆Oracle,发现登陆不上去了,报”oracle initializationg or shutdown in progress 错误提示” 错误. 然后就想着怎么去解决,首先自己到win ...

  5. 在linux 服务器上用.htaccess文件实现二级域名访问子目录

    实现子域名绑定网站子目录的方法有很多,比如可以用js跳转,可以用php实现,可以301跳转等,但最常用的应该是RewriteEngine方式了.   新建一个笔记本命名为.htaccess(在wind ...

  6. CNN初步-2

    Pooling 为了解决convolved之后输出维度太大的问题 在convolved的特征基础上采用的不是相交的区域处理     http://www.wildml.com/2015/11/unde ...

  7. 剑指Offer-【面试题07:两个栈实现队列】

    package com.cxz.question7; import java.util.Stack; /** * 用两个栈实现一个队列.队列的声明如下,请实现它的两个函数appendTail 和del ...

  8. UE3名称结构(Name)

    解释说明: (1) 直接通过FName的index进行比较来判断两个FName是否相等 (2) 通过FName的index从全局Names数组中取出对应的FNameEntry,可以获得FName的字符 ...

  9. TCP连接的建立和终止

    TCP的简要要说明 标签(空格分隔): TCP 网络编程 Linux 面试 在此输入正文 一.TCP是什么 TCP全称传输控制协议(Transmission Control Protocol).TCP ...

  10. 开发常用技巧之css字体编码

    简介: 当我们写css时,通常需要设置字体名称,我们可以直接写中文,这样没错,但是文件编码为GB2312.UTF-8等不匹配将会出现乱码.因此将中文字体名称转为unicode编码来避免出现这些错误. ...