利用赫夫曼编码对文件进行压缩和解压

1.用来构造赫夫曼树的一个类
Node.java


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

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

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

	public void list(Node root) {
		if (root != null) {
			root.before();
		} else {
			System.out.println("树为空,不能遍历!");
		}
	}

	public void before() {
		System.out.println(this);
		if (this.left != null) {
			this.left.before();
		}
		if (this.right != null) {
			this.right.before();
		}
	}

	@Override
	public int compareTo(Node o) {

		return this.value - o.value;
	}
}

2.利用赫夫曼编码进行压缩的一个方法类
zip.java

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

public class Zip {
	//用来存放赫夫曼编码
	static HashMap <Byte, String>huffmanCodes=new HashMap<Byte, String>();
	//进行字符串拼接
	static StringBuilder stringBuilder=new StringBuilder();
	
	//5.把所有方法封装到一块方便调用
	public byte[] hufumanzip(byte[] bytes) {
		List<Node> hufullList = hufullList(bytes);
		//根据hufullList创建哈夫曼树
		Node findNode = findNode(hufullList);
		//对应的哈夫曼编码
		HashMap<Byte, String> codes = getCodes(findNode);
		//根据赫夫曼编码,得到压缩后的哈夫曼编码
		byte[] zip = zip(bytes, codes);
		return zip;
		
	}
	
	// 1.接收一个字符数组并统计每个字符出现的次数
	public List<Node> hufullList(byte[] bytes) {
		// 创建链表用于存储结点
		ArrayList<Node> list = new ArrayList<Node>();
		// 创建哈希表存放带有键值的结点
		HashMap<Byte, Integer> map = new HashMap<>();
		for (byte b : bytes) {
			// 获取键值的value值
			Integer count = map.get(b);
			// 对value值进行计数
			if (count == null) {
				map.put(b, 1);
			} else {
				map.put(b, count+1);
			}
		}
		// 对哈希表进行遍历取出键值对
		for (Map.Entry<Byte, Integer> entry : map.entrySet()) {
			list.add(new Node(entry.getKey(), entry.getValue()));
		}
		return list;
	}
	

	// 2.转化成哈夫曼树
	public Node findNode(List<Node> list) {
		while (list.size() > 1) {
			Collections.sort(list);
			Node leftNode = list.get(0);
			Node rightNode = list.get(1);
			Node root = new Node(null, leftNode.value + rightNode.value);
			root.left = leftNode;
			root.right = rightNode;
			list.remove(leftNode);
			list.remove(rightNode);
			list.add(root);
		}
		return list.get(0);

	}
	
	//3.重载getCodes()方法方便调用
	public HashMap<Byte, String> getCodes(Node node) {
		if (node==null) {
			return null;
		}
		getCodes(node, "", stringBuilder);
		return huffmanCodes;
	}
	
	//3.创建哈夫曼表每个字符用它对应的哈夫曼编码表示(每个叶子结点的路径码)
	public void getCodes(Node node,String code,StringBuilder stringBuilder) {
		StringBuilder builder=new StringBuilder(stringBuilder);
		builder.append(code);
		if (node!=null) {//为空结点
			//判断是叶子节点还是非叶子结点
			if (node.data==null) {
				//向左递归处理
				getCodes(node.left, "0", builder);
				//向右递归
				getCodes(node.right, "1", builder);
			}else {
				//表示到达叶子结点,放入map中
				huffmanCodes.put(node.data, builder.toString());
			}
		}
	}
	
	//4.对哈夫曼编码转化为byte字符进行压缩
	/**
	 * @param bytes 原始字符串对应的byte[]
	 * @param hufullmancodes 生成哈夫曼编码map
	 * @return 返回赫夫曼编码后的byte[];
	 */	
	public byte[] zip(byte[] bytes,HashMap <Byte, String>hufullmancodes) { 
		StringBuilder stringBuilder=new StringBuilder();
		for (byte b : bytes) {
			stringBuilder.append(hufullmancodes.get(b));
		}
		//System.out.println("赫夫曼编码"+stringBuilder);
		//return bytes;
		//统计返回的byte[] 的长度
		int len;
		if (stringBuilder.length()%8==0) {
			len=stringBuilder.length()/8;
		}else {
			len=stringBuilder.length()/8+1;
		}
		//创建压缩后的byte数组
		byte[] by=new byte[len];
		//因为一个八位的二进制数构成一个byte字节
		int index=0;//记录byte[]数组的下标
		for (int i = 0; i < stringBuilder.length(); i+=8) {
			String strByte;
			if (i+8>stringBuilder.length()) {//不够八位
				strByte=stringBuilder.substring(i);	
			}else {
				
				strByte=stringBuilder.substring(i,i+8);
			}
			//将strByte转成一个byte,放入到by;
			by[index]=(byte) Integer.parseInt(strByte,2);
			index++;
		}
		return by;
	}
}

3.对文件解压的一个方法类
Unzip.java


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

public class Unzip {

	/**
	 * @param flag     标志是否需要补高位如果是true,表示需要,false表示否.最后一个字符无需补高位
	 * @param b传入的byte
	 * @return 是该b对应的二进制的字符串(按补码返回)
	 */
	public String byteTobitString(boolean flag, byte b) {
		// 使用变量保存b
		int temp = b; // 将b转成int
		// 如果是正数我们还存在补高位
		if (flag) {
			temp |= 256; // 按位或256 1 0000 0000 | 0000 0001=> 1 0000 0001
		}
		String str = Integer.toBinaryString(temp);// 返回的是temp对应的二进制的补码
		if (flag) {
			return str.substring(str.length() - 8);
		} else {
			return str;
		}
	}

