本身動手寫壓縮軟件

        想吃項記的燴麪了……這小地方的可難吃。html

        看完了《裸婚時代》,我以爲冬瓜說得對,劉易陽不敢面對本身的真實感覺。
        女生都是感性的,工做永遠不如生活重要。
java

        不是坐在一塊兒就叫團隊,不是不吵架就叫態度。node

        昨晚一哥們說求k小直接能夠進行k次冒泡,我都想不起來,我想到的是區間快拍,說明基礎很重要。算法

        對原版本的算法有很大修改,我的認爲原版代碼的命名不太規範,讀起來比較累,本程序主要是界面參考了參考資料,讀寫文件徹底是本身搞的(原文字節流,個人字符流),不過原文不可壓縮漢字(原版壓縮效率高一些),個人能夠,壓縮比40%左右(文件大小作除)。
數據結構

一.概念引入app

        優先級隊列(Priority Queue)又叫最小堆。Huffman( 哈夫曼 ) 算法在上世紀五十年代初提出來了,它是一種無損壓縮方法,在壓縮過程當中不會丟失信息熵,並且能夠證實Huffman 算法在無損壓縮算法中是最優的。Hufmann coding 是最古老,以及最優雅的數據壓縮方法之一。它是以最小冗餘編碼爲基礎的,即若是咱們知道數據中的不一樣符號在數據中的出現頻率,咱們就能夠對它用一種佔用空間最少的編碼方式進行編碼,這種方法是,對於最頻繁出現的符號制定最短長度的編碼,而對於較少出現的符號給較長長度的編碼。哈夫曼編碼能夠對各類類型的數據進行壓縮,但在本文中咱們僅僅針對字符進行編碼。函數

        哈夫曼編碼是一種前綴碼,即任一個字符的編碼都不是其餘字符編碼的前綴。從咱們的編碼過程當中能夠很容易看到這一點,由於全部字符都是哈夫曼樹中的葉子節點,這個特徵可以保證解碼的惟一性,不會產生歧義。筆者認爲這樣說或許更好理解:任意字符的編碼的全部前綴都不是其它字符的編碼,並且字符和編碼是滿單射,爲後面value反查key打基礎。能夠看出,出現頻率最高的字符,使用最短的編碼,字符出現頻率越低,編碼逐漸增加。這樣不一樣字符在文檔中出現的頻率差別越大,則壓縮效果將會越好。所以字符出現頻率越大,咱們但願給它的編碼越短(在哈夫曼樹中的深度越淺),即咱們但願更晚的將它所在的樹進行合併。反之,字符頻率越低,咱們但願給他的編碼最長(在哈夫曼樹中的深度越深),所以咱們但願越早的將它所在的樹進行合併。所以,哈夫曼編碼的貪心策略就體如今合併樹的過程當中,咱們每一次老是選擇根節點頻率最小的兩個樹先合併,這樣就能達到咱們所但願的編碼結果。this

        例:假設一個文本文件中只包含7個字符{A,B,C,D,E,F,G},這7個字符在文本中出現的次數爲{5,24,7,17,34,5,13},求這些字符的哈夫曼編碼。編碼

                                image

         仔細看上圖,發現樹的深度不必定是n(節點數),內節點個數是(n-1)。右子樹的頻率爲17的內節點並無和字符節點的D(頻率17)合併,先和G(頻率13)合併了(用的是內節點頻率爲17的,不是字符節點,數據結構課本上也是這麼搞的),而後左子樹的D、B合併。spa

        爲何能壓縮?壓縮的時候當咱們遇到了文本中的1 、 2 、 3 、 4 幾個字符的時候,咱們不用原來的存儲,而是轉化爲用它們的 01 串來存儲不久是能減少了空間佔用了嗎。(什麼 01 串不是比原來的字符還多了嗎?怎麼減小?)你們應該知道的,計算機中咱們存儲一個 int 型數據的時候通常式佔用了32 個 01 位,由於計算機中全部的數據都是最後轉化爲二進制位去存儲的。因此,想一想咱們的編碼不就是隻含有 0 和 1 嘛,所以咱們就直接將編碼按照計算機的存儲規則用位的方法寫入進去就能實現壓縮了。好比:1這個數字,用整數寫進計算機硬盤去存儲,佔用了32個二進制位, 而若是用它的哈弗曼編碼去存儲,只有幾個二進制位,效果顯而易見。不過筆者認爲這是採用字節流,那爲何我用字符流也能實現壓縮呢?由於寫入的時候都是一個字節,原來可能一、二、4字節。

