LZW压缩算法

介绍

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:

ababbabab
词组类WordFix.java:

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:

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:

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);
	}
}
结果输出:

压缩前的字符:ababbabab
压缩后的字符:a,b,256,257,259,
解压后的字符:ababbabab
在文件目录中的3个文件显示:


算法的遗漏点

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

LZW算法的特点

LZW压缩算法对于可预测性不大的数据压缩的效果会比较好,还有1个是时常出现重复的字符时,也可以比较好的压缩,还有是对于机器的硬件要求不太高。


posted @ 2020-01-12 19:09  回眸,境界  阅读(960)  评论(0编辑  收藏  举报