	/**
	 * @param huffmancodes 赫夫曼编码表map 32=01, 97=100, 100=11000, 117=11001, 101=1110,
	 * @param huffmanbytes 赫夫曼编码得到的字节数组 -88, -65, -56, -65, -56, -65, -55, 77, -57,
	 *                     6, -24, -14,
	 * @return
	 */
	public byte[] decode(HashMap<Byte, String> huffmancodes, byte[] huffmanbytes) {
		// 先得到huffmanbytes对应的二进制的字符串
		StringBuilder stringBuilder = new StringBuilder();
		// 将byte数组转成二进制的字符串
		for (int i = 0; i < huffmanbytes.length; i++) {
			byte b = huffmanbytes[i];
			// 判断是不是最后一个字节
			boolean flag = (i == huffmanbytes.length - 1);
			stringBuilder.append(byteTobitString(!flag, b));
		}
		// System.out.println(stringBuilder.toString());
		// 把字符串按照指定的赫夫曼编码进行解码
		// 把赫夫曼编码来进行调换,因为反向查询
		HashMap<String, Byte> hashMap = new HashMap<String, Byte>();
		for (Map.Entry<Byte, String> entry : huffmancodes.entrySet()) {
			hashMap.put(entry.getValue(), entry.getKey());
		}
		// System.out.println(hashMap);
		// 创建集合,存放byte;
		List<Byte> list = new ArrayList<>();
		// i可以理解为是索引,扫描stringBuilder
		for (int i = 0; i < stringBuilder.length();) {
			int count = 1; // 计数器
			boolean flag = true;
			Byte b = null;

			while (flag) {
				// 取出一个‘1’‘0’
				String key = stringBuilder.substring(i, i + count);// i不动,让count移动,直到匹配到一个字符
				b = hashMap.get(key);
				if (b == null) { // 说明没有匹配到
					count++;
				} else {
					flag = false;
				}
			}
			list.add(b);
			i += count; // i直接移动到count
		}
		// for循环结束后,我们list中就存放了所有的字符
		// 把list中的数据放入到byte[] 并返回
		byte[] b = new byte[list.size()];
		for (int i = 0; i < b.length; i++) {
			b[i] = list.get(i);
		}
		return b;

	}
}

4.调用各种流读取文件并进行压缩
Filezip.java


import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.ObjectOutputStream;

public class Filezip {

	public void filezip(String srcFile, String dstFile) throws Exception {
	
				//创建文件输入流
				FileInputStream is = new FileInputStream(srcFile);		
			//创建byte数组;
			byte[] bytes=new byte[is.available()];		
			is.read(bytes);
			Zip zip = new Zip();
			byte[] hufumanzip = zip.hufumanzip(bytes);
			//创建文件输出流
			FileOutputStream os=new FileOutputStream(dstFile);	
			//创建一个对象输出流
			ObjectOutputStream oos=new ObjectOutputStream(os);	
			//把赫夫曼编码后的字节数组写入压缩文件
			oos.writeObject(hufumanzip);
			//把赫夫曼编码写入压缩文件
			oos.writeObject(Zip.huffmanCodes);
			is.close();
			os.close();
			oos.close();
	}
}

5.调用各种流对文件进行解压
Fileunzip.java


import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.ObjectInputStream;
import java.util.HashMap;

public class Fileunzip {
	public void unzip(String zipfile, String srcfile) throws Exception {
		FileInputStream is = new FileInputStream(zipfile);
		ObjectInputStream os = new ObjectInputStream(is);
		// 读取赫夫曼字节数组
		byte[] huffmanBytes = (byte[]) os.readObject();
		// 读取赫夫曼编码表
		HashMap<Byte, String> huffmanCodes = (HashMap<Byte, String>) os.readObject();
		Unzip jieya = new Unzip();
		// 解码
		byte[] decode = jieya.decode(huffmanCodes, huffmanBytes);
		// 创建输出流
		FileOutputStream fos = new FileOutputStream(srcfile);
		// 把文件写入到指定位置
		fos.write(decode);
		is.close();
		os.close();
		fos.close();
	}
}

6.测试类
Filetext.java

public class Filetext {

	public static void main(String[] args) throws Exception {
		Filezip filezip = new Filezip();
		String s1="E:\\1.txt";
		String s2="E:\\2.zip";
		filezip.filezip(s1, s2);
		System.out.println("压缩成功");
//		Fileunzip fileunzip = new Fileunzip();
//		String sr1="E:\\2.zip";
//		String sr2="E:\\3.txt";
//		fileunzip.unzip(sr1, sr2);
//		System.out.println("解压成功");
	}
}

注意:
1.个别文件进行压缩效果可能不明显(可能会变大)
2.以上代码不能对文件夹进行压缩
3.压缩成zip文件时是不能通过电脑上的压缩软件进行解压,必须通过我们代码来解压。
4.该代码可以用来对文件进行加密处理

代码有啥可以优化和改进的地方,欢迎各位大神指出!

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值