數據結構Java06【赫夫曼樹、概述、原理分析、代碼實現(數據壓縮、創建編碼表、解碼、壓縮文件、解壓文件)】

目   錄

P40-4.13 赫夫曼樹概述

P41-4.14 創建赫夫曼樹的流程分析

P42-4.15 代碼實現創建赫夫曼樹

1、Node.java

2、TestHuffmanTree.java

P43-4.16 赫夫曼編碼原理分析

1、通信領域中信息的處理1-定長編碼

2、通信領域中信息的處理2-非定長編碼

3、通信領域中信息的處理3-赫夫曼編碼

P44-4.17 數據壓縮之創建赫夫曼樹

1、Map從入門到性能分析

2、思路分析

3、代碼實現

3.1、Node.java

3.2、TestHuffmanCode44.java

P45-4.18 數據壓縮之創建編碼表&編碼

1、TestHuffmanCode45.java

P46-4.19 使用赫夫曼編碼進行解碼

P47-4.20 使用赫夫曼編碼壓縮文件

P48-4.21 使用赫夫曼編碼解壓文件


P40-4.13 赫夫曼樹概述

最優二叉樹(赫夫曼樹):它是n個帶權葉子結點構成的所有二叉樹中,帶權路徑長度最小的二叉樹。 

P41-4.14 創建赫夫曼樹的流程分析

赫夫曼樹

P42-4.15 代碼實現創建赫夫曼樹

Alt + Shift + S【快捷鍵】

1、Node.java

package demo9;

public class Node implements Comparable<Node> {
	int value;
	Node left;
	Node right;

	public Node(int value) {
		this.value = value;
	}

	@Override
	public int compareTo(Node o) {
		return -(this.value - o.value);// 加“-”,大在前
	}

	// Alt + Shift + S【快捷鍵】
	@Override
	public String toString() {
		return "Node [value=" + value + "]";
	}
}

2、TestHuffmanTree.java

package demo9;

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

public class TestHuffmanTree {
	public static void main(String[] args) {
		int[] arr = { 3, 7, 8, 29, 5, 11, 23, 14 };
		Node node = createHuffmanTree(arr);
		System.out.println(node);
	}

	// 創建赫夫曼樹
	public static Node createHuffmanTree(int[] arr) {
		// 1、先使用數組中所有的元素創建若干個二叉樹,(只有一個節點)
		List<Node> nodes = new ArrayList<>();// 創建數組集合,用來存二叉樹
		for (int value : arr) { // for循環的另外一種表達,遍歷數組元素用的
			nodes.add(new Node(value));
		}
		// Collections.sort(nodes);
		// System.out.println(nodes);
		// 循環處理!!!
		while (nodes.size() > 1) { // 只有1顆樹時, 結束處理!
			// 2、排序【排序前提:Node可以排序!】
			Collections.sort(nodes);// 需要Node實現接口
			// 3、取出來權值最小的兩個二叉樹
			// 3.1、取出最權值最小的二叉樹
			Node left = nodes.get(nodes.size() - 1);
			// 3.2、取出最權值次小的二叉樹
			Node right = nodes.get(nodes.size() - 2);
			// 4、創建一顆新的二叉樹
			Node parent = new Node(left.value + right.value);
			// 5、把取出來的兩個二叉樹移除
			nodes.remove(left);
			nodes.remove(right);
			// 6、放入原來的二叉樹集合中
			nodes.add(parent);
		}
		return nodes.get(0);
	}

}

P43-4.16 赫夫曼編碼原理分析

1、通信領域中信息的處理1-定長編碼

赫夫曼編碼的運用

計算機並不能直接將字符發給別人。

先將 字符 按照某個標準(ASCII)轉換爲數字。【c-->99,a-->97】

【計算機只能識別0、1串,每一個數字都是一個byte(字節)】

再將 數字 轉換爲 8位的字節,

【1100011--->99--->c】

將01串發給另一臺計算機B,計算機B按 8位 劃分 01串,
將 01串 轉換爲 十進制數字,再將 十進制數字 根據 ASCII 轉換爲 字母。

 

定長編碼方式,傳輸效率 極低!!!

固定長度~浪費空間 ==> 非定長編碼

https://tool.lu/hexconvert/

2、通信領域中信息的處理2-非定長編碼

先對計算串進行處理,統計字符串中每個字符出現了多少次,

令經常出現的字符長度長一些,不經常出現的字符長度短一些,【不按照ASCII進行處理】

次數當作權值構造哈夫曼!!!

3、通信領域中信息的處理3-赫夫曼編碼

字符出現次數少的,在樹底;

字符出現次數多的,在樹頂。 

 

左0 右1

將 字符串 按照 赫夫曼編碼表 進行編碼,

按照 編碼表 進行 解碼,無損壓縮!

P44-4.17 數據壓縮之創建赫夫曼樹

1、Map從入門到性能分析

Java---Map從入門到性能分析

https://blog.csdn.net/weixin_44949135/article/details/106862811

2、思路分析

1、統計字符出現次數,並排序

2、根據排序結果,將字符轉換爲節點

