《哈夫曼压缩》总结
压缩:
一:压缩原理
什么是哈夫曼压缩?呵呵。。。。顾名思义。借用哈夫曼树来实现的压缩就是的了。那么它的压缩原理是什么呢?(这里只能压缩和解压字符)
压缩:
首先将文件中每个字符出现的频率统计出来----->根据频率构建一颗哈夫曼树-------->根据构建的哈夫曼树获得每个字符的哈夫曼编码并存放在一个队列中(这里的每个字符实际上就是树的每个叶子节点,因为文件中出现的字符只会是树的叶子节点)------>写入码表和源文件
码表:码表中应包含编码的个数、每个编码的补零个数、码表的长度、字符的ascll码值、字符对应的哈夫曼编码补足零后的编码
源文件:挨个读出源文件中的字节(此时读出的是字符对应的ascll码),再将读出的ascll码和之前写入的码表中的ascll码相比对,找到相等的值后再写入在码表中该ascll码对应的哈夫曼编码值,注意在写入最后一个字节时,要判断是否需要补零,以及补零个数,便于文件的解压
二:压缩算法分析
/** * 根据文件统计文件中每个字节出现的频率 * * @param path * 文件路径 */ // 创建一个存放字节频率的int数组 private int arrByte[] = new int[256]; public int[] countByte(String path, Probar pro) { int newarr[] = new int[256]; // 创建输出流对象 try { FileInputStream fis = new FileInputStream(path); filelen = fis.available(); // pro=new Probar("压缩"); // pro.start(); while (fis.available() > 0) {// 当文件中还有字节 // System.out.println("|||||||||||||||||||"+prolen); float f = (float) ((float) filelen - (float) fis.available()) / (float) filelen; // System.out.println("@@@@@@@@@@@@@@@@@@filelen=" + filelen // + " 还剩字节数:" + fis.available() + "此时的prolen=" + prolen // + " f=" + f); if (f * 100 > prolen) { // System.out.println("开始prolen的转变了!!!"); prolen = (int) (f * 100); // System.out.println("()()()()()()()()f*100="+f*100); // System.out.println("转变后的prolen是:"+prolen); pro.getBar().setValue(prolen); } int i = fis.read(); arrByte[i]++; } newarr = arrByte; } catch (FileNotFoundException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } return newarr; }
/** * 将表头和源文件写入压缩文件。实现压缩 */ public int yasuo(ArrayList<TreeNode> list) { // 写表头 // 写入码表长度 int length = 0; for (int j = 0; j < list.size(); j++) { // System.out.println(list.get(j).getHfmcode()+"写入码表时每个的哈夫曼编码的长度是:"+list.get(j).getHfmcode().length()); length += 3 + list.get(j).getHfmcode().length(); } // System.out.println("写入文件时码表的长度是:"+length); try { out.write(length); } catch (IOException e1) { e1.printStackTrace(); } for (int i = 0; i < list.size(); i++) { TreeNode node = list.get(i); // System.out.println(node.getASCLL()+"哈夫曼编码"+node.getHfmcode()); // System.out.println("补零个数"+node.getCount_0()); // System.out.println("补零后哈夫曼编码"+node.getHfmcode_0()); try { // 写入字符对应的阿斯科码值 out.write(node.getASCLL()); // System.out.println("已经写入了ascll:" + node.getASCLL()); // 写入补零后的编码字节数 out.write(node.getHfmcode_0().length() / 8); // 写入每个编码补零的个数 out.write(node.getCount_0()); int k = node.getHfmcode_0().length(); // 如果补零后的编码长度大于8 if (k > 8) { int t = 0; while (t < k) { String str = ""; for (int j = t; j < t + 8; j++) { // System.out.println(j+"---------"); str += node.getHfmcode_0().charAt(j); } // System.out.println("写入的补零后8位编码是"+str); out.write(changeString(str)); t += 8; } } else { out.write(changeString(node.getHfmcode_0())); } } catch (IOException e) { e.printStackTrace(); } } /** * 开始写入源文件 */ try { // 创建一个输入流对象 FileInputStream fis = new FileInputStream(path); // 开始时间条进程 // FileOutputStream fos=new FileOutputStream("src/ya_txt"); // 源文件中字符匹配到的哈夫曼编码,也就是要输入文件的哈夫曼编码 String hfm = ""; // 从源文件中读取的字符的ascll码 int ascll; try { // 当文件还有字节的时候 while (fis.available() > 0) { ascll = fis.read(); // write是要写入的编码 String write = ""; for (int i = 0; i < list.size(); i++) { // 如果检索到对应的ASCLL if (ascll == list.get(i).getASCLL()) { // 将哈夫曼编码串起来 hfm += list.get(i).getHfmcode(); // 如果哈夫曼编码大于8位 if (hfm.length() > 8) { // 先取前八位 for (int j = 0; j < 8; j++) { write += hfm.charAt(j); } // 将前八位写入文件中 out.write(changeString(write)); // 清空前八位,继续读取后面不足八位的哈夫曼编码 String newWrite = ""; for (int k = 8; k < hfm.length(); k++) { newWrite += hfm.charAt(k); } hfm = newWrite; } else {// 如果哈夫曼编码不足8位 int newascll = fis.read(); for (int l = 0; l < list.size(); l++) { if (newascll == list.get(l).getASCLL()) { // 找到对应的哈夫曼编码并取出 hfm += list.get(l).getHfmcode(); } } } } } } // 判断文件是否需要补零 if (hfm.length() % 8 != 0) { File_0 = 8 - hfm.length() % 8; } // 写入文件补零个数 // out.write(File_0); for (int i = 0; i < File_0; i++) { hfm += "0"; } if (hfm != null) { // System.out.println("file_0="+File_0+"<><><><><><><><><>hfm"+hfm); out.write(changeString(hfm)); } // 关闭输入输出流 fis.close(); out.close(); } catch (IOException e) { e.printStackTrace(); } } catch (FileNotFoundException e) { e.printStackTrace(); } return File_0; }
解压:
一:解压原理
写完压缩,解压其实很简单,相当于解压的一个逆运算,首先
读码表:根据写入码表时各个元素的顺序读出相应的编码的个数、每个编码的补零个数、码表的长度、字符的ascll码值、字符对应的哈夫曼编码补足零后的编码,将读出的ascll码以及相应的哈夫曼编码(这里的哈夫曼编码是去掉补零后的原始编码)存放在一个hashmap中,编码作为key、ascll值作为键值value以便于后面对读取的源文件进行还原。
读源文件:先将读取的字节转换成一个01串,再从头逐个读取01串将其作为map的键值在之前码表的hashmap中看是否存在这个键值。若存在则取出键值写入目标文件中并设置下次读取从这个0/1串后一位开始。若不存在,则再往后读一个0/1字符继续进行比较。这样循环比较直到最后一个字节,因为最后一个字节中包含写入文件时文件是否补零以及补零个数,所以应该根据压缩时写入文件最后一个字节的元素顺序读出补零个数,再根据补零个数取出真正的编码,并在map中利用前面的方法找到对应的ascll码并写入目标文件中。
二:解压算法分析
/** * 1.读取文件中的码表 * 2.取出码表中的ascll码和对应的哈夫曼编码,存放在hashmap中 * 3.读取文件的源文件内容,注意要去掉写入源文件时最后的补零 * 4.将读取源文件的哈夫曼编码逐一的跟hashmap中的哈夫曼编码比对,若比对相同,则将该哈夫曼编码对应的 * ascll码翻译成字符并写入到新文件中 */ public void jieya(ArrayList<TreeNode> list,JTable table){ try { fis=new FileInputStream(FilePath); fos=new FileOutputStream(newPath); } catch (FileNotFoundException e) { e.printStackTrace(); } map=new HashMap<String,Integer>(); //读取码表长度 try { length=fis.read(); System.out.println("码表的长度是:"+length); } catch (IOException e1) { e1.printStackTrace(); } while(length>0){ try { //读取码表中的ascll码 int ascll = fis.read(); System.out.println("读到的ascll码是:"+ascll); //读取码表中每个ascll码对应的哈夫曼码字节数 int count_hfm=fis.read(); System.out.println("它对应的哈夫曼共"+count_hfm+"个字节"); //读取哈夫曼编码补零的个数 int count_hfm_0=fis.read(); System.out.println("该哈夫曼编码中补零的个数是:"+count_hfm_0); //读取码表中对应的哈夫曼编码 String hfmCode_0=""; for(int j=0;j<count_hfm;j++){ hfmCode_0+=int_to_string(fis.read()); } System.out.println("它对应的哈夫曼编码是:"+hfmCode_0); String hfmCode=""; for(int k=0;k<hfmCode_0.length()-count_hfm_0;k++){ hfmCode+=hfmCode_0.charAt(k); } System.out.println("它对应的去掉零后的哈夫曼编码是:"+hfmCode); //将码表中的ascll码及其对应的哈夫曼编码写入map中 map.put( hfmCode,ascll); System.out.println("存放在map中的hfmCode="+hfmCode+" 对应的ascll="+map.get(hfmCode)); // length-=(3+count_hfm_0); length-=(3+hfmCode.length()); System.out.println("此时码表的长度是:"+length); } catch (IOException e) { e.printStackTrace(); } } /** * 读取源文件 */ String file=""; try { while(fis.available()>1){ int write=0; //将读取的字节转换成01串 file+=int_to_string(fis.read()); System.out.println("%%%%%%%%%%%%%%读取到的哈夫曼编码是:"+file); int count=1; int k=0; while(count<=file.length()){ String hfm=""; for(int i=k;i<count;i++){ hfm+=file.charAt(i); // System.out.println("count="+count+" i="+i+"&&&&&&&&&&&&&&hfm="+hfm); // System.out.println("((((((((((((map中:"+map.get(hfm)); if(map.get(hfm)!=null){ System.out.println("*******************找到对应的哈夫曼编码是:"+map.get(hfm)); fos.write(map.get(hfm)); write+=hfm.length(); k=count; } } count++; } String str=""; for(int i=write;i<file.length();i++){ str+=file.charAt(i); } file=str; } //读取最后一个字符 // CreateTree ct=new CreateTree(); while(fis.available()>0){ String str_end=file+int_to_string(fis.read()); System.out.println("{}{}{}{}{}{}{}{}{}{}最后一个字符是:"+str_end); //读取源文件补零的个数 int count_file_0=File_0; System.out.println("++++++++++++补零的个数是:"+count_file_0); String str_0=""; for(int i=0;i<str_end.length()-count_file_0;i++){ str_0+=str_end.charAt(i); } str_end=str_0; System.out.println("[][][][][][][][][][]去掉补零后的哈夫曼编码是:"+str_end); int count=1; int k=0; while(count<=str_end.length()){ String str=""; for(int i=k;i<count;i++){ str+=str_end.charAt(i); } if(map.get(str)!=null){ //写入最后一个字符 fos.write(map.get(str)); System.out.println("写入的最后一个01串中对应的ascll是:"+map.get(str)); k=count; } count++; } } } catch (IOException e) { e.printStackTrace(); }
附图:
小女不才,可能大家看了我的文字后还是搞不清所以然,下面附上了源代码,希望对大家能有所帮助。欢迎大家提出宝贵意见哈!