LZW压缩算法

来源:互联网 发布:北京游戏外包公司知乎 编辑:程序博客网 时间:2024/05/16 06:32

介绍

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?
  1. ababbabab  
词组类WordFix.java:

[java] view plaincopyprint?
  1. package LZW;  
  2.   
  3. import java.util.HashMap;  
  4. import java.util.Map;  
  5.   
  6. /** 
  7.  * 词组,包括前缀和后缀 
  8.  *  
  9.  * @author lyq 
  10.  *  
  11.  */  
  12. public class WordFix {  
  13.     // 词组前缀  
  14.     String prefix;  
  15.     // 词组后缀  
  16.     String suffix;  
  17.       
  18.     // 编码词组映射表  
  19.     HashMap<WordFix, Integer> word2Code;  
  20.   
  21.     public WordFix(String prefix, String suffix,  
  22.             HashMap<WordFix, Integer> word2Code) {  
  23.         this.prefix = prefix;  
  24.         this.suffix = suffix;  
  25.         this.word2Code = word2Code;  
  26.     }  
  27.   
  28.     /** 
  29.      * 设置前缀 
  30.      *  
  31.      * @param str 
  32.      */  
  33.     public void setPrefix(String str) {  
  34.         this.prefix = str;  
  35.     }  
  36.   
  37.     /** 
  38.      * 设置后缀 
  39.      *  
  40.      * @param str 
  41.      */  
  42.     public void setSuffix(String str) {  
  43.         this.suffix = str;  
  44.     }  
  45.   
  46.     /** 
  47.      * 获取前缀字符 
  48.      *  
  49.      * @return 
  50.      */  
  51.     public String getPrefix() {  
  52.         return this.prefix;  
  53.     }  
  54.   
  55.     /** 
  56.      * 判断2个词组是否相等,比较前后字符是否相等 
  57.      *  
  58.      * @param wf 
  59.      * @return 
  60.      */  
  61.     public boolean isSame(WordFix wf) {  
  62.         boolean isSamed = true;  
  63.   
  64.         if (!this.prefix.equals(wf.prefix)) {  
  65.             isSamed = false;  
  66.         }  
  67.   
  68.         if (!this.suffix.equals(wf.suffix)) {  
  69.             isSamed = false;  
  70.         }  
  71.   
  72.         return isSamed;  
  73.     }  
  74.   
  75.     /** 
  76.      * 判断此词组是否已经被编码 
  77.      *  
  78.      * @return 
  79.      */  
  80.     public boolean hasWordCode() {  
  81.         boolean isContained = false;  
  82.         WordFix wf = null;  
  83.   
  84.         for (Map.Entry entry : word2Code.entrySet()) {  
  85.             wf = (WordFix) entry.getKey();  
  86.             if (this.isSame(wf)) {  
  87.                 isContained = true;  
  88.                 break;  
  89.             }  
  90.         }  
  91.   
  92.         return isContained;  
  93.     }  
  94.   
  95.     /** 
  96.      * 词组进行编码 
  97.      *  
  98.      * @param wordCode 
  99.      *            此词组将要被编码的值 
  100.      */  
  101.     public void wordFixCoded(int wordCode) {  
  102.         word2Code.put(this, wordCode);  
  103.     }  
  104.   
  105.     /** 
  106.      * 读入后缀字符 
  107.      *  
  108.      * @param str 
  109.      */  
  110.     public void readSuffix(String str) {  
  111.         int code = 0;  
  112.         boolean isCoded = false;  
  113.         WordFix wf = null;  
  114.   
  115.         for (Map.Entry entry : word2Code.entrySet()) {  
  116.             code = (int) entry.getValue();  
  117.             wf = (WordFix) entry.getKey();  
  118.             if (this.isSame(wf)) {  
  119.                 isCoded = true;  
  120.                 // 编码变为前缀  
  121.                 this.prefix = code + "";  
  122.                 break;  
  123.             }  
  124.         }  
  125.   
  126.         if (!isCoded) {  
  127.             return;  
  128.         }  
  129.         this.suffix = str;  
  130.     }  
  131.   
  132.     /** 
  133.      * 将词组转为连续的字符形式 
  134.      *  
  135.      * @return 
  136.      */  
  137.     public String transToStr() {  
  138.         int code = 0;  
  139.         String currentPrefix = this.prefix;  
  140.           
  141.         for(Map.Entry entry: word2Code.entrySet()){  
  142.             code = (int) entry.getValue();  
  143.             //如果前缀字符还是编码,继续解析  
  144.             if(currentPrefix.equals(code + "")){  
  145.                 currentPrefix =((WordFix) entry.getKey()).transToStr();  
  146.                 break;  
  147.             }  
  148.         }  
  149.           
  150.         return currentPrefix + this.suffix;  
  151.     }  
  152.   
  153. }  