3、將節點轉換爲赫夫曼樹

 字符 可以轉換爲 byte。

    // 節點 要存儲 字符(data) 與 權值(weight-字符出現的次數)
    // 每個字符作爲一個節點
    // 按照 權值(weight-字符出現的次數) 進行 排序
    Byte data;// 存儲節點代表的字符/英文字符可轉byte
    int weight;// 權值
    Node left;// 左節點
    Node right;// 右節點
    // 創建赫夫曼樹的時候,新創建的節點,無data,只有weight   data可爲空

不 對 字符串 進行 編碼,對 字符串的byte數組 進行 編碼。 

所有的數據 都 可以 轉換爲 byte數組。【將 數據 轉換爲 byte數組--->文件操作!!!】

3、代碼實現

3.1、Node.java

package demo10;

public class Node implements Comparable<Node> {
	// 節點 要存儲 字符(data) 與 權值(weight-字符出現的次數)
	// 每個字符作爲一個節點
	// 按照 權值(weight-字符出現的次數) 進行 排序
	Byte data;// 存儲節點代表的字符/英文字符可轉byte
	int weight;// 權值
	Node left;// 左節點
	Node right;// 右節點
	// 創建赫夫曼樹的時候,新創建的節點,無data,只有weight|data可爲空

	public Node(Byte data, int weight) {
		this.data = data;
		this.weight = weight;
	}

	@Override
	public String toString() {
		return "Node [data=" + data + ", weight=" + weight + "]";
	}

	@Override
	public int compareTo(Node o) {
		return o.weight - this.weight;//倒序
	}
}

3.2、TestHuffmanCode44.java

package demo10;

import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

public class TestHuffmanCode44 {

	public static void main(String[] args) {
		String msg = "can you can a can as a can canner can a can.";
		/**
		 * 赫夫曼編碼 不直接對字符串進行編碼。而是編碼字符串的byte數組。 
		 * 所有的數據都可以轉換爲byte數組。文件壓縮使用byte數組。
		 * 文件讀取出來是一個byte數組。所以,將數據轉換爲byte數組。
		 */
		byte[] bytes = msg.getBytes();

		byte[] b = huffmanZip(bytes);// 獲取使用赫夫曼編碼壓縮後的byte數組
	}

	/**
	 * 進行赫夫曼編碼壓縮的方法
	 * 
	 * @param bytes
	 * @return
	 */
	private static byte[] huffmanZip(byte[] bytes) {
		// 1、先統計每一個byte出現的次數,並放入一個集合中【getNodes()】
		List<Node> nodes = getNodes(bytes);
		// 2、創建一棵赫夫曼樹
		Node tree = createHuffmanTree(nodes);
		System.out.println(tree);
		System.out.println(tree.right);
		System.out.println(tree.left);
		// 3、創建一個赫夫曼編碼表
		// 4、編碼
		return null;
	}
	
	/**
	 * 1、把byte數組轉爲node集合
	 * 
	 * @param bytes
	 * @return
	 */
	private static List<Node> getNodes(byte[] bytes) {
		List<Node> nodes = new ArrayList<>();
		// 存儲每一個byte出現了多少次。
		Map<Byte, Integer> counts = new HashMap<>();
		// 統計每一個byte出現的次數
		for (byte b : bytes) {
			Integer count = counts.get(b);
			if (count == null) {
				counts.put(b, 1);
			} else {
				counts.put(b, count + 1);
			}
		}
		//System.out.println(counts);
		//{32=11, 97=11, 114=1, 99=7, 115=1, 117=1, 101=1, 121=1, 110=8, 46=1, 111=1}
		// 32-空格、97-a
		// 把每一個鍵值對轉爲一個node對象
		for (Map.Entry<Byte, Integer> entry : counts.entrySet()) {
			nodes.add(new Node(entry.getKey(), entry.getValue()));
		}
		return nodes;
	}
	
	/**
	 * 2、創建赫夫曼樹
	 * 
	 * @param nodes
	 * @return
	 */
	private static Node createHuffmanTree(List<Node> nodes) {
		while (nodes.size() > 1) {
			// 排序
			Collections.sort(nodes);
			// System.out.println(nodes);
			// 取出兩個權值最低的二叉樹
			Node left = nodes.get(nodes.size() - 1);
			Node right = nodes.get(nodes.size() - 2);
			// 創建一顆新的二叉樹
			Node parent = new Node(null, left.weight + right.weight);
			// 把之前取出來的兩顆二叉樹設置爲新創建的二叉樹的子樹
			parent.left = left;
			parent.right = right;
			// 把前面取出來的兩顆二叉樹刪除
			nodes.remove(left);
			nodes.remove(right);
			// 把新創建的二叉樹放入集合中
			nodes.add(parent);
		}
		return nodes.get(0);
	}

}

P45-4.18 數據壓縮之創建編碼表&編碼

1、TestHuffmanCode45.java

package demo10;

import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

public class TestHuffmanCode45 {

	public static void main(String[] args) {
		String msg = "can you can a can as a can canner can a can.";
		/**
		 * 赫夫曼編碼 不直接對字符串進行編碼。而是編碼字符串的byte數組。 
		 * 所有的數據都可以轉換爲byte數組。文件壓縮使用byte數組。
		 * 文件讀取出來是一個byte數組。所以,將數據轉換爲byte數組。
		 */
		byte[] bytes = msg.getBytes();

		byte[] b = huffmanZip(bytes);// 獲取使用赫夫曼編碼壓縮後的byte數組
		System.out.println(bytes.length);
		System.out.println(b.length);
	}

