java實現哈夫曼樹的壓縮與解壓

 

下方鏈接爲用 java 實現哈夫曼樹:

https://blog.csdn.net/www_chinese_com/article/details/88070625

目錄

一、壓縮

二、解壓


一、壓縮

利用哈夫曼編碼對文件進行壓縮和解壓的大概步驟如下

(1)讀取文檔中的所有字符,在長度爲256的int型數組(以下取名爲 ascii)中記錄相應字符出現的次數,下標爲字符的ASCII碼值

(2)將數組中值不爲0的項構成二叉樹(權值爲該項的數值,字符爲該項下標ASCII碼值對應的字符),將二叉樹存儲在結點數組中

(3)將結點數字中的結點進行排序並創建一棵哈夫曼樹

(4)爲了能快速找到相應字符所對應的哈夫曼編碼,創建一個哈希表,key 值爲 ascii 數組中值不爲0的爲項的下標,value 爲該 key 值字符的哈夫曼編碼。

(5)根據文件中的字符串提取出每一個字符,在哈希表中找到對應編碼,將編碼以 String 類型進行儲存。

(6)將解壓時需要用到的數據存進壓縮文件中

(7)將得到的編碼,每 32 個字符爲一組,將其裝換爲 int 型數據存入壓縮文件中。如果得到的編碼字符數不是 32 的倍數,可以在編碼的全面或後面補 0 ,再進行上述操作,不過要將補 0 個數寫進文件中。

TreeNode root;// 樹的頭結點
File inputFile;// 要進行壓縮的文件
File outputFile;// 壓縮成的文件
String source = "";// 文件中所有的字符,用於編碼
String allCode = "";// 記錄文件中全部字符的編碼
int[] ascii = new int[256];// 存放str中各個字符出現的次數(除中文外)
ArrayList<TreeNode> array = new ArrayList<TreeNode>();// 將存在的字符的樹節點存在array中,用於排序
HashMap<String, String> map = new HashMap<String, String>();// 存儲字符及其哈夫曼編碼

/**
 * 構建一個Haffuman樹
 * 
 * @param str
 *            用於構建Huffman樹的數據
 */
public Huffman(File file) {
	this.inputFile = file;
	collect();// 記錄str中字符的出現次序
	setArrayList();// 將其存入鏈表中
	sort();// 將其進行排序
	// 將鏈表中的結點
	TreeNode tNode = null;
	while (array.size() > 1) { // 當鏈表中的結點數大於1個的時候,將結點不斷的加入到哈夫曼樹中

		tNode = product(array.get(0), array.get(1));
		array.remove(0);// 移除原本鏈表中最小的兩個結點
		array.remove(0);
		array.add(0, tNode);
		sort();// 再將新的鏈表進行排序

	}
	root = tNode;

	setHashMap(root, ""); //(4)
	getAllCode(); //(5)
	saveCode(); // (6) (7)

}

分解步驟:

