LZW 压缩算法英文全称 Lempel-Ziv-Welch Encoding,该算法是由 Lempel、Ziv、Welch 三人发明,它基于表查询算法删除重复数据从而实现减小文件大小的无损压缩算法,也称为“串表压缩算法”,主要用于 TIFF、GIF、PDF 文件压缩以及文字压缩。LZW 压缩算法实现简单、通用性强并且在硬件实现中具有高吞吐量,通常在许多 PC 应用程序都将它作为通用数据压缩的方式,也是 Unix 操作系统文件压缩程序的一部分。
1. 基本原理
LZW 压缩算法的基本原理:通过建立一个字典表,用较短的代码表示较长的字符串,从而实现对数据的无损压缩。详细点说,LZW 算法提取原始文件数据中的不同字符,基于这些字符创建一个字典表,然后用字典表中的字符索引代替原始文件数据中相应的字符,从而实现对原始文件数据的无损压缩。
2. 编解码流程
对原始数据TOBEORNOTTOBEORTOBEORNOT进行编解码演示,假设采用 ASCII 编码规则,以 ASCII 码表作为初始字典表。ASCII 码表十进制范围为0~255,详细内容可点击这里查看,简单描述可查看附录。
2.1 编码流程演示
编码过程中构造的字典的 Key 为 ASCII 码、Value 为 ASCII 值
程序输入TOBEORNOTTOBEORTOBEORNOT、输出[84, 79, 66, 69, 79, 82, 78, 79, 84, 256, 258, 260, 265, 259, 261, 263]。
输入的字符共包含24个字符,在 Java 程序中,每个字符占用2个字节、16位,共占用384位。输出结果中最大值为263,可以用9位的二进制值表示,共占用16 * 9 = 144位,压缩比为2.67。
| 步骤 | 已识别字符Ω | 当前字符λ | Ω+λ | Ω+λ是否在字典中 | 操作 | 输出 |
|---|---|---|---|---|---|---|
| 1 | (空字符串) | T | T | Y(84) | Ω=Ω+λ(T) | |
| 2 | T | O | TO | N | 字典新增TO=256; Ω=λ(O); 输出结果增加84 | 84 |
| 3 | O | B | OB | N | 字典新增OB=257; Ω=λ(B); 输出结果增加79 | 84、79 |
| 4 | B | E | BE | N | 字典新增BE=258; Ω=λ(E); 输出结果增加66 | 84、79、66 |
| 5 | E | O | EO | N | 字典新增EO=259; Ω=λ(O); 输出结果增加69 | 84、79、66、69 |
| 6 | O | R | OR | N | 字典新增OR=260; Ω=λ(R); 输出结果增加79 | 84、79、66、69、79 |
| 7 | R | N | RN | N | 字典新增RN=261; Ω=λ(N); 输出结果增加82 | 84、79、66、69、79、82 |
| 8 | N | O | NO | N | 字典新增NO=262; Ω=λ(O); 输出结果增加78 | 84、79、66、69、79、82、78 |
| 9 | O | T | OT | N | 字典新增OT=263; Ω=λ(T); 输出结果增加79 | 84、79、66、69、79、82、78、79 |
| 10 | T | T | TT | N | 字典新增TT=264; Ω=λ(T); 输出结果增加84 | 84、79、66、69、79、82、78、79、84 |
| 11 | T | O | TO | Y(256) | Ω=Ω+λ(TO) | |
| 12 | TO | B | TOB | N | 字典新增TOB=265; Ω=λ(B); 输出结果增加256 | 84、79、66、69、79、82、78、79、84、256 |
| 13 | B | E | BE | Y(258) | Ω=Ω+λ(BE) | |
| 14 | BE | O | BEO | N | 字典新增BEO=266; Ω=λ(O); 输出结果增加258 | 84、79、66、69、79、82、78、79、84、256、258 |
| 15 | O | R | OR | Y(260) | Ω=Ω+λ(OR) | |
| 16 | OR | T | ORT | N | 字典新增ORT=267; Ω=λ(T); 输出结果增加260 | 84、79、66、69、79、82、78、79、84、256、258、260 |
| 17 | T | O | TO | Y(256) | Ω=Ω+λ(TO) | |
| 18 | TO | B | TOB | Y(265) | Ω=Ω+λ(TOB) | |
| 19 | TOB | E | TOBE | N | 字典新增TOBE=268; Ω=λ(E); 输出结果增加265 | 84、79、66、69、79、82、78、79、84、256、258、260、265 |
| 20 | E | O | EO | Y(259) | Ω=Ω+λ(EO) | |
| 21 | EO | R | EOR | N | 字典新增EOR=269; Ω=λ(R); 输出结果增加259 | 84、79、66、69、79、82、78、79、84、256、258、260、265、259 |
| 22 | R | N | RN | Y(261) | Ω=Ω+λ(RN) | |
| 23 | RN | O | RNO | N | 字典新增RNO=270; Ω=λ(O); 输出结果增加261 | 84、79、66、69、79、82、78、79、84、256、258、260、265、259、261 |
| 24 | O | T | OT | Y(263) | Ω=Ω+λ(OT) | |
| 25 | OT | (空字符串) | OT | Y(263) | 输出结果增加263 | 84、79、66、69、79、82、78、79、84、256、258、260、265、259、261、263 |
2.2 解码流程演示
解码构造的字典与编码构造的字典有所不同,其 Key 为 ASCII 值、Value 为 ASCII 码
程序输入[84, 79, 66, 69, 79, 82, 78, 79, 84, 256, 258, 260, 265, 259, 261, 263]、输出TOBEORNOTTOBEORTOBEORNOT。
| 步骤 | 前一个被解码的字符Ω | 当前读入的字符λ | λ是否在字典中 | 操作 | 输出 |
|---|---|---|---|---|---|
| 1 | (空字符串) | 84 | 第一个字符肯定在字典中,直接输出 | Ω=λ(T); 输出结果增加T | T |
| 2 | T | 79 | Y(O) | 字典新增256=Ωλ<第1个字符>(TO); Ω=λ(O); 输出结果增加O | TO |
| 3 | O | 66 | Y(B) | 字典新增257=Ωλ<第1个字符>(OB); Ω=λ(B); 输出结果增加B | TOB |
| 4 | B | 69 | Y(E) | 字典新增258=Ωλ<第1个字符>(BE); Ω=λ(E); 输出结果增加E | TOBE |
| 5 | E | 79 | Y(O) | 字典新增259=Ωλ<第1个字符>(EO); Ω=λ(O); 输出结果增加O | TOBEO |
| 6 | O | 82 | Y(R) | 字典新增260=Ωλ<第1个字符>(OR); Ω=λ(R); 输出结果增加R | TOBEOR |
| 7 | R | 78 | Y(N) | 字典新增261=Ωλ<第1个字符>(RN); Ω=λ(N); 输出结果增加N | TOBEORN |
| 8 | N | 79 | Y(O) | 字典新增262=Ωλ<第1个字符>(NO); Ω=λ(O); 输出结果增加O | TOBEORNO |
| 9 | O | 84 | Y(T) | 字典新增263=Ωλ<第1个字符>(OT); Ω=λ(T); 输出结果增加T | TOBEORNOT |
| 10 | T | 256 | Y(TO) | 字典新增264=Ωλ<第1个字符>(TT); Ω=λ(TO); 输出结果增加TO | TOBEORNOTTO |
| 11 | TO | 258 | Y(BE) | 字典新增265=Ωλ<第1个字符>(TOB); Ω=λ(BE); 输出结果增加BE | TOBEORNOTTOBE |
| 12 | BE | 260 | Y(OR) | 字典新增266=Ωλ<第1个字符>(BEO); Ω=λ(OR); 输出结果增加OR | TOBEORNOTTOBEOR |
| 13 | OR | 265 | Y(TOB) | 字典新增267=Ωλ<第1个字符>(ORT); Ω=λ(BE); 输出结果增加TOB | TOBEORNOTTOBEORTOB |
| 14 | BE | 259 | Y(EO) | 字典新增268=Ωλ<第1个字符>(BEE); Ω=λ(EO); 输出结果增加EO | TOBEORNOTTOBEORTOBEO |
| 15 | EO | 261 | Y(RN) | 字典新增269=Ωλ<第1个字符>(EOR); Ω=λ(RN); 输出结果增加RN | TOBEORNOTTOBEORTOBEORN |
| 16 | RN | 263 | Y(OT) | 字典新增270=Ωλ<第1个字符>(RNO); Ω=λ(OT); 输出结果增加OT | TOBEORNOTTOBEORTOBEORNOT |
3. 代码实现
DICT_SIZE = 256
3.1 编码
public static List<Integer> encoder(String charStream) {
// 初始化字典
Map<String, Integer> dictionary = new HashMap<>();
for (int i = 0; i < DICT_SIZE; i++) {
dictionary.put(String.valueOf((char) i), i);
}
// 压缩数据
List<Integer> result = new ArrayList<>();
String foundCharacters = "";
for (char character : charStream.toCharArray()) {
String charactersToAdd = foundCharacters + character;
if (dictionary.containsKey(charactersToAdd)) {
foundCharacters = charactersToAdd;
} else {
result.add(dictionary.get(foundCharacters));
dictionary.put(charactersToAdd, dictionary.size());
foundCharacters = String.valueOf(character);
}
}
if (!foundCharacters.isEmpty()) {
result.add(dictionary.get(foundCharacters));
}
return result;
}
3.2 解码
public static String decode(List<Integer> codeStream) {
// 初始化字典
Map<Integer, String> dictionary = new HashMap<>();
for (int i = 0; i < DICT_SIZE; i++) {
dictionary.put(i, String.valueOf((char) i));
}
// 解压数据
String characters = String.valueOf((char) codeStream.remove(0).intValue());
StringBuilder result = new StringBuilder(characters);
for (int code : codeStream) {
String entry = dictionary.containsKey(code) ? dictionary.get(code) : characters + characters.charAt(0);
result.append(entry);
dictionary.put(dictionary.size(), characters + entry.charAt(0));
characters = entry;
}
return result.toString();
}
附录:ACSII 码表
- 第0-31位表示控制字符
- 第32-127位表示打印字符
- 第128-255位表示扩展字符