二.Java實現

import java.awt.Container;
import javax.swing.JMenu;
import javax.swing.JMenuBar;
import javax.swing.JMenuItem;
import javax.swing.JPanel;
public class HuffmanMain extends javax.swing.JFrame{
	
	static javax.swing.JTextArea textarea=null;
	public static void main(String[] args) {
		new HuffmanMain().display();
	}
	
	public void display(){
		this.setSize(400,400);
		this.setTitle("火星十一郎解壓壓縮軟件");
		this.setLayout(new java.awt.FlowLayout());
		//添加菜單欄
		this.setJMenuBar(getMenu());
		this.setVisible(true);
	}
	
	public JMenuBar getMenu(){
		//菜單欄、菜單項、菜單條目
		JMenuBar mb = new JMenuBar();
		
		//菜單項
		JMenu file = new JMenu("File");
		JMenu contact = new JMenu("Contact");
		//菜單條目
		JMenuItem Code = new JMenuItem("Code");
		JMenuItem Decode = new JMenuItem("Decode");
		JMenuItem exit = new JMenuItem ("Exit");
		JMenuItem qq = new JMenuItem ("About");
		
		//加入文件菜單項中
		file.add(Code);
		file.addSeparator();
		file.add(Decode);
		//在這加了個分割線
		file.addSeparator();
		file.add(exit);
		contact.add(qq);
		
		//添加菜單事件
		Action action = new Action();
		Code.addActionListener(action);
		Decode.addActionListener(action);
		exit.addActionListener(action);
		qq.addActionListener(action);
		
		//放上去
		mb.add(file);
		mb.add(contact);
		return mb;
	}
}
class Node implements Comparable<Node>{
	
   public char data;
   public int times;
   public Node lchild;
   public Node rchild;
   
   public Node(char data,int times){
	   this.data=data;
	   this.times=times;
   }
	   
	public int getData() {
		return data;
	}
	public void setData(char data) {
		this.data = data;
	}
	public int getTimes() {
		return times;
	}
	public void setTimes(int times) {
		this.times = times;
	}
	public Node getLchild() {
		return lchild;
	}
	public void setLchild(Node lchild) {
		this.lchild = lchild;
	}
	public Node getRchild() {
		return rchild;
	}
	public void setRchild(Node rchild) {
		this.rchild = rchild;
	}
	
	public int compareTo(Node o) {
		Node other = o;
		int temp = times-other.times;
		if(temp>0){
			return 1;
		}else {		
			return 0;
		}
	}
}

---------------------------------------------------------------------------------------------------------

import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import javax.swing.JFileChooser;
import javax.swing.JOptionPane;
import javax.swing.filechooser.FileNameExtensionFilter;
public class Action implements ActionListener {
	public void actionPerformed(ActionEvent e) {
		String command = e.getActionCommand();
		//3個if是並列的,由於可能屢次壓縮、解壓
		if ("Code".equals(command)) {
			
			System.out.println("進行壓縮");
			// 文件選擇
			JFileChooser Chooser = new JFileChooser();
			int t = Chooser.showOpenDialog(null);// 彈出文件選擇框
			if (t == Chooser.APPROVE_OPTION) {// 若是點擊的是肯定
				// 獲得文件的絕對路徑
				String path = Chooser.getSelectedFile().getAbsolutePath();
				HuffmanCode code = new HuffmanCode(path);
				code.readFile();// 讀取文件
				code.writeFile();// 寫出壓縮文件
			}
		}
		if ("Decode".equals(command)) {
			
			System.out.println("解壓縮");
			// 顯示打開的窗口
			JFileChooser chooser = new JFileChooser();
			//參數:文件描述(顯示的)和文件類型
			FileNameExtensionFilter filter = new FileNameExtensionFilter(
					"hxsyl壓縮文件", "hxsyl");
			chooser.setFileFilter(filter);
			int returnVal = chooser.showOpenDialog(null);
			if (returnVal == chooser.APPROVE_OPTION) {
				String path = chooser.getSelectedFile().getAbsolutePath();// 獲得壓縮文件的路徑
				HuffmanDecode decode = new HuffmanDecode(path);
				decode.decode();// 解壓縮文件
			}
		}
		if ("Exit".equals(command)) {
			
			System.exit(0);
		}
		if ("About".equals(command)) {
			
			JOptionPane.showConfirmDialog(null, "By 791909235@qq.com", "做者",
					JOptionPane.YES_OPTION, JOptionPane.QUESTION_MESSAGE);
		}
	}
}