压缩算法工具类LZWTool.java:

[java] view plaincopyprint?
  1. package LZW;  
  2.   
  3. import java.io.BufferedReader;  
  4. import java.io.File;  
  5. import java.io.FileNotFoundException;  
  6. import java.io.FileOutputStream;  
  7. import java.io.FileReader;  
  8. import java.io.IOException;  
  9. import java.io.PrintStream;  
  10. import java.util.ArrayList;  
  11. import java.util.HashMap;  
  12. import java.util.Map;  
  13.   
  14. /** 
  15.  * LZW解压缩算法工具类 
  16.  *  
  17.  * @author lyq 
  18.  *  
  19.  */  
  20. public class LZWTool {  
  21.     // 开始的编码的编码号从256开始  
  22.     public static int LZW_CODED_NUM = 256;  
  23.   
  24.     // 待压缩文件地址  
  25.     private String srcFilePath;  
  26.     // 目标文件地址  
  27.     private String desFileLoc;  
  28.     // 压缩后的目标文件名  
  29.     private String desFileName;  
  30.     // 结果字符,将被写到输出文件中  
  31.     private String resultStr;  
  32.     // 编码词组映射表  
  33.     HashMap<WordFix, Integer> word2Code;  
  34.     // 源文件数据  
  35.     private ArrayList<String> totalDatas;  
  36.   
  37.     public LZWTool(String srcFilePath, String desFileLoc, String desFileName) {  
  38.         this.srcFilePath = srcFilePath;  
  39.         this.desFileLoc = desFileLoc;  
  40.         this.desFileName = desFileName;  
  41.   
  42.         word2Code = new HashMap<>();  
  43.         totalDatas = new ArrayList<>();  
  44.         readDataFile(totalDatas);  
  45.     }  
  46.   
  47.     /** 
  48.      * 从文件中读取数据 
  49.      *  
  50.      * @param inputData 
  51.      *            输入数据容器 
  52.      */  
  53.     private void readDataFile(ArrayList<String> inputData) {  
  54.         File file = new File(srcFilePath);  
  55.         ArrayList<String[]> dataArray = new ArrayList<String[]>();  
  56.   
  57.         try {  
  58.             BufferedReader in = new BufferedReader(new FileReader(file));  
  59.             String str;  
  60.             String[] tempArray;  
  61.             while ((str = in.readLine()) != null) {  
  62.                 tempArray = new String[str.length()];  
  63.                 for (int i = 0; i < str.length(); i++) {  
  64.                     tempArray[i] = str.charAt(i) + "";  
  65.                 }  
  66.   
  67.                 dataArray.add(tempArray);  
  68.             }  
  69.             in.close();  
  70.         } catch (IOException e) {  
  71.             e.getStackTrace();  
  72.         }  
  73.   
  74.         System.out.print("压缩前的字符:");  
  75.         for (String[] array : dataArray) {  
  76.             for (String s : array) {  
  77.                 inputData.add(s);  
  78.                 System.out.print(s);  
  79.             }  
  80.         }  
  81.         System.out.println();  
  82.     }  
  83.   
  84.     /** 
  85.      * 进行lzw压缩 
  86.      */  
  87.     public void compress() {  
  88.         resultStr = "";  
  89.         boolean existCoded = false;  
  90.         String prefix = totalDatas.get(0);  
  91.         WordFix wf = null;  
  92.   
  93.         for (int i = 1; i < totalDatas.size(); i++) {  
  94.             wf = new WordFix(prefix, totalDatas.get(i), word2Code);  
  95.             existCoded = false;  
  96.   
  97.             // 如果当前词组存在相应编码,则继续读入后缀  
  98.             while (wf.hasWordCode()) {  
  99.                 i++;  
  100.                 // 如果到底了则跳出循环  
  101.                 if (i == totalDatas.size()) {  
  102.                     // 说明还存在词组编码的  
  103.                     existCoded = true;  
  104.                     wf.readSuffix("");  
  105.                     break;  
  106.                 }  
  107.   
  108.                 wf.readSuffix(totalDatas.get(i));  
  109.             }  
  110.   
  111.             if (!existCoded) {  
  112.                 // 对未编码过的词组进行编码  
  113.                 wf.wordFixCoded(LZW_CODED_NUM);  
  114.                 LZW_CODED_NUM++;  
  115.             }  
  116.   
  117.             // 将前缀输出  
  118.             resultStr += wf.getPrefix() + ",";  
  119.             // 后缀边前缀  
  120.             prefix = wf.suffix;  
  121.         }  
  122.   
  123.         // 将原词组的后缀加入也就是新的词组的前缀  
  124.         resultStr += prefix;  
  125.         System.out.println("压缩后的字符:" + resultStr);  
  126.         writeStringToFile(resultStr, desFileLoc + desFileName);  
  127.     }  
  128.   
  129.     public void unCompress(String srcFilePath, String desFilePath) {  
  130.         String result = "";  
  131.         int code = 0;  
  132.   
  133.         File file = new File(srcFilePath);  
  134.         ArrayList<String[]> datas = new ArrayList<String[]>();  
  135.   
  136.         try {  
  137.             BufferedReader in = new BufferedReader(new FileReader(file));  
  138.             String str;  
  139.             String[] tempArray;  
  140.             while ((str = in.readLine()) != null) {  
  141.                 tempArray = str.split(",");  
  142.                 datas.add(tempArray);  
  143.             }  
  144.             in.close();  
  145.         } catch (IOException e) {  
  146.             e.getStackTrace();  
  147.         }  
  148.   
  149.         for (String[] array : datas) {  
  150.             for (String s : array) {  
  151.                 for (Map.Entry entry : word2Code.entrySet()) {  
  152.                     code = (int) entry.getValue();  
  153.                     if (s.equals(code + "")) {  
  154.                         s = ((WordFix) entry.getKey()).transToStr();  
  155.                         break;  
  156.                     }  
  157.                 }  
  158.   
  159.                 result += s;  
  160.             }  
  161.         }  
  162.   
  163.         System.out.println("解压后的字符:" + result);  
  164.         writeStringToFile(result, desFilePath);  
  165.     }  
  166.   
  167.     /** 
  168.      * 写字符串到目标文件中 
  169.      *  
  170.      * @param resultStr 
  171.      */  
  172.     public void writeStringToFile(String resultStr, String desFilePath) {  
  173.         try {  
  174.             File file = new File(desFilePath);  
  175.             PrintStream ps = new PrintStream(new FileOutputStream(file));  
  176.             ps.println(resultStr);// 往文件里写入字符串  
  177.         } catch (FileNotFoundException e) {  
  178.             // TODO Auto-generated catch block  
  179.             e.printStackTrace();  
  180.         }  
  181.     }  
  182.   
  183. }  
