Huffman树实现解压缩文件

代码是根据尚硅谷韩顺平老师写的,发现有错误,先放着留着学习吧。package com.attongji.huffmancode;import java.io.FileInputStream;import java.io.FileOutputStream;import java.io.InputStream;import java.io.ObjectInputStream;impo...
摘要由CSDN通过智能技术生成

代码是根据尚硅谷韩顺平老师写的,发现有错误,先放着留着学习吧。

package com.attongji.huffmancode;

import java.io.FileInputStream;
import java.io.FileOutputStream;
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;
import java.util.Map.Entry;

public class HuffmanCode {

	public static void main(String[] args) {
		zipFile("D:\\eclipseworkspace\\DataStructure\\img\\白竞.txt",
				"D:\\eclipseworkspace\\DataStructure\\img\\test.zip");
		System.out.println("压缩成功!");

		unZipFile("D:\\eclipseworkspace\\DataStructure\\img\\test.zip",
				"D:\\eclipseworkspace\\DataStructure\\testunzip.txt");
		System.out.println("解压成功!");
	}

	public static void unZipFile(String zipFile, String dstFile) {
		InputStream is = null;
		ObjectInputStream ois = null;
		OutputStream os = null;

		try {

			is = new FileInputStream(zipFile);
			ois = new ObjectInputStream(is);
			byte[] huffmanbytes = (byte[]) ois.readObject();
			Map<Byte, String> huffmanCodes = (Map<Byte, String>) ois.readObject();

			byte[] bytes = decode(huffmanCodes, huffmanbytes);

			os = new FileOutputStream(dstFile);
			os.write(bytes);
		} catch (Exception e) {

			System.out.println(e.getMessage());
		} finally {
			try {
				os.close();
				ois.close();
				is.close();
			} catch (Exception e2) {
				System.out.println(e2.getMessage());
			}
		}
	}

	/**
	 * 
	 * @param srcFile 文件输入路径
	 * @param dstFile 压缩后文件输出路径
	 */
	public static void zipFile(String srcFile, String dstFile) {
		OutputStream os = null;
		ObjectOutputStream oos = null;
		FileInputStream is = null;

		try {
			is = new FileInputStream(srcFile);
			byte[] b = new byte[is.available()];
			// 将srcFile按字节读进b中
			is.read(b);

			byte[] huffmanCode = huffmanzip(b);

			os = new FileOutputStream(dstFile);
			// 因为是字节数组,为了方便不采用FileOutPutStream直接写入
			// byte[]中,而是采用ObjectOutPutStream
			oos = new ObjectOutputStream(os);

			oos.writeObject(huffmanCode);
			// 为了方便之后解压,应该把huffman编码表也写入文件
			oos.writeObject(huffmanCodes);

		} catch (Exception e) {

			System.out.println(e.getMessage());
		} finally {

			try {
				is.close();
				oos.close();
				os.close();
			} catch (Exception e2) {
				System.out.println(e2.getMessage());
			}

		}

	}

	/**
	 * 前序遍历构造好的huffman树
	 * 
	 * @param root
	 */
	public static void preOrder(Node root) {
		if (root != null) {
			root.preOrder();
		} else {
			return;
		}
	}