	/**
	 * 進行赫夫曼編碼壓縮的方法
	 * 
	 * @param bytes
	 * @return
	 */
	private static byte[] huffmanZip(byte[] bytes) {
		// 1、先統計每一個byte出現的次數,並放入一個集合中【getNodes()】
		List<Node> nodes = getNodes(bytes);
		// 2、創建一棵赫夫曼樹
		Node tree = createHuffmanTree(nodes);
		System.out.println(tree);
		System.out.println(tree.right);
		System.out.println(tree.left);
		// 3、創建一個赫夫曼編碼表
		Map<Byte, String> huffCodes = getCodes(tree);
		System.out.println(huffCodes);
		// 4、編碼
		byte[] b = zip(bytes, huffCodes);
		return b;
	}
	
	/**
	 * 1、把byte數組轉爲node集合
	 * 
	 * @param bytes
	 * @return
	 */
	private static List<Node> getNodes(byte[] bytes) {
		List<Node> nodes = new ArrayList<>();
		// 存儲每一個byte出現了多少次。
		Map<Byte, Integer> counts = new HashMap<>();
		// 統計每一個byte出現的次數
		for (byte b : bytes) {
			Integer count = counts.get(b);
			if (count == null) {
				counts.put(b, 1);
			} else {
				counts.put(b, count + 1);
			}
		}
		//System.out.println(counts);
		//{32=11, 97=11, 114=1, 99=7, 115=1, 117=1, 101=1, 121=1, 110=8, 46=1, 111=1}
		// 32-空格、97-a
		// 把每一個鍵值對轉爲一個node對象
		for (Map.Entry<Byte, Integer> entry : counts.entrySet()) {
			nodes.add(new Node(entry.getKey(), entry.getValue()));
		}
		return nodes;
	}
	
	/**
	 * 2、創建赫夫曼樹
	 * 
	 * @param nodes
	 * @return
	 */
	private static Node createHuffmanTree(List<Node> nodes) {
		while (nodes.size() > 1) {
			// 排序
			Collections.sort(nodes);
			// System.out.println(nodes);
			// 取出兩個權值最低的二叉樹
			Node left = nodes.get(nodes.size() - 1);
			Node right = nodes.get(nodes.size() - 2);
			// 創建一顆新的二叉樹
			Node parent = new Node(null, left.weight + right.weight);
			// 把之前取出來的兩顆二叉樹設置爲新創建的二叉樹的子樹
			parent.left = left;
			parent.right = right;
			// 把前面取出來的兩顆二叉樹刪除
			nodes.remove(left);
			nodes.remove(right);
			// 把新創建的二叉樹放入集合中
			nodes.add(parent);
		}
		return nodes.get(0);
	}
	
	// 用於臨時存儲路徑
	static StringBuilder sb = new StringBuilder();
	// 用於存儲赫夫曼編碼
	static Map<Byte, String> huffCodes = new HashMap<>();

	/**
	 * 3、根據赫夫曼樹獲取赫夫曼編碼
	 * 需要保存前面樹支的結果0、1
	 * @param tree
	 * @return
	 */
	private static Map<Byte, String> getCodes(Node tree) {
		if (tree == null) {
			return null;
		}
		getCodes(tree.left, "0", sb);
		getCodes(tree.right, "1", sb);
		return huffCodes;
	}
	
	private static void getCodes(Node node, String code, StringBuilder sb) {
		StringBuilder sb2 = new StringBuilder(sb);
		sb2.append(code);
		if (node.data == null) {
			getCodes(node.left, "0", sb2);
			getCodes(node.right, "1", sb2);
		} else {
			huffCodes.put(node.data, sb2.toString());
		}
	}
	
	/**
	 * 4、進行赫夫曼編碼
	 * 
	 * @param bytes
	 * @param huffCodes2
	 * @return
	 */
	private static byte[] zip(byte[] bytes, Map<Byte, String> huffCodes) {
		StringBuilder sb = new StringBuilder();
		// 把需要壓縮的byte數組處理成一個二進制的字符串
		for (byte b : bytes) {
			sb.append(huffCodes.get(b));
		}
		// System.out.println(sb.toString());
		// 定義長度
		int len;
		if (sb.length() % 8 == 0) {
			len = sb.length() / 8;
		} else {
			len = sb.length() / 8 + 1;
		}
		// 用於存儲壓縮後的byte
		byte[] by = new byte[len];
		// 記錄新byte的位置
		int index = 0;
		for (int i = 0; i < sb.length(); i += 8) {
			String strByte;
			if (i + 8 > sb.length()) {
				strByte = sb.substring(i);
			} else {
				strByte = sb.substring(i, i + 8);
			}
			byte byt = (byte) Integer.parseInt(strByte, 2);//二進制轉十進制
			System.out.println(strByte + " : " + byt);
			by[index] = byt;
			index++;
		}
		return by;
	}
}

P46-4.19 使用赫夫曼編碼進行解碼

package demo10;

import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

public class TestHuffmanCode45 {

