zoukankan      html  css  js  c++  java
  • 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个是时常出现重复的字符时,也可以比较好的压缩,还有是对于机器的硬件要求不太高。


  • 相关阅读:
    Thumbnailator压缩图片
    dubbo序列化的一点注意
    Java编程思想读书笔记之内部类
    Hello World
    sql中where和having的区别
    Linux下服务器搭建
    maven中profile的激活方式
    <![CDATA[ ]]>
    linux下用xampp安装php集成环境,并修改各自端口号
    关于星号(**/*.java)
  • 原文地址:https://www.cnblogs.com/bianqi/p/12183989.html
Copyright © 2011-2022 走看看