	public static byte[] decode(Map<Byte, String> huffmanCodes, byte[] huffmanbytes) {
		StringBuilder builder = new StringBuilder();
		for (int i = 0; i < huffmanbytes.length; i++) {
			byte b = huffmanbytes[i];
			// 判断是否到了最后一个字符
			boolean flag = (i == huffmanbytes.length - 1);
			builder.append(byteToString(!flag, b));
		}
		// 因为是将转化(压缩后得到的)huffman编码解码回原来的字符数组(原始信息)
		// 则应该将huffman表转置为键(key)为huffman编码,值(value)为字符
		Map<String, Byte> map = new HashMap<String, Byte>();
		for (Map.Entry<Byte, String> entry : huffmanCodes.entrySet()) {
			map.put(entry.getValue(), entry.getKey());
		}

		List<Byte> list = new ArrayList<>();
		// 逐个地取出拼接出的二进制string类型的huffman编码,(最终的huffman编码其实是不是二进制的)
		// 然后用去转置后的huffman编码表中找,如果有匹配的字符,则继续找,没有的话指针继续前进
		// 这里之所以用list存放解码回来的字符,是因为不知道字符到底有多少,是不确定长度的。
		for (int i = 0; i < builder.length();) {
			int count = 1;
			boolean flag = true;
			Byte b = null;

			while (flag) {
				String key = builder.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;
	}

	/**
	 * 该方法是将b(就是转化为huffmancode形式的字符)转化回原来的二进制形式
	 * 
	 * @param flag
	 * @param b
	 * @return
	 */
	public static String byteToString(boolean flag, byte b) {
		int temp = b;
		if (flag) {
			// 256的二进制字节码为1000 0000,如果flag为true,
			// 说明是中间需要拼接的字符
			// 中间需要凭借的字符则应注意对高位补全,例如如果中间出现了正数1
			// 其二进制就为1,不足8位,与256位或之后为1000 0001;
			// 又比如中间出现了6,其二进制为110,也不足8位,则需要补全
			// 与256位或之后位1000 0110
			temp |= 256;
		}
		String str = Integer.toBinaryString(temp);
		if (flag) {
			return str.substring(str.length() - 8);
		} else {
			return str;
		}
	}

	// 使用一个方法将上述方法进行封装
	public static byte[] huffmanzip(byte[] bytes) {
		// Step.1 将字符数组按照出现频次作为权重封装为Node,并将Node放入List集合
		List<Node> nodes = getNodes(bytes);
		// Step.2 根据list创建huffman树
		Node root = createHuffmanTree(nodes);
		// Step.3 根据huffman树的叶子节点创建出各个叶子节点的权重路径huffman编码表
		Map<Byte, String> map = getCodes(root);
		// step.4 根据创建出来的huffman编码表去和待处理的bytes数组对比从而得到最终的压缩后的结果
		byte[] resultByte = zip(bytes, map);
		return resultByte;

	}

	/**
	 * 给定一个byte数组和huffman编码数组,将这个byte数组进行 压缩。相当于对下面的几个方法进行封装。
	 * 
	 * @param bytes
	 * @param huffmanCodes
	 * @return
	 */
	public static byte[] zip(byte[] bytes, Map<Byte, String> huffmanCodes) {
		// 先利用一个Stringbuilder对byte数组中的元素对应的huffman编码进行拼接
		StringBuilder builder = new StringBuilder();
		for (byte b : bytes) {
			builder.append(huffmanCodes.get(b));
		}

		// 接下来的操作是上面拼接得到的huffman编码再简化一下
		// 每8个编码为一组重新装到一个byte[]数组中
		int len;
		if (builder.length() % 8 == 0) {
			len = builder.length() / 8;
		} else {
			len = builder.length() / 8+ 1;
		}

		byte[] huffmanCodeBytes = new byte[len];
		int index = 0;
		for (int i = 0; i < builder.length(); i += 8) {
			String strByte;
			if (i + 8 > builder.length()) {
				strByte = builder.substring(i);
			} else {
				strByte = builder.substring(i, i + 8);
			}
			// 这一步是将占用内存较大的不可用编码转化为压缩率更高的最终huffman编码的关键
			// 采用了包装类Integer.parseInt()方法
			huffmanCodeBytes[index++] = (byte) Integer.parseInt(strByte,2);
		}

		return huffmanCodeBytes;
	}

	/**
	 * 为了调用方便,重载该方法
	 */
	public static Map<Byte, String> getCodes(Node root) {
		if (root == null) {
			return null;
		}
		getCodes(root.leftNode, "0", builder);
		getCodes(root.rightNode, "1", builder);

		return huffmanCodes;

	}

	// 提前创建一个map用来存储哥哥字符对应的huffman码
	private static Map<Byte, String> huffmanCodes = new HashMap<>();
	// 初始利用huffman树叶子节点的带权路径拼接出的huffman编码是0、1组成的不可读或者长度
	// 及内存占用较大的不善编码,这种编码的压缩率较差,所以需要每8位转化为占用内存更加小的
	// 可用编码。
	private static StringBuilder builder = new StringBuilder();

	/**
	 * 该方法的目的是将传入的构建好的huffman树的根节点得到 其叶子节点对应的huffman编码
	 * 
	 * @param root:传入的huffman树的根节点
	 * @param code:用来标识左子路还是右子路
	 * @param builder:用于拼接路径(即对应的huffman编码)
	 */
	public static void getCodes(Node root, String code, StringBuilder builder) {
		// 利用非叶子节点的data为null这个特点作为是否接着进行递归调用
		// 的凭据
		StringBuilder builder2 = new StringBuilder(builder);
		builder2.append(code);
		if (root.data == null) {
			getCodes(root.leftNode, "0", builder2);
			getCodes(root.rightNode, "1", builder2);
		} else {
			huffmanCodes.put(root.data, builder2.toString());
		}
	}

	/**
	 * 将存放字符的数组包装成node之后装入List中,这样方便之后HuffmanTree的创建
	 * 
	 * @param bytes
	 * @return
	 */
	public static List<Node> getNodes(byte[] bytes) {
		// 该map是用来统计每个字符在数组中出现的次数
		Map<Byte, Integer> map = new HashMap<Byte, Integer>();
		for (byte b : bytes) {
			Integer count=map.get(b);
			if (count == null) {
				map.put(b, 1);
			} else {
				map.put(b, count+1);
			}
		}
		// 创建一个list来存放根据字符(key)、出现次数(值,也就是权值)
		// 包装的Node类
		List<Node> nodes = new ArrayList<>();

		for (Entry<Byte, Integer> entry : map.entrySet()) {
			nodes.add(new Node(entry.getKey(), entry.getValue()));
		}

		return nodes;
	}

	/**
	 * 根据传入的node组成的集合创建huffman树
	 * 
	 * @param nodes
	 * @return
	 */
	public static Node createHuffmanTree(List<Node> nodes) {
		// 逻辑上list每挤出两个node,又添加进一个node
		// 所以当list的尺寸大于等于2就可以接着进行huffman二叉树的创建
		while (nodes.size() > 1) {
			Collections.sort(nodes);
			Node n1 = nodes.get(0);
			Node n2 = nodes.get(1);

			Node newNode = new Node(null, n1.weight + n2.weight);
			newNode.leftNode = n1;
			newNode.rightNode = n2;

			nodes.remove(n1);
			nodes.remove(n2);

			nodes.add(newNode);
		}

		return nodes.get(0);
	}

}

class Node implements Comparable<Node> {
	Byte data;// 只有叶子节点才有,就是字符对应的数字
	int weight;// 权重值,最初指的是每个字符出现的次数
	Node leftNode;
	Node rightNode;

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

	@Override
	public int compareTo(Node o) {
		// TODO Auto-generated method stub
		return this.weight - o.weight;
	}

	/**
	 * 每个节点都有自己前序遍历的逻辑实现
	 */
	public void preOrder() {
		System.out.println(this);
		if (this.leftNode != null) {
			this.leftNode.preOrder();
		}
		if (this.rightNode != null) {
			this.rightNode.preOrder();
		}
	}

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

}

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值