	public static void main(String[] args) {
		String msg = "can you can a can as a can canner can a can.";
		/**
		 * 赫夫曼編碼 不直接對字符串進行編碼。而是編碼字符串的byte數組。 
		 * 所有的數據都可以轉換爲byte數組。文件壓縮使用byte數組。
		 * 文件讀取出來是一個byte數組。所以,將數據轉換爲byte數組。
		 */
		byte[] bytes = msg.getBytes();
		//進行赫夫曼編碼壓縮
		byte[] b = huffmanZip(bytes);// 獲取使用赫夫曼編碼壓縮後的byte數組
		System.out.println(bytes.length);
		System.out.println(b.length);
		//使用赫夫曼編碼進行解碼
		byte[] newBytes = decode(huffCodes,b);
		System.out.println(new String(newBytes));
	}

	/**
	 * 進行赫夫曼編碼壓縮的方法
	 * 
	 * @param bytes
	 * @return
	 */
	private static byte[] huffmanZip(byte[] bytes) {
		// 1、先統計每一個byte出現的次數,並放入一個集合中【getNodes()】
		List<Node> nodes = getNodes(bytes);
		// 2、創建一棵赫夫曼樹
		Node tree = createHuffmanTree(nodes);
		System.out.println(tree);
		System.out.println(tree.right);
		System.out.println(tree.left);
		// 3、創建一個赫夫曼編碼表
		Map<Byte, String> huffCodes = getCodes(tree);
		System.out.println(huffCodes);
		// 4、編碼
		byte[] b = zip(bytes, huffCodes);
		return b;
	}
	
	/**
	 * 1、把byte數組轉爲node集合
	 * 
	 * @param bytes
	 * @return
	 */
	private static List<Node> getNodes(byte[] bytes) {
		List<Node> nodes = new ArrayList<>();
		// 存儲每一個byte出現了多少次。
		Map<Byte, Integer> counts = new HashMap<>();
		// 統計每一個byte出現的次數
		for (byte b : bytes) {
			Integer count = counts.get(b);
			if (count == null) {
				counts.put(b, 1);
			} else {
				counts.put(b, count + 1);
			}
		}
		//System.out.println(counts);
		//{32=11, 97=11, 114=1, 99=7, 115=1, 117=1, 101=1, 121=1, 110=8, 46=1, 111=1}
		// 32-空格、97-a
		// 把每一個鍵值對轉爲一個node對象
		for (Map.Entry<Byte, Integer> entry : counts.entrySet()) {
			nodes.add(new Node(entry.getKey(), entry.getValue()));
		}
		return nodes;
	}
	
	/**
	 * 2、創建赫夫曼樹
	 * 
	 * @param nodes
	 * @return
	 */
	private static Node createHuffmanTree(List<Node> nodes) {
		while (nodes.size() > 1) {
			// 排序
			Collections.sort(nodes);
			// System.out.println(nodes);
			// 取出兩個權值最低的二叉樹
			Node left = nodes.get(nodes.size() - 1);
			Node right = nodes.get(nodes.size() - 2);
			// 創建一顆新的二叉樹
			Node parent = new Node(null, left.weight + right.weight);
			// 把之前取出來的兩顆二叉樹設置爲新創建的二叉樹的子樹
			parent.left = left;
			parent.right = right;
			// 把前面取出來的兩顆二叉樹刪除
			nodes.remove(left);
			nodes.remove(right);
			// 把新創建的二叉樹放入集合中
			nodes.add(parent);
		}
		return nodes.get(0);
	}
	
	// 用於臨時存儲路徑
	static StringBuilder sb = new StringBuilder();
	// 用於存儲赫夫曼編碼
	static Map<Byte, String> huffCodes = new HashMap<>();

	/**
	 * 3、根據赫夫曼樹獲取赫夫曼編碼
	 * 需要保存前面樹支的結果0、1
	 * @param tree
	 * @return
	 */
	private static Map<Byte, String> getCodes(Node tree) {
		if (tree == null) {
			return null;
		}
		getCodes(tree.left, "0", sb);
		getCodes(tree.right, "1", sb);
		return huffCodes;
	}
	
	private static void getCodes(Node node, String code, StringBuilder sb) {
		StringBuilder sb2 = new StringBuilder(sb);
		sb2.append(code);
		if (node.data == null) {
			getCodes(node.left, "0", sb2);
			getCodes(node.right, "1", sb2);
		} else {
			huffCodes.put(node.data, sb2.toString());
		}
	}
	
	/**
	 * 4、進行赫夫曼編碼
	 * 
	 * @param bytes
	 * @param huffCodes2
	 * @return
	 */
	private static byte[] zip(byte[] bytes, Map<Byte, String> huffCodes) {
		StringBuilder sb = new StringBuilder();
		// 把需要壓縮的byte數組處理成一個二進制的字符串
		for (byte b : bytes) {
			sb.append(huffCodes.get(b));
		}
		// System.out.println(sb.toString());
		// 定義長度
		int len;
		if (sb.length() % 8 == 0) {
			len = sb.length() / 8;
		} else {
			len = sb.length() / 8 + 1;
		}
		// 用於存儲壓縮後的byte
		byte[] by = new byte[len];
		// 記錄新byte的位置
		int index = 0;
		for (int i = 0; i < sb.length(); i += 8) {
			String strByte;
			if (i + 8 > sb.length()) {
				strByte = sb.substring(i);
			} else {
				strByte = sb.substring(i, i + 8);
			}
			byte byt = (byte) Integer.parseInt(strByte, 2);//二進制轉十進制
			System.out.println(strByte + " : " + byt);
			by[index] = byt;
			index++;
		}
		return by;
	}
	