测试调用类Client.java:

[java] view plaincopyprint?
  1. package LZW;  
  2.   
  3. /** 
  4.  * LZW解压缩算法 
  5.  * @author lyq 
  6.  * 
  7.  */  
  8. public class Client {  
  9.     public static void main(String[] args){  
  10.         //源文件地址  
  11.         String srcFilePath = "C:\\Users\\lyq\\Desktop\\icon\\srcFile.txt";  
  12.         //压缩后的文件名  
  13.         String desFileName = "compressedFile.txt";  
  14.         //压缩文件的位置  
  15.         String desFileLoc = "C:\\Users\\lyq\\Desktop\\icon\\";  
  16.         //解压后的文件名  
  17.         String unCompressedFilePath = "C:\\Users\\lyq\\Desktop\\icon\\unCompressedFile.txt";  
  18.           
  19.         LZWTool tool = new LZWTool(srcFilePath, desFileLoc, desFileName);  
  20.         //压缩文件  
  21.         tool.compress();  
  22.           
  23.         //解压文件  
  24.         tool.unCompress(desFileLoc + desFileName, unCompressedFilePath);  
  25.     }  
  26. }  
结果输出:

[java] view plaincopyprint?
  1. 压缩前的字符:ababbabab  
  2. 压缩后的字符:a,b,256,257,259,  
  3. 解压后的字符:ababbabab  
在文件目录中的3个文件显示:


算法的遗漏点

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

LZW算法的特点

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

0 0
原创粉丝点击