---------------------------------------------------------------------------------------

import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.FileReader;
import java.io.FileWriter;
import java.util.HashMap;
import java.util.Hashtable;
import java.util.Iterator;
import java.util.Map;
import java.util.PriorityQueue;
import java.util.Scanner;
public class HuffmanCode {
	private String path;// 文件的輸入絕對路徑
	Node root = null;// 定義的根節點
	private Map<Character,Integer> mm = new Hashtable<Character, Integer>();
	
	//大小:大小寫字母、數字、漢字,就開100吧
	private Map<Character,String> mapCode = new Hashtable<Character, String>();
	public HuffmanCode() {
		
	}
	
	public HuffmanCode(String path) {
		//頻率清零 初始化
		this.path = path;
		mapCode.clear();
	}
	//我想起了單例模式,纔想起這種方法來在另外一個java文件裏由於一個java文件裏的成員變量
	//注意不是方法,不然直接new 類調用就好
	public Map<Character,String> returnMap() {
		return this.mapCode;
	}
	public void readFile() {
		try {
			System.out.println("---------------讀入文件--------------");
			//把「\」換成「/」,實際上不必
			path.replaceAll("\\\\", "/");
			FileReader fr = new FileReader(path);
			BufferedReader br = new BufferedReader(fr);//有readline
			String str = "";
			mm.clear();
			while ((str=br.readLine())!=null) {
				System.out.println("讀入了:"+str);
				char[] ch = str.toCharArray();
				for(int i=0; i<ch.length; i++) {
					//加上if判斷就能夠防止NullPointer異常了
					if(mm.get(ch[i])!=null) {
						mm.put(ch[i], mm.get(ch[i])+1);
					}else {
						mm.put(ch[i], 1);
					}
				}
			}
			System.out.println("---------文件讀入結束-------------");
			// 構建哈弗曼樹
			createHfmTree();
			// 遞歸生成編碼,root是成員函數
			genenateHFMCode(root, "");
			br.close();
			fr.close();
		} catch (Exception e) {
			e.printStackTrace();
		}
	}
	public void writeFile() {
		try {
			System.out.println("------------寫入文件---------------");
			path.replaceAll("\\\\", "/");
			FileReader fr = new FileReader(path);
			BufferedReader br = new BufferedReader(fr);//有readline
			//第二個參數true表示追加
			BufferedWriter bw = new BufferedWriter(new FileWriter(path + ".hxsyl",true));
			String str = "";
			while ((str=br.readLine())!=null) {
				char[] ch = str.toCharArray();
				for(int i=0; i<ch.length; i++) {
					bw.write(mapCode.get(ch[i]));
				}
			}
			//刷新該流的緩衝,br沒有該方法
			bw.flush();
			System.out.println("------------文件寫入完畢---------------");
		} catch (Exception ef) {
			ef.printStackTrace();
		}
	}
	//廣搜建樹
	public void createHfmTree() { 
		//裏面是Node
		PriorityQueue<Node> nodeQueue = new PriorityQueue<Node>();
		// 把全部的節點都加入到 隊列裏面去
		Iterator<Character> iter = mm.keySet().iterator();
		while(iter.hasNext()) {
			char key = iter.next();
			if (mm.get(key)!= 0) {//實際上這個判斷不必,由於Map裏沒的都是null
				Node node = new Node(key, mm.get(key));
				nodeQueue.add(node);// 加入節點
			}
		}
		//不是不爲空,最後是一個節點
		while (nodeQueue.size() > 1) {
			Node min1 = nodeQueue.poll();
			Node min2 = nodeQueue.poll();
			/*
			 * data搞爲‘$’,說明是內節點
			 * 權值和就是data值爲$的節點的times值之和
			 * 使用優先隊列算權值就是基於此
			 */
			Node result = new Node('$', min1.times + min2.times);
			result.lchild = min1;
			result.rchild = min2;
			nodeQueue.add(result);
		}
		root = nodeQueue.peek(); // 獲得根節點
	}
	//遞歸生成Hfm編碼
	public void genenateHFMCode(Node root, String s) {
		if (null == root) {
			return ;
		}
		//hfm節點所有是葉子節點
		if ((root.lchild == null) && (root.rchild == null)) {
			//root是Node
			System.out.println("節點" + root.data + "編碼" + s);
			mapCode.put(root.data,s);
		}
		if (root.lchild != null) {// 左0 右 1
			genenateHFMCode(root.lchild, s + '0');
		}
		if (root.rchild != null) {
			genenateHFMCode(root.rchild, s + '1');
		}
	}
}

