您的位置:首页 > 其它

LZW压缩算法

2015-12-17 16:45 218 查看


介绍

LZW算法是非常常见的一种压缩算法,他的压缩原理是对于多次重复出现的字符串,进行压缩,至于怎么压缩,在后文中会细细描述,LZW算法可以用在很多的场合,诸如图像压缩,文本压缩等等,而且算法简单易懂,并不是人们想象中的那么深奥。


算法原理

在介绍算法原理之前,得先明白几个概念:

1、Prefix,在这里代表前缀字符的意思。

2、Suffix,对应的意思是后缀字符的意思。

为什么提到这2个概念呢,是因为后面的字符的压缩的输入的过程就与这2者相关。这里假设压缩的是文本字符,字符内容如下:

ababbabab

测试的数据未必是必须多的,上面的字符中还是存在着一些重复的字符段的,可以满足题目的要求的。好,下面是压缩的流程:

1、从左往右逐一的读取源文件中的字符,构成前缀,后缀字符词组的方式。

2、如果构成的词组没有被编码过,则进行编码,并且输出此时的前缀字符,然后后缀字符替代前缀字符,后缀字符继续从文件中读入。

3、如果构成的词组被编码过,就是说这个词组之前出现过,是重复的,则不输出,将对应于此时词组的编码赋给词组的前缀,然后继续读入后缀字符。

第几步
前缀
后缀

存在对应码
输出

1
a
(,a)
2
a
b
(a,b)
no
a
256
3
b
a
(b,a)
no
b
257
4
a
b
(a,b)
yes
5
256
b
(256,b)
no
256
258
6
b
a
(b,a)
yes
7
257
b
(257,b)
no
257
259
8
b
a
(b,a)
yes
9
257
b
(257,b)
yes
上述的最后一步是在输入结束之后,最后将(257,b)变为259后输出,所以最后的输出为:

a,b,256,257,259。

解压的时候过程正好相反,根据码表,做码制与字符的替换输出就行了,具体细节可以参照我的代码实现。

算法代码实现:

输入源文件srcFile.txt:

[java] view
plaincopyprint?

ababbabab

词组类WordFix.java:

[java] view
plaincopyprint?

package LZW;

import java.util.HashMap;

import java.util.Map;

/**

* 词组,包括前缀和后缀

*

* @author lyq

*

*/

public class WordFix {

// 词组前缀

String prefix;

// 词组后缀

String suffix;

// 编码词组映射表

HashMap<WordFix, Integer> word2Code;

public WordFix(String prefix, String suffix,

HashMap<WordFix, Integer> word2Code) {

this.prefix = prefix;

this.suffix = suffix;

this.word2Code = word2Code;

}

/**

* 设置前缀

*

* @param str

*/

public void setPrefix(String str) {

this.prefix = str;

}

/**

* 设置后缀

*

* @param str

*/

public void setSuffix(String str) {

this.suffix = str;

}

/**

* 获取前缀字符

*

* @return

*/

public String getPrefix() {

return this.prefix;

}

/**

* 判断2个词组是否相等,比较前后字符是否相等

*

* @param wf

* @return

*/

public boolean isSame(WordFix wf) {

boolean isSamed = true;

if (!this.prefix.equals(wf.prefix)) {

isSamed = false;

}

if (!this.suffix.equals(wf.suffix)) {

isSamed = false;

}

return isSamed;

}

/**

* 判断此词组是否已经被编码

*

* @return

*/

public boolean hasWordCode() {

boolean isContained = false;

WordFix wf = null;

for (Map.Entry entry : word2Code.entrySet()) {

wf = (WordFix) entry.getKey();

if (this.isSame(wf)) {

isContained = true;

break;

}

}

return isContained;

}

/**

* 词组进行编码

*

* @param wordCode

* 此词组将要被编码的值

*/

public void wordFixCoded(int wordCode) {

word2Code.put(this, wordCode);

}

/**

* 读入后缀字符

*

* @param str

*/

public void readSuffix(String str) {

int code = 0;

boolean isCoded = false;

WordFix wf = null;

for (Map.Entry entry : word2Code.entrySet()) {

code = (int) entry.getValue();

wf = (WordFix) entry.getKey();

if (this.isSame(wf)) {

isCoded = true;

// 编码变为前缀

this.prefix = code + "";

break;

}

}

if (!isCoded) {

return;

}

this.suffix = str;

}

/**

* 将词组转为连续的字符形式

*

* @return

*/

public String transToStr() {

int code = 0;

String currentPrefix = this.prefix;

for(Map.Entry entry: word2Code.entrySet()){

code = (int) entry.getValue();

//如果前缀字符还是编码,继续解析

if(currentPrefix.equals(code + "")){

currentPrefix =((WordFix) entry.getKey()).transToStr();

break;

}

}

return currentPrefix + this.suffix;

}

}

压缩算法工具类LZWTool.java:

[java] view
plaincopyprint?

package LZW;

import java.io.BufferedReader;

import java.io.File;

import java.io.FileNotFoundException;

import java.io.FileOutputStream;

import java.io.FileReader;

import java.io.IOException;

import java.io.PrintStream;

import java.util.ArrayList;

import java.util.HashMap;

import java.util.Map;

/**

* LZW解压缩算法工具类

*

* @author lyq

*

*/