	/**
	 * 使用指定的赫夫曼編碼表進行解碼
	 * 
	 * @param huffCodes2
	 * @param b
	 * @return
	 */
	private static byte[] decode(Map<Byte, String> huffCodes, byte[] bytes) {
		StringBuilder sb = new StringBuilder();
		// 把byte數組轉爲一個二進制的字符串
		for (int i = 0; i < bytes.length; i++) {
			byte b = bytes[i];
			// 是否是最後一個。
			boolean flag = (i == bytes.length - 1);
			sb.append(byteToBitStr(!flag, b));
		}
		// 把字符串按照指定的赫夫曼編碼進行解碼
		// 把赫夫曼編碼的鍵值對進行調換
		Map<String, Byte> map = new HashMap<>();
		for (Map.Entry<Byte, String> entry : huffCodes.entrySet()) {
			map.put(entry.getValue(), entry.getKey());
		}
		// 創建一個集合,用於存byte
		List<Byte> list = new ArrayList<>();
		// 處理字符串
		for (int i = 0; i < sb.length();) {
			int count = 1;
			boolean flag = true;
			Byte b = null;
			// 截取出一個byte
			while (flag) {
				String key = sb.substring(i, i + count);
				b = map.get(key);
				if (b == null) {
					count++;
				} else {
					flag = false;
				}
			}
			list.add(b);
			i += count;
		}
		// 把集合轉爲數組
		byte[] b = new byte[list.size()];
		for (int i = 0; i < b.length; i++) {
			b[i] = list.get(i);
		}
		return b;
	}
	/**
	 * 轉爲8位的字符串
	 * @param flag
	 * @param b
	 * @return
	 */
	private static String byteToBitStr(boolean flag, byte b) {
		int temp = b;
		if (flag) {
			temp |= 256;//按位或 256
		}
		String str = Integer.toBinaryString(temp);//返回int變量的二進制表示的字符串。
		if (flag) {
			return str.substring(str.length() - 8);
		} else {
			return str;
		}
	}
}

P47-4.20 使用赫夫曼編碼壓縮文件

運行項目後,選中項目,進行刷新。

package demo10;

import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.ObjectOutputStream;
import java.io.OutputStream;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

public class TestHuffmanCode45 {

	public static void main(String[] args) {
//		String msg = "can you can a can as a can canner can a can.";
//		/**
//		 * 赫夫曼編碼 不直接對字符串進行編碼。而是編碼字符串的byte數組。 
//		 * 所有的數據都可以轉換爲byte數組。文件壓縮使用byte數組。
//		 * 文件讀取出來是一個byte數組。所以,將數據轉換爲byte數組。
//		 */
//		byte[] bytes = msg.getBytes();
//		//進行赫夫曼編碼壓縮
//		byte[] b = huffmanZip(bytes);// 獲取使用赫夫曼編碼壓縮後的byte數組
//		System.out.println(bytes.length);
//		System.out.println(b.length);
//		//使用赫夫曼編碼進行解碼
//		byte[] newBytes = decode(huffCodes,b);
//		System.out.println(new String(newBytes));
		
		String src = "1.png";
		String dest = "2.zip";
		try{
			zipFile(src, dest);
		} catch(IOException e){
			e.printStackTrace();
		}
	}
	
	/**
	 * 壓縮文件
	 * 
	 * @param src
	 * @param dst
	 * @throws IOException
	 */
	public static void zipFile(String src, String dst) throws IOException {
		// 創建一個輸入流
		InputStream is = new FileInputStream(src);
		// 創建一個和輸入流指向的文件大小一樣的byte數組
		byte[] b = new byte[is.available()];//available()獲取讀的文件所有的字節個數
		// 讀取文件內容
		is.read(b);
		is.close();
		// 使用赫夫曼編碼進行編碼
		byte[] byteZip = huffmanZip(b);
		System.out.println(b.length);
		System.out.println(byteZip.length);
		// 輸出流
		OutputStream os = new FileOutputStream(dst);
		ObjectOutputStream oos = new ObjectOutputStream(os);
		// 把壓縮後的byte數組寫入文件
		oos.writeObject(byteZip);
		// 把赫夫曼編碼表寫入文件
		oos.writeObject(huffCodes);
		oos.close();
		os.close();
	}
	
	/**
	 * 進行赫夫曼編碼壓縮的方法
	 * 
	 * @param bytes
	 * @return
	 */
	private static byte[] huffmanZip(byte[] bytes) {
		// 1、先統計每一個byte出現的次數,並放入一個集合中【getNodes()】
		List<Node> nodes = getNodes(bytes);
		// 2、創建一棵赫夫曼樹
		Node tree = createHuffmanTree(nodes);
		System.out.println(tree);
		System.out.println(tree.right);
		System.out.println(tree.left);
		// 3、創建一個赫夫曼編碼表
		Map<Byte, String> huffCodes = getCodes(tree);
		System.out.println(huffCodes);
		// 4、編碼
		byte[] b = zip(bytes, huffCodes);
		return b;
	}
	