----------------------------------------------------------------------------------------------

import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.FileReader;
import java.io.FileWriter;
import java.util.ArrayList;
import java.util.Hashtable;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;
/*
 * 爲何輸出漢子提示呢?
 * 由於實際上須要保存在日誌裏的.
 * 
 * 解碼須要所有讀入文件,不能一行一行,由於
 */
public class HuffmanDecode {
	private String path;
	Map<Character,String> mapCode = new Hashtable<Character, String>();
	
	public HuffmanDecode(String path) {
		this.path = path;
		//想了很久想到的方法,哈哈
		this.mapCode = new HuffmanCode().returnMap();
	}
	
	//經過
	private static char valueGetKey(Map<Character,String> map,StringBuffer value) {
	    Set set = map.entrySet();
	    Iterator it = set.iterator();
	    char ch='$';//表示沒有找到,實際不必,由於是哈夫曼編碼是單射
	    while(it.hasNext()) {
	      Map.Entry entry = (Map.Entry)it.next();
	      if(entry.getValue().equals(value)) {
	    	  //轉換爲char報錯
	        ch = (Character)entry.getKey();
	        return ch;
	      }
	    }
	    //加這個是爲了避免讓編譯器報錯,緣由如上
	    return ch;
	  }
	//解壓縮
	public void decode() {
		try {
			path.replaceAll("\\\\", "/");
			FileReader fr = new FileReader(path);
			BufferedReader br = new BufferedReader(fr);//有readline
			//第二個參數true表示追加
			int index = path.lastIndexOf(".hxsyl");
			path = path.substring(0,index);
			BufferedWriter bw = new BufferedWriter(new FileWriter(path,true));
			StringBuffer sb = new StringBuffer("");
			String str = "";
			
			while ((str=br.readLine())!=null) {
				sb.append(str);
			}
			//刷新該流的緩衝,br沒有該方法
			bw.flush();
			System.out.println(sb);
			System.out.println("------------解碼文件---------------");
			StringBuffer waitSB = new StringBuffer("");
			for(int i=0; i<sb.length(); i++) {
				if(mapCode.containsValue(waitSB)) {
					char ch = valueGetKey(mapCode, waitSB);
					bw.write(ch);
					waitSB.delete(0, waitSB.length());
					if(null==waitSB) {
						break;
					}
					
				}else {
					waitSB.append(sb.charAt(i));
				}
			}
			System.out.println("------------解碼完畢---------------");
			
		} catch (Exception e) {
			e.printStackTrace();
		}
	}
}

        運行界面以下:

imageimage

三.結束語

         處理IO過程當中我發現以Reader結尾的都是read單個字符,以stream結尾的都是read字節。如何從現有字符讀入呢?能夠用StringReader或者System.in(str)。

        壓縮也能實現信息隱藏,把壓縮文件命名爲常見格式好比txt,只有有該解壓軟件的才能打開。本身寫的,你懂得,bug在所不免,若您發現,還請告知,我們共同進步。

       參考文獻:http://www.cppblog.com/biao/archive/2010/12/04/135457.html

相關文章
相關標籤/搜索