java實(shí)現(xiàn)哈夫曼壓縮的實(shí)例
哈夫曼壓縮的原理:
通過統(tǒng)計(jì)文件中每個(gè)字節(jié)出現(xiàn)的頻率,將8位的01串轉(zhuǎn)換為位數(shù)較短的哈夫曼編碼.
其中哈夫曼編碼是根據(jù)文件中字節(jié)出現(xiàn)的頻率構(gòu)建的,其中出現(xiàn)頻率越高的字節(jié),其路徑長(zhǎng)度越短;
出現(xiàn)頻率越低的字節(jié)其路徑長(zhǎng)度越長(zhǎng).從而達(dá)到壓縮的目的.
如何構(gòu)造哈夫曼樹?
一. 定義字節(jié)類
我的字節(jié)類定義了一下屬性
public int data;//節(jié)點(diǎn)數(shù)據(jù) public int weight;//該節(jié)點(diǎn)的權(quán)值 public int point;//該節(jié)點(diǎn)所在的左右位置 0-左 1-右 private NodeData parent;//父節(jié)點(diǎn)引用 private NodeData left;//左節(jié)點(diǎn)引用 private NodeData right;//右節(jié)點(diǎn)引用
二.建哈夫曼樹
1.定義一個(gè)存儲(chǔ)字節(jié)信息的數(shù)組: int array_Bytes[256];
其中數(shù)組的下標(biāo)[0,256)代表字節(jié)數(shù)值(一個(gè)字節(jié)8位,其值在[0,256)范圍內(nèi));數(shù)組存儲(chǔ)字節(jié)出現(xiàn)的次數(shù).
2.遍歷要壓縮的文件,統(tǒng)計(jì)字節(jié)出現(xiàn)的次數(shù).
InputStream data = new FileInputStream(path); /******** 文件中字符個(gè)數(shù) ********/ int number = data.available(); for (int i = 0; i < number; i++) { int b = data.read(); array_Bytes[b] ++; } data.close();
3.將字節(jié)類對(duì)象存入優(yōu)先隊(duì)列
4.運(yùn)用HashMap 構(gòu)造碼表
哈夫曼壓縮代碼如下:
1.字節(jié)類
package compressFile; /** * 節(jié)點(diǎn)數(shù)據(jù) * 功能:定義數(shù)據(jù),及其哈夫曼編碼 * @author Andrew * */ public class NodeData { public NodeData(){ } public int data;//節(jié)點(diǎn)數(shù)據(jù) public int weight;//該節(jié)點(diǎn)的權(quán)值 public int point;//該節(jié)點(diǎn)所在的左右位置 0-左 1-右 private NodeData parent; private NodeData left; private NodeData right; public int getData(){ return data; } public NodeData getParent() { return parent; } public void setParent(NodeData parent) { this.parent = parent; } public NodeData getLeft() { return left; } public void setLeft(NodeData left) { this.left = left; } public NodeData getRight() { return right; } public void setRight(NodeData right) { this.right = right; } }
2.統(tǒng)計(jì)字節(jié)的類,并生成碼表
package compressFile; import java.io.BufferedInputStream; import java.io.FileInputStream; import java.io.IOException; import java.io.InputStream; import java.util.ArrayList; import java.util.Comparator; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.PriorityQueue; /** * 統(tǒng)計(jì)指定文件中每個(gè)字節(jié)數(shù) 功能:定義數(shù)組,將文件中的字節(jié)類型及個(gè)數(shù)寫入數(shù)組 * 創(chuàng)建優(yōu)先隊(duì)列和哈夫曼樹 * @author Andrew * */ public class StatisticBytes { /** * 第一步: * 統(tǒng)計(jì)文件中字節(jié)的方法 * * @param path * 所統(tǒng)計(jì)的文件路徑 * @return 字節(jié)個(gè)數(shù)數(shù)組 */ private int[] statistic(String path) { /******儲(chǔ)存字節(jié)數(shù)據(jù)的數(shù)組--索引值代表字節(jié)類型-存儲(chǔ)值代表權(quán)值******/ int[] array_Bytes = new int[256]; try { InputStream data = new FileInputStream(path); BufferedInputStream buffered = new BufferedInputStream(data); /******** 文件中字符個(gè)數(shù) ********/ int number = data.available(); System.out.println("字節(jié)個(gè)數(shù)》》》"+number); for (int i = 0; i < number; i++) { int b = data.read(); array_Bytes[b] ++; } data.close(); } catch (IOException e) { e.printStackTrace(); } return array_Bytes; } /** * 第二步: * 根據(jù)統(tǒng)計(jì)的字節(jié)數(shù)組創(chuàng)建優(yōu)先隊(duì)列 * @param array 統(tǒng)計(jì)文件字節(jié)的數(shù)組 * @return 優(yōu)先隊(duì)列 */ private PriorityQueue<NodeData> createQueue(int[] array){ //定義優(yōu)先隊(duì)列,根據(jù)數(shù)據(jù)的權(quán)值排序從小到大 PriorityQueue<NodeData> queue = new PriorityQueue<NodeData>(array.length,new Comparator<NodeData>(){ public int compare(NodeData o1, NodeData o2) { return o1.weight - o2.weight; } }); for(int i=0; i<array.length; i++){ if(0 != array[i]){ NodeData node = new NodeData(); node.data = i;//設(shè)置該節(jié)點(diǎn)的數(shù)據(jù) node.weight = array[i];//設(shè)置該節(jié)點(diǎn)的權(quán)值 queue.add(node); } } return queue; } /** * 第三步: * 根據(jù)優(yōu)先隊(duì)列創(chuàng)建哈夫曼樹 * @param queue 優(yōu)先隊(duì)列 * @return 哈夫曼樹根節(jié)點(diǎn) */ private NodeData createTree(PriorityQueue<NodeData> queue){ while(queue.size() > 1){ NodeData left = queue.poll();//取得左節(jié)點(diǎn) NodeData right = queue.poll();//取得右節(jié)點(diǎn) NodeData root = new NodeData();//創(chuàng)建新節(jié)點(diǎn) root.weight = left.weight + right.weight; root.setLeft(left); root.setRight(right); left.setParent(root); right.setParent(root); left.point = 0; right.point = 1; queue.add(root); } NodeData firstNode = queue.poll(); return firstNode; } /** * 第四步: * 尋找葉節(jié)點(diǎn)并獲得哈夫曼編碼 * @param root 根節(jié)點(diǎn) */ private void achievehfmCode(NodeData root){ if(null == root.getLeft() && null == root.getRight()){ array_Str[root.data] = this.achieveLeafCode(root); /** * * 得到將文件轉(zhuǎn)換為哈夫曼編碼后的文集長(zhǎng)度 * 文件長(zhǎng)度 = 一種編碼的長(zhǎng)度 * 該編碼出現(xiàn)的次數(shù) */ WriteFile.size_File += (array_Str[root.data].length())*(root.weight); } if(null != root.getLeft()){ achievehfmCode(root.getLeft()); } if(null != root.getRight()){ achievehfmCode(root.getRight()); } } /** * 根據(jù)某葉節(jié)點(diǎn)獲得該葉節(jié)點(diǎn)的哈夫曼編碼 * @param leafNode 葉節(jié)點(diǎn)對(duì)象 */ private String achieveLeafCode(NodeData leafNode){ String str = ""; /*****************存儲(chǔ)節(jié)點(diǎn) 01 編碼的隊(duì)列****************/ List<Integer> list_hfmCode = new ArrayList<Integer>(); list_hfmCode.add(leafNode.point); NodeData parent = leafNode.getParent(); while(null != parent){ list_hfmCode.add(parent.point); parent = parent.getParent(); } int size = list_hfmCode.size(); for(int i=size-2; i>=0; i--){ str += list_hfmCode.get(i); } System.out.println(leafNode.weight+"的哈夫曼編碼為>>>"+str); return str; } /** * 第五步: * 根據(jù)獲得的哈夫曼表創(chuàng)建 碼表 * Integer 為字節(jié)byte [0~256) * String 為哈夫曼編碼 * @return 碼表 */ public Map<Integer,String> createMap(){ int[] hfm_Codes = this.statistic("F:\\JAVA\\壓縮前.txt");//獲得文件字節(jié)信息 PriorityQueue<NodeData> queue = this.createQueue(hfm_Codes);//獲得優(yōu)先隊(duì)列 /* * 獲得哈夫曼樹的根節(jié)點(diǎn), * this.createTree(queue)方法調(diào)用之后(含有poll()),queue.size()=0 */ NodeData root = this.createTree(queue); this.achievehfmCode(root);//獲得哈夫曼編碼并將其存入數(shù)組中 Map<Integer,String> map = new HashMap<Integer,String>(); for(int i=0; i<256; i++){ if(null != array_Str[i]){ map.put(i, array_Str[i]); } } return map; } /** * 存儲(chǔ)字節(jié)哈夫曼編碼的數(shù)組 * 下標(biāo):字節(jié)數(shù)據(jù) * 元素:哈夫曼編碼 */ public String[] array_Str = new String[256]; }
3.將碼表寫入壓縮文件并壓縮正文
package compressFile; import java.io.BufferedInputStream; import java.io.BufferedOutputStream; import java.io.DataOutputStream; import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.IOException; import java.util.Iterator; import java.util.Map; import java.util.Set; /** * 將碼表和文件寫入新的文件中 * @author Andrew * */ public class WriteFile { // 實(shí)例化創(chuàng)建碼表類對(duì)象 private StatisticBytes statistic = new StatisticBytes(); private Map<Integer, String> map = statistic.createMap();// 獲得碼表 // 字節(jié)接收變量,接收哈夫曼編碼連接夠8位時(shí)轉(zhuǎn)換的字節(jié) private int exmpCode; public static int size_File; public static void main(String[] args) { WriteFile writeFile = new WriteFile(); writeFile.init(); } private void init() { String filePath = "F:\\JAVA\\壓縮后.txt"; this.writeFile(filePath); } /** * 第一步: 向文件中寫入碼表 * * @param dataOut * 基本數(shù)據(jù)流 */ private void writeCodeTable(DataOutputStream dataOut) { Set<Integer> set = map.keySet(); Iterator<Integer> p = set.iterator(); try { //向文件中寫入碼表的長(zhǎng)度 dataOut.writeInt(map.size()); while (p.hasNext()) { Integer key = p.next(); String hfmCode = map.get(key); dataOut.writeInt(key);//寫入字節(jié) //寫入哈夫曼編碼的長(zhǎng)度 dataOut.writeByte(hfmCode.length()); for(int i=0; i<hfmCode.length(); i++){ dataOut.writeChar(hfmCode.charAt(i));//寫入哈夫曼編碼 } } } catch (IOException e) { e.printStackTrace(); } } /** * 第二步: 向壓縮文件中寫入編碼 * * @param path */ private void writeFile(String path) { try { // 輸入流 FileInputStream in = new FileInputStream("F:\\JAVA\\壓縮前.txt"); BufferedInputStream bIn = new BufferedInputStream(in); // 輸出流 FileOutputStream out = new FileOutputStream(path); DataOutputStream dataOut = new DataOutputStream(out); BufferedOutputStream bOut = new BufferedOutputStream(out); // 向文件中寫入碼表 this.writeCodeTable(dataOut); /********************寫入補(bǔ)零個(gè)數(shù)*********************/ if(0 != size_File % 8){ dataOut.writeByte(8 - (size_File % 8)); } String transString = "";//中轉(zhuǎn)字符串,存儲(chǔ)字符串直到size大于8 String waiteString = "";//轉(zhuǎn)化字符串, int size_File = in.available(); for(int i=0; i<size_File; i++){ int j = bIn.read(); System.out.println("]]]]]]]]]]]>>"); waiteString = this.changeStringToByte(transString + statistic.array_Str[j]); if(waiteString != ""){ bOut.write(exmpCode); transString = waiteString; }else{ transString += statistic.array_Str[j]; } } if("" != transString){ int num = 8-transString.length()%8; for(int i=0; i<num; i++){ transString += 0; } } transString = this.changeStringToByte(transString); bOut.write(exmpCode); bIn.close(); bOut.flush(); bOut.close(); out.close(); } catch (IOException e) { e.printStackTrace(); } } /** * 附屬第二步: * 將字符串轉(zhuǎn)化為byte * * @param str * 要轉(zhuǎn)化的字符串 * @return 如果str的長(zhǎng)度大于8返回一個(gè)截取前8位后的str * 否則返回"" */ private String changeStringToByte(String str) { if (8 <= str.length()) { exmpCode = ((str.charAt(0) - 48) * 128 + (str.charAt(1) - 48) * 64 + (str.charAt(2) - 48) * 32 + (str.charAt(3) - 48) * 16 + (str.charAt(4) - 48) * 8 + (str.charAt(5) - 48) * 4 + (str.charAt(6) - 48) * 2 + (str.charAt(7) - 48)); str = str.substring(8); return str; } return ""; } }
二.哈夫曼解壓
原理:將壓縮的逆向,即你是如何壓縮的就怎樣去解壓。相當(dāng)于你根據(jù)自己定義的協(xié)議進(jìn)行壓縮與解壓縮文件。
代碼如下:
package decompressionFile; import java.io.DataInputStream; import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.util.ArrayList; import java.util.HashMap; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Set; /** * 解壓文件 從壓縮文件中讀入數(shù)據(jù)解壓 * * @author Andrew * */ public class ReadFile { /** * 碼表 Integter: 字節(jié) [0,255) String: 哈夫曼編碼 */ private Map<Integer, String> code_Map = new HashMap<Integer, String>(); public static void main(String[] args) { ReadFile readFile = new ReadFile(); readFile.readFile(); } /** * 第一步: 從文件中讀出碼表 * * @param dataInput * 基本數(shù)據(jù)輸入流 * */ private void readMap(DataInputStream dataInput) { try { // 讀出碼表的長(zhǎng)度 int size_Map = dataInput.readInt(); /**************** 讀出碼表信息 ************************************/ for (int i = 0; i < size_Map; i++) { int data = dataInput.readInt();// 讀入字節(jié)信息 String str = "";// 哈夫曼編碼 // 哈夫曼編碼長(zhǎng)度,存儲(chǔ)時(shí)以字符寫入 byte codeSize = dataInput.readByte(); for (byte j = 0; j < codeSize; j++) { str += dataInput.readChar(); } System.out.println("&&&&&&&&&&>>>>"+data+"!!!!!!!>>"+str); code_Map.put(data, str); } /***************************************************************/ } catch (IOException e) { e.printStackTrace(); } } /** * 第二步: 解壓正式文件 */ private void readFile() { try { // 文件輸入流 InputStream input = new FileInputStream("F:\\JAVA\\壓縮后.txt"); // BufferedInputStream bIn = new BufferedInputStream(input); DataInputStream dInput = new DataInputStream(input); // 調(diào)用讀出碼表的方法 this.readMap(dInput); byte zerofill = dInput.readByte();// 讀出文件補(bǔ)零個(gè)數(shù) System.out.println("補(bǔ)零個(gè)數(shù)》》》>>>>"+zerofill); // 文件輸出流 OutputStream out = new FileOutputStream("F:\\JAVA\\解壓后.txt"); String transString = "";//接收用于匹配碼表中哈夫曼編碼的字符串 String waiteString = "";//接收截取哈夫曼編碼后剩余的字符串 /***********************************不耗內(nèi)存的方法*****************************************/ int readCode = input.read();//從壓縮文件中讀出一個(gè)數(shù)據(jù) int size = input.available(); for(int j=0; j<=size; j++){ System.out.println("readCodereadCodereadCode》》>>>>"+readCode); waiteString += this.changeIntToBinaryString(readCode);//將讀到的整數(shù)轉(zhuǎn)化為01字符串 for(int i=0; i<waiteString.length(); i++){ //將從文件中讀出的01串一個(gè)一個(gè)字節(jié)的截取添加與碼表中的哈夫曼編碼比較 transString += waiteString.charAt(i); if(this.searchHC(transString, out)){ // waiteString = waiteString.substring( i+1 ); transString = ""; } } waiteString = ""; readCode = input.read(); if(j == size-1){ break; } } /************************************處理最后一個(gè)字***************************************/ // int lastByte = input.read(); String lastWord = this.changeIntToBinaryString(readCode); waiteString = transString + lastWord.substring(0, 8-zerofill); transString = ""; for(int i=0; i<waiteString.length(); i++){ //將從文件中讀出的01串一個(gè)一個(gè)字節(jié)的截取添加與碼表中的哈夫曼編碼比較 transString += waiteString.charAt(i); if(this.searchHC(transString, out)){ // waiteString = waiteString.substring( i+1 ); transString = ""; } } // this.searchHC(transString, out); /***********************************隊(duì)列法,耗內(nèi)存****************************************/ // int readCode = input.read();//從壓縮文件中讀出一個(gè)數(shù)據(jù) // List<Character> list = new ArrayList<Character>(); // while(-1 != readCode){ // String str = this.changeIntToBinaryString(readCode); // for(int i=0; i<str.length(); i++){ // list.add(str.charAt(i)); // } // readCode = input.read(); // } // for(int j=0; j<list.size()-zerofill; j++){ // transString += list.get(j); // if(this.searchHC(transString, out)){ // transString = ""; // } // } /*****************************************************************************************/ input.close(); out.close(); } catch (IOException e) { e.printStackTrace(); } } /** * 將從文件中讀到的01 串與碼表中的哈夫曼編碼比較 * 若在碼表中含有與之對(duì)應(yīng)的哈夫曼編碼則將碼表中對(duì)應(yīng)的 * 數(shù)據(jù)寫入解壓文件,否則不寫入 * @param str 從文件中讀到的01 字符串 * @param out 文件輸出流 * @return 若寫入文件返回true,否則返回false * @throws IOException 寫入發(fā)生錯(cuò)誤時(shí)拋出異常 */ private boolean searchHC(String str, OutputStream out) throws IOException{ Set<Integer> set = code_Map.keySet(); Iterator<Integer> p = set.iterator(); while (p.hasNext()) { Integer key = p.next();//獲得碼表中的字節(jié)數(shù)據(jù) String hfmCode = code_Map.get(key);//獲得哈夫曼編碼 if(hfmCode.equals(str)){ out.write(key); return true; } } return false; } /** * 根據(jù) "除2取余,逆序排列"法 * 將十進(jìn)制數(shù)字轉(zhuǎn)化為二進(jìn)制字符串 * @param a 要轉(zhuǎn)化的數(shù)字 * @return 01 字符串 */ private String changeIntToBinaryString(int a) { int b = a; int count = 0; //記錄 a 可轉(zhuǎn)化為01串的個(gè)數(shù),在不夠8為時(shí)便于補(bǔ)零 String str = "";// 逆序二進(jìn)制字符串 String exmpStr = "";// 順序二進(jìn)制字符串 while (a != 0) { b = a/2; if (a % 2 != 0) { str += 1; } else { str += 0; } a = b; count++; } //不夠8位的地方補(bǔ)零 for (int i = 0; i < 8 - count; i++) { str += 0; } //將轉(zhuǎn)化后的二進(jìn)制字符串正序 for (int j = 7; j >= 0; j--) { System.out.print(str.charAt(j)); exmpStr += str.charAt(j); } System.out.println("轉(zhuǎn)化后的字符串>>>>>>>>>"+exmpStr); return exmpStr; } }
相關(guān)文章
Java Web使用Html5 FormData實(shí)現(xiàn)多文件上傳功能
這篇文章主要介紹了Java Web使用Html5 FormData實(shí)現(xiàn)多文件上傳功能,需要的朋友可以參考下2017-07-07Java在運(yùn)行時(shí)識(shí)別類型信息的方法詳解
這篇文章主要給大家介紹了關(guān)于Java在運(yùn)行時(shí)識(shí)別類型信息的相關(guān)資料,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考借鑒,下面來一起看看吧2019-01-01IntelliJ?IDEA?2020.2?全家桶及以下版本激活工具大全【喜訊】
這篇文章主要介紹了IntelliJ?IDEA?2020.2?全家桶及以下版本激活工具大全【喜訊】,本文給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2020-09-09Spring Boot 整合mybatis 與 swagger2
之前使用springMVC+spring+mybatis,總是被一些繁瑣的xml配置,還經(jīng)常出錯(cuò),下面把以前的一些ssm項(xiàng)目改成了spring boot + mybatis,相對(duì)于來說優(yōu)點(diǎn)太明顯了,具體內(nèi)容詳情大家通過本文學(xué)習(xí)吧2017-08-08解決Eclipse的Servers視圖中無法添加Tomcat6/Tomcat7的方法
這篇文章主要介紹了解決Eclipse的Servers視圖中無法添加Tomcat6/Tomcat7的方法的相關(guān)資料,需要的朋友可以參考下2017-02-02Spring中的ContextLoaderListener詳細(xì)解析
這篇文章主要介紹了Spring中的ContextLoaderListener詳細(xì)解析,在web容器即Tomact容器啟動(dòng)web應(yīng)用即servlet應(yīng)用時(shí),會(huì)觸發(fā)ServletContextEvent時(shí)間,這個(gè)事件會(huì)被ServletContextListener監(jiān)聽,需要的朋友可以參考下2023-12-12