	/**
	 * 1、把byte數組轉爲node集合
	 * 
	 * @param bytes
	 * @return
	 */
	private static List<Node> getNodes(byte[] bytes) {
		List<Node> nodes = new ArrayList<>();
		// 存儲每一個byte出現了多少次。
		Map<Byte, Integer> counts = new HashMap<>();
		// 統計每一個byte出現的次數
		for (byte b : bytes) {
			Integer count = counts.get(b);
			if (count == null) {
				counts.put(b, 1);
			} else {
				counts.put(b, count + 1);
			}
		}
		//System.out.println(counts);
		//{32=11, 97=11, 114=1, 99=7, 115=1, 117=1, 101=1, 121=1, 110=8, 46=1, 111=1}
		// 32-空格、97-a
		// 把每一個鍵值對轉爲一個node對象
		for (Map.Entry<Byte, Integer> entry : counts.entrySet()) {
			nodes.add(new Node(entry.getKey(), entry.getValue()));
		}
		return nodes;
	}
	
	/**
	 * 2、創建赫夫曼樹
	 * 
	 * @param nodes
	 * @return
	 */
	private static Node createHuffmanTree(List<Node> nodes) {
		while (nodes.size() > 1) {
			// 排序
			Collections.sort(nodes);
			// System.out.println(nodes);
			// 取出兩個權值最低的二叉樹
			Node left = nodes.get(nodes.size() - 1);
			Node right = nodes.get(nodes.size() - 2);
			// 創建一顆新的二叉樹
			Node parent = new Node(null, left.weight + right.weight);
			// 把之前取出來的兩顆二叉樹設置爲新創建的二叉樹的子樹
			parent.left = left;
			parent.right = right;
			// 把前面取出來的兩顆二叉樹刪除
			nodes.remove(left);
			nodes.remove(right);
			// 把新創建的二叉樹放入集合中
			nodes.add(parent);
		}
		return nodes.get(0);
	}
	
	// 用於臨時存儲路徑
	static StringBuilder sb = new StringBuilder();
	// 用於存儲赫夫曼編碼
	static Map<Byte, String> huffCodes = new HashMap<>();

	/**
	 * 3、根據赫夫曼樹獲取赫夫曼編碼
	 * 需要保存前面樹支的結果0、1
	 * @param tree
	 * @return
	 */
	private static Map<Byte, String> getCodes(Node tree) {
		if (tree == null) {
			return null;
		}
		getCodes(tree.left, "0", sb);
		getCodes(tree.right, "1", sb);
		return huffCodes;
	}
	
	private static void getCodes(Node node, String code, StringBuilder sb) {
		StringBuilder sb2 = new StringBuilder(sb);
		sb2.append(code);
		if (node.data == null) {
			getCodes(node.left, "0", sb2);
			getCodes(node.right, "1", sb2);
		} else {
			huffCodes.put(node.data, sb2.toString());
		}
	}
	
	/**
	 * 4、進行赫夫曼編碼
	 * 
	 * @param bytes
	 * @param huffCodes2
	 * @return
	 */
	private static byte[] zip(byte[] bytes, Map<Byte, String> huffCodes) {
		StringBuilder sb = new StringBuilder();
		// 把需要壓縮的byte數組處理成一個二進制的字符串
		for (byte b : bytes) {
			sb.append(huffCodes.get(b));
		}
		// System.out.println(sb.toString());
		// 定義長度
		int len;
		if (sb.length() % 8 == 0) {
			len = sb.length() / 8;
		} else {
			len = sb.length() / 8 + 1;
		}
		// 用於存儲壓縮後的byte
		byte[] by = new byte[len];
		// 記錄新byte的位置
		int index = 0;
		for (int i = 0; i < sb.length(); i += 8) {
			String strByte;
			if (i + 8 > sb.length()) {
				strByte = sb.substring(i);
			} else {
				strByte = sb.substring(i, i + 8);
			}
			byte byt = (byte) Integer.parseInt(strByte, 2);//二進制轉十進制
			System.out.println(strByte + " : " + byt);
			by[index] = byt;
			index++;
		}
		return by;
	}
	
	/**
	 * 使用指定的赫夫曼編碼表進行解碼
	 * 
	 * @param huffCodes2
	 * @param b
	 * @return
	 */
	private static byte[] decode(Map<Byte, String> huffCodes, byte[] bytes) {
		StringBuilder sb = new StringBuilder();
		// 把byte數組轉爲一個二進制的字符串
		for (int i = 0; i < bytes.length; i++) {
			byte b = bytes[i];
			// 是否是最後一個。
			boolean flag = (i == bytes.length - 1);
			sb.append(byteToBitStr(!flag, b));
		}
		// 把字符串按照指定的赫夫曼編碼進行解碼
		// 把赫夫曼編碼的鍵值對進行調換
		Map<String, Byte> map = new HashMap<>();
		for (Map.Entry<Byte, String> entry : huffCodes.entrySet()) {
			map.put(entry.getValue(), entry.getKey());
		}
		// 創建一個集合,用於存byte
		List<Byte> list = new ArrayList<>();
		// 處理字符串
		for (int i = 0; i < sb.length();) {
			int count = 1;
			boolean flag = true;
			Byte b = null;
			// 截取出一個byte
			while (flag) {
				String key = sb.substring(i, i + count);
				b = map.get(key);
				if (b == null) {
					count++;
				} else {
					flag = false;
				}
			}
			list.add(b);
			i += count;
		}
		// 把集合轉爲數組
		byte[] b = new byte[list.size()];
		for (int i = 0; i < b.length; i++) {
			b[i] = list.get(i);
		}
		return b;
	}
	/**
	 * 轉爲8位的字符串
	 * @param flag
	 * @param b
	 * @return
	 */
	private static String byteToBitStr(boolean flag, byte b) {
		int temp = b;
		if (flag) {
			temp |= 256;//按位或 256
		}
		String str = Integer.toBinaryString(temp);//返回int變量的二進制表示的字符串。
		if (flag) {
			return str.substring(str.length() - 8);
		} else {
			return str;
		}
	}
}

P48-4.21 使用赫夫曼編碼解壓文件

package demo10;

import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.OutputStream;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

public class TestHuffmanCode45 {

	public static void main(String[] args) {
//		String msg = "can you can a can as a can canner can a can.";
//		/**
//		 * 赫夫曼編碼 不直接對字符串進行編碼。而是編碼字符串的byte數組。 
//		 * 所有的數據都可以轉換爲byte數組。文件壓縮使用byte數組。
//		 * 文件讀取出來是一個byte數組。所以,將數據轉換爲byte數組。
//		 */
//		byte[] bytes = msg.getBytes();
//		//進行赫夫曼編碼壓縮
//		byte[] b = huffmanZip(bytes);// 獲取使用赫夫曼編碼壓縮後的byte數組
//		System.out.println(bytes.length);
//		System.out.println(b.length);
//		//使用赫夫曼編碼進行解碼
//		byte[] newBytes = decode(huffCodes,b);
//		System.out.println(new String(newBytes));
		
		String src = "1.png";
		String dest = "2.zip";
//		try {
//			zipFile(src, dest);
//		} catch (IOException e) {
//			e.printStackTrace();
//		}
		try {
			unZip("2.zip", "3.png");
		} catch (Exception e) {
			e.printStackTrace();
		}
	}
	
	/**
	 * 文件的解壓
	 * 
	 * @param src
	 * @param dst
	 * @throws Exception
	 */
	public static void unZip(String src, String dst) throws Exception {
		// 創建一個輸入流
		InputStream is = new FileInputStream("2.zip");
		ObjectInputStream ois = new ObjectInputStream(is);
		// 讀取byte數組
		byte[] b = (byte[]) ois.readObject();
		// 讀取赫夫曼編碼表
		Map<Byte, String> codes = (Map<Byte, String>) ois.readObject();
		ois.close();
		is.close();
		// 解碼
		byte[] bytes = decode(codes, b);
		// 創建一個輸出流
		OutputStream os = new FileOutputStream(dst);
		// 寫出數據
		os.write(bytes);
		os.close();
	}
	
	/**
	 * 壓縮文件
	 * 
	 * @param src
	 * @param dst
	 * @throws IOException
	 */
	public static void zipFile(String src, String dst) throws IOException {
		// 創建一個輸入流
		InputStream is = new FileInputStream(src);
		// 創建一個和輸入流指向的文件大小一樣的byte數組
		byte[] b = new byte[is.available()];//available()獲取讀的文件所有的字節個數
		// 讀取文件內容
		is.read(b);
		is.close();
		// 使用赫夫曼編碼進行編碼
		byte[] byteZip = huffmanZip(b);
		System.out.println(b.length);
		System.out.println(byteZip.length);
		// 輸出流
		OutputStream os = new FileOutputStream(dst);
		ObjectOutputStream oos = new ObjectOutputStream(os);
		// 把壓縮後的byte數組寫入文件
		oos.writeObject(byteZip);
		// 把赫夫曼編碼表寫入文件
		oos.writeObject(huffCodes);
		oos.close();
		os.close();
	}
	
	/**
	 * 進行赫夫曼編碼壓縮的方法
	 * 
	 * @param bytes
	 * @return
	 */
	private static byte[] huffmanZip(byte[] bytes) {
		// 1、先統計每一個byte出現的次數,並放入一個集合中【getNodes()】
		List<Node> nodes = getNodes(bytes);
		// 2、創建一棵赫夫曼樹
		Node tree = createHuffmanTree(nodes);
		System.out.println(tree);
		System.out.println(tree.right);
		System.out.println(tree.left);
		// 3、創建一個赫夫曼編碼表
		Map<Byte, String> huffCodes = getCodes(tree);
		System.out.println(huffCodes);
		// 4、編碼
		byte[] b = zip(bytes, huffCodes);
		return b;
	}
	
	/**
	 * 1、把byte數組轉爲node集合
	 * 
	 * @param bytes
	 * @return
	 */
	private static List<Node> getNodes(byte[] bytes) {
		List<Node> nodes = new ArrayList<>();
		// 存儲每一個byte出現了多少次。
		Map<Byte, Integer> counts = new HashMap<>();
		// 統計每一個byte出現的次數
		for (byte b : bytes) {
			Integer count = counts.get(b);
			if (count == null) {
				counts.put(b, 1);
			} else {
				counts.put(b, count + 1);
			}
		}
		//System.out.println(counts);
		//{32=11, 97=11, 114=1, 99=7, 115=1, 117=1, 101=1, 121=1, 110=8, 46=1, 111=1}
		// 32-空格、97-a
		// 把每一個鍵值對轉爲一個node對象
		for (Map.Entry<Byte, Integer> entry : counts.entrySet()) {
			nodes.add(new Node(entry.getKey(), entry.getValue()));
		}
		return nodes;
	}
	
