数据结构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~

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