(1)讀取文檔中的所有字符,在長度爲256的int型數組(以下取名爲 ascii)中記錄相應字符出現的次數,下標爲字符的ASCII碼值

	/**
	 * 堆str中的字符遍歷,ascii存放各個字符出現的次數
	 * 
	 * @param str
	 *            用於構建Huffman樹的數據
	 */
	public void collect() {

		Reader reader;
		try {
			reader = new FileReader(inputFile);
			int n = reader.read();// 讀取文件中的字符
			while (n != -1) {
				char c = (char) n;// 得到文件中字符的ASCII值
				source = source + c;
				// System.out.println("壓縮類collect中的文件中字符c = " + c + " n = " + n);
				ascii[n]++;// 將對應位置的次序加一
				n = reader.read();
			}

			reader.close();

		} catch (FileNotFoundException e) {
			e.printStackTrace();
		} catch (IOException e) {
			e.printStackTrace();
		}

(2)將數組中值不爲0的項構成二叉樹(權值爲該項的數值,字符爲該項下標ASCII碼值對應的字符),將二叉樹存儲在結點數組中

/**
	 * 將ascii中不爲0的數放進鏈表中
	 */
	public void setArrayList() {

		for (int i = 0; i < ascii.length; i++) {// 將ascii數組遍歷一遍

			if (ascii[i] != 0) { // 當其中的值不爲0
				TreeNode tNode = new TreeNode(new Node(ascii[i], (char) i + ""));
				array.add(tNode);
			}
		}

	}

(3)將結點數字中的結點進行排序並創建一棵哈夫曼樹

	/**
	 * 根據權值(鏈表中存放的數據),將ascii代表的字符從小到大進行排序
	 */
	public void sort() {
		// 認爲i前面的數據都是有序的,從第二個數開始遍歷,
		// 記住開始的位置,記住開始的結點,當遇到第一個比它小的數時,
		// 檢驗此時的位置j是否和開始的位置相同,若相同,則不用變化
		// 若不相同,刪除上一次的位置,將這個結點插入到現在這個位置
		for (int i = 1; i < array.size(); i++) {
			int min = i;
			int j;
			TreeNode tNode = array.get(i);
			for (j = i; j > 0; j--) {
				TreeNode lastNode = array.get(j - 1);
				if (tNode.node.pow > lastNode.node.pow)
					break;// 找到第一個比他小的數據時。退出循環
			}
			min = j;
			if (min != i) {// 如果,min值發生變化,即前面有比pow大的數
				array.remove(tNode);
				array.add(min, tNode);
			}
		}

	}

(4)爲了能快速找到相應字符所對應的哈夫曼編碼,創建一個哈希表,key 值爲 ascii 數組中值不爲0的爲項的下標,value 爲該 key 值字符的哈夫曼編碼。

/**
	 * 得到每一個字符對應的哈夫曼編碼,再將編碼存儲到HashMap中(遞歸)
	 * 
	 * @param tNode
	 *            根結點
	 * @param code
	 *            這個結點對應的編碼
	 */
	public void setHashMap(TreeNode tNode, String code) {

		if (tNode.left != null) {
			setHashMap(tNode.left, code + "0");

		}
		if (tNode.right != null) {

			setHashMap(tNode.right, code + "1");
		}
		if (tNode.left == null && tNode.right == null)
			map.put(tNode.node.c, code);

	}

(5)根據文件中的字符串提取出每一個字符,在哈希表中找到對應編碼,將編碼以 String 類型進行儲存。

	/**
	 * 得到文件中全部字符的全部編碼
	 */
	public void getAllCode() {
		// 將在Huffman中得到的文件的內容全部翻譯爲編碼
		for (int i = 0; i < source.length(); i++) {

			String key = source.charAt(i) + "";// 得到每一個字符
			String value = map.get(key);// 得到每一個字符的編碼
			allCode = allCode + value;
		}
	}

(6、7)儲存相關數據到壓縮文件中

/**
	 * 將哈夫曼編碼存起來
	 */
	public void saveCode() {
		// 存入字符及字符對應的編碼, 存入補零個數,存入所有字符的編碼

		outputFile = new File("src\\Tree\\SaveInformation");
		OutputStream out;
		try {
			// 創建輸出流對象
			out = new FileOutputStream(outputFile);
			DataOutputStream dout = new DataOutputStream(out);
			// 將鍵值數寫進壓縮文件中
			int size = map.size();// map中的鍵值數
			dout.writeInt(size);
			// 將HashMap寫進文件中
			Iterator iterator = map.keySet().iterator();
			while (iterator.hasNext()) {// 得到map中的內容
				String key = (String) iterator.next();
				String value = map.get(key);
				dout.writeUTF(key);
				dout.writeUTF(value);

			}

			int addZero = 0;// 添加0的個數
			if (allCode.length() % 32 != 0) {

				addZero = 32 - allCode.length() % 32;
				for (int i = 0; i < addZero; i++)// 給得到的編碼補零(在前面加上)
					allCode = "0" + allCode;
			}
			// 寫入補零個數
			dout.writeInt(addZero);
			// 每32位爲一個int進行輸入
			for (int i = 0; i < allCode.length(); i += 32) {

				char[] dst = new char[32];
				allCode.getChars(i, i + 32, dst, 0);// 得到32個字符形成的字符數組 (用這個方法不可以,出錯了)

				// for (int j = 0; j < 32; j++) { //依次取出32位編碼
				// dst[j] = allCode.charAt(i + j);
				// }

				// 對字符數組進行處理,算出int值,寫入文件
				// int型第0位爲符號位,爲避免出錯,從第1位開始
				int intCode = 0;
				for (int j = 1; j < 32; j++) { 
					intCode = intCode * 2;
					intCode = intCode + (dst[j] - '0');
				}
				//對0號位上的字符進行判斷,決定int型數據的正負
				if (dst[0] == '1')
					intCode = -intCode;
				dout.writeInt(intCode);
			}

			dout.close();

		} catch (FileNotFoundException e) {
			e.printStackTrace();
		} catch (IOException e) {
			e.printStackTrace();
		}

	}

 

二、解壓

由於在壓縮是利用數據流將數據存進文件中,因此同樣的,用數據流將數據從壓縮文件中讀取出來,再將編碼翻譯成字符串即可。

	File sourceFile;// 需要解壓的文件
	File objectFile;// 目標文件
	DataInputStream din;// 輸入流
	Writer writer;// 字符輸出流
	// 讀取文件中HashMap中的內容,得到字符及其相應的編碼,但是key值爲編碼,value值爲字符
	HashMap<String, String> map = new HashMap<String, String>();

	public Decompress(File sourceFile) {
		this.sourceFile = sourceFile;// 得到目標文件
		try {
			din = new DataInputStream(new FileInputStream(sourceFile));// 實例化輸入流對象
			objectFile = new File("src\\Tree\\DecompressingFile");// 構建解壓的目標文件
			writer = new FileWriter(objectFile);// 實例化輸出流對象
			operate(); //進行解壓的操作方法
		} catch (FileNotFoundException e) {
			e.printStackTrace();
		} catch (IOException e) {
			e.printStackTrace();
		}
	}

按存入的順序將數據讀取出來,在這裏,我們同樣需要將哈希表讀取出來並創建一張新的哈希表,但是新創建的這張哈希表,key 值爲字符的編碼,value 值爲字符,因爲此時我們是通過編碼找到字符。

/**
 * 得到HashMap表
 */
public void getHashMap() {
	try {
		int size = din.readInt();// 得到map的鍵值數
		for (int i = 0; i < size; i++) {
			String value = din.readUTF();// 得到字符
			String key = din.readUTF();// 得到字符對應的編碼
			map.put(key, value);
		}
	} catch (IOException e) {
		e.printStackTrace();
	}
}

將讀取出來的 int 數據轉變爲二進制的方法

	public int[] change(int num) {

		int[] array = new int[32];// 將byte化爲二進制後的結果存入這個數組中
		if (num < 0)
			array[0] = 1;
		else
			array[0] = 0;
		num = Math.abs(num);// 得到正數
		for (int i = 31; i > 0; i--) {
			array[i] = num % 2;
			num = num / 2;
		}
		return array;

	}

進行解壓的相關操作的方法

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章