	/**
	 * 2、創建赫夫曼樹
	 * 
	 * @param nodes
	 * @return
	 */
	private static Node createHuffmanTree(List<Node> nodes) {
		while (nodes.size() > 1) {
			// 排序
			Collections.sort(nodes);
			// System.out.println(nodes);
			// 取出兩個權值最低的二叉樹
			Node left = nodes.get(nodes.size() - 1);
			Node right = nodes.get(nodes.size() - 2);
			// 創建一顆新的二叉樹
			Node parent = new Node(null, left.weight + right.weight);
			// 把之前取出來的兩顆二叉樹設置爲新創建的二叉樹的子樹
			parent.left = left;
			parent.right = right;
			// 把前面取出來的兩顆二叉樹刪除
			nodes.remove(left);
			nodes.remove(right);
			// 把新創建的二叉樹放入集合中
			nodes.add(parent);
		}
		return nodes.get(0);
	}
	
	// 用於臨時存儲路徑
	static StringBuilder sb = new StringBuilder();
	// 用於存儲赫夫曼編碼
	static Map<Byte, String> huffCodes = new HashMap<>();

	/**
	 * 3、根據赫夫曼樹獲取赫夫曼編碼
	 * 需要保存前面樹支的結果0、1
	 * @param tree
	 * @return
	 */
	private static Map<Byte, String> getCodes(Node tree) {
		if (tree == null) {
			return null;
		}
		getCodes(tree.left, "0", sb);
		getCodes(tree.right, "1", sb);
		return huffCodes;
	}
	
	private static void getCodes(Node node, String code, StringBuilder sb) {
		StringBuilder sb2 = new StringBuilder(sb);
		sb2.append(code);
		if (node.data == null) {
			getCodes(node.left, "0", sb2);
			getCodes(node.right, "1", sb2);
		} else {
			huffCodes.put(node.data, sb2.toString());
		}
	}
	
	/**
	 * 4、進行赫夫曼編碼
	 * 
	 * @param bytes
	 * @param huffCodes2
	 * @return
	 */
	private static byte[] zip(byte[] bytes, Map<Byte, String> huffCodes) {
		StringBuilder sb = new StringBuilder();
		// 把需要壓縮的byte數組處理成一個二進制的字符串
		for (byte b : bytes) {
			sb.append(huffCodes.get(b));
		}
		// System.out.println(sb.toString());
		// 定義長度
		int len;
		if (sb.length() % 8 == 0) {
			len = sb.length() / 8;
		} else {
			len = sb.length() / 8 + 1;
		}
		// 用於存儲壓縮後的byte
		byte[] by = new byte[len];
		// 記錄新byte的位置
		int index = 0;
		for (int i = 0; i < sb.length(); i += 8) {
			String strByte;
			if (i + 8 > sb.length()) {
				strByte = sb.substring(i);
			} else {
				strByte = sb.substring(i, i + 8);
			}
			byte byt = (byte) Integer.parseInt(strByte, 2);//二進制轉十進制
			System.out.println(strByte + " : " + byt);
			by[index] = byt;
			index++;
		}
		return by;
	}
	
	/**
	 * 使用指定的赫夫曼編碼表進行解碼
	 * 
	 * @param huffCodes2
	 * @param b
	 * @return
	 */
	private static byte[] decode(Map<Byte, String> huffCodes, byte[] bytes) {
		StringBuilder sb = new StringBuilder();
		// 把byte數組轉爲一個二進制的字符串
		for (int i = 0; i < bytes.length; i++) {
			byte b = bytes[i];
			// 是否是最後一個。
			boolean flag = (i == bytes.length - 1);
			sb.append(byteToBitStr(!flag, b));
		}
		// 把字符串按照指定的赫夫曼編碼進行解碼
		// 把赫夫曼編碼的鍵值對進行調換
		Map<String, Byte> map = new HashMap<>();
		for (Map.Entry<Byte, String> entry : huffCodes.entrySet()) {
			map.put(entry.getValue(), entry.getKey());
		}
		// 創建一個集合,用於存byte
		List<Byte> list = new ArrayList<>();
		// 處理字符串
		for (int i = 0; i < sb.length();) {
			int count = 1;
			boolean flag = true;
			Byte b = null;
			// 截取出一個byte
			while (flag) {
				String key = sb.substring(i, i + count);
				b = map.get(key);
				if (b == null) {
					count++;
				} else {
					flag = false;
				}
			}
			list.add(b);
			i += count;
		}
		// 把集合轉爲數組
		byte[] b = new byte[list.size()];
		for (int i = 0; i < b.length; i++) {
			b[i] = list.get(i);
		}
		return b;
	}
	/**
	 * 轉爲8位的字符串
	 * @param flag
	 * @param b
	 * @return
	 */
	private static String byteToBitStr(boolean flag, byte b) {
		int temp = b;
		if (flag) {
			temp |= 256;//按位或 256
		}
		String str = Integer.toBinaryString(temp);//返回int變量的二進制表示的字符串。
		if (flag) {
			return str.substring(str.length() - 8);
		} else {
			return str;
		}
	}
}

11111111111111111~

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