public class LZWTool {

// 开始的编码的编码号从256开始

public static int LZW_CODED_NUM = 256;

// 待压缩文件地址

private String srcFilePath;

// 目标文件地址

private String desFileLoc;

// 压缩后的目标文件名

private String desFileName;

// 结果字符,将被写到输出文件中

private String resultStr;

// 编码词组映射表

HashMap<WordFix, Integer> word2Code;

// 源文件数据

private ArrayList<String> totalDatas;

public LZWTool(String srcFilePath, String desFileLoc, String desFileName) {

this.srcFilePath = srcFilePath;

this.desFileLoc = desFileLoc;

this.desFileName = desFileName;

word2Code = new HashMap<>();

totalDatas = new ArrayList<>();

readDataFile(totalDatas);

}

/**

* 从文件中读取数据

*

* @param inputData

* 输入数据容器

*/

private void readDataFile(ArrayList<String> inputData) {

File file = new File(srcFilePath);

ArrayList<String[]> dataArray = new ArrayList<String[]>();

try {

BufferedReader in = new BufferedReader(new FileReader(file));

String str;

String[] tempArray;

while ((str = in.readLine()) != null) {

tempArray = new String[str.length()];

for (int i = 0; i < str.length(); i++) {

tempArray[i] = str.charAt(i) + "";

}

dataArray.add(tempArray);

}

in.close();

} catch (IOException e) {

e.getStackTrace();

}

System.out.print("压缩前的字符:");

for (String[] array : dataArray) {

for (String s : array) {

inputData.add(s);

System.out.print(s);

}

}

System.out.println();

}

/**

* 进行lzw压缩

*/

public void compress() {

resultStr = "";

boolean existCoded = false;

String prefix = totalDatas.get(0);

WordFix wf = null;

for (int i = 1; i < totalDatas.size(); i++) {

wf = new WordFix(prefix, totalDatas.get(i), word2Code);

existCoded = false;

// 如果当前词组存在相应编码,则继续读入后缀

while (wf.hasWordCode()) {

i++;

// 如果到底了则跳出循环

if (i == totalDatas.size()) {

// 说明还存在词组编码的

existCoded = true;

wf.readSuffix("");

break;

}

wf.readSuffix(totalDatas.get(i));

}

if (!existCoded) {

// 对未编码过的词组进行编码

wf.wordFixCoded(LZW_CODED_NUM);

LZW_CODED_NUM++;

}

// 将前缀输出

resultStr += wf.getPrefix() + ",";

// 后缀边前缀

prefix = wf.suffix;

}

// 将原词组的后缀加入也就是新的词组的前缀

resultStr += prefix;

System.out.println("压缩后的字符:" + resultStr);

writeStringToFile(resultStr, desFileLoc + desFileName);

}

public void unCompress(String srcFilePath, String desFilePath) {

String result = "";

int code = 0;

File file = new File(srcFilePath);

ArrayList<String[]> datas = new ArrayList<String[]>();

try {

BufferedReader in = new BufferedReader(new FileReader(file));

String str;

String[] tempArray;

while ((str = in.readLine()) != null) {

tempArray = str.split(",");

datas.add(tempArray);

}

in.close();

} catch (IOException e) {

e.getStackTrace();

}

for (String[] array : datas) {

for (String s : array) {

for (Map.Entry entry : word2Code.entrySet()) {

code = (int) entry.getValue();

if (s.equals(code + "")) {

s = ((WordFix) entry.getKey()).transToStr();

break;

}

}

result += s;

}

}

System.out.println("解压后的字符:" + result);

writeStringToFile(result, desFilePath);

}

/**

* 写字符串到目标文件中

*

* @param resultStr

*/

public void writeStringToFile(String resultStr, String desFilePath) {

try {

File file = new File(desFilePath);

PrintStream ps = new PrintStream(new FileOutputStream(file));

ps.println(resultStr);// 往文件里写入字符串

} catch (FileNotFoundException e) {

// TODO Auto-generated catch block

e.printStackTrace();

}

}

}

测试调用类Client.java:

[java] view
plaincopyprint?

package LZW;

/**

* LZW解压缩算法

* @author lyq

*

*/

public class Client {

public static void main(String[] args){

//源文件地址

String srcFilePath = "C:\\Users\\lyq\\Desktop\\icon\\srcFile.txt";

//压缩后的文件名

String desFileName = "compressedFile.txt";

//压缩文件的位置

String desFileLoc = "C:\\Users\\lyq\\Desktop\\icon\\";

//解压后的文件名

String unCompressedFilePath = "C:\\Users\\lyq\\Desktop\\icon\\unCompressedFile.txt";

LZWTool tool = new LZWTool(srcFilePath, desFileLoc, desFileName);

//压缩文件

tool.compress();

//解压文件

tool.unCompress(desFileLoc + desFileName, unCompressedFilePath);

}

}

结果输出:

[java] view
plaincopyprint?

压缩前的字符:ababbabab

压缩后的字符:a,b,256,257,259,

解压后的字符:ababbabab

在文件目录中的3个文件显示:




算法的遗漏点

算法整体不是很难,仔细去想一般都能找到压缩的方式,就是在解压的过程中药考虑到编码前缀解析掉之后,他的编码前缀还可能是一个编码所以需要递归的解析,在这个测试例子中你可能没有看见预想到的压缩效果,那时因为文本量实在太小,就几个字节,当测试的文本达到几十k的时候,并且捏造的数据中出现大量的重复字符串时,压缩的效果就会显现出来。


LZW算法的特点

LZW压缩算法对于可预测性不大的数据压缩的效果会比较好,还有1个是时常出现重复的字符时,也可以比较好的压缩,还有是对于机器的硬件要求不太高。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: