利用huffman编码对文本文件进行压缩与解压(java实现)

利用huffman编码对文本文件进行压缩与解压

输入:一个文本文件
输出:压缩后的文件

算法过程:
(1)统计文本文件中每个字符的使用频度
(2)构造huffman编码
(3)以二进制流形式压缩文件

采用哈夫曼编码进行文件的压缩和解压,主要原理是通过huffman编码来表示字符,出现次数多的编码短,出现次数少的编码长,这样整体而言,所需的总的比特位是最少的。但是当大部分字符出现的频率都差不多时,huffman压缩的压缩率就会很低。先统计出文件中各个字符出现的次数;构建哈夫曼树,生成每个字符对应的编码,然后将编码写入压缩文件中;解压缩是将压缩后的文件翻译过来,根据哈夫曼编码找到对应的字符。

package compress_file;

import java.io.BufferedReader;
import java.io.FileReader;
import java.util.*;

class HaffmanTree {//哈夫曼树类
	public static final int MAXVALUE = 1000;// 最大权值
	public int nodeNum; // 叶子结点个数

	public HaffmanTree(int n) {
		this.nodeNum = n;
	}

	public void haffman(char[] names, int[] weight, HaffNode[] nodes) {//构造哈夫曼树,weight 权值,nodes 叶子节点
		int n = this.nodeNum;
		int m1, m2, x1, x2;// m1,m2,表示最小的两个权值,x1、x2表示最小两个权值对应的编号,m1表示最小,m2表示次小
		for (int i = 0; i < 2 * n - 1; i++) {// 初始化所有的结点,对应有n个叶子结点的哈夫曼树,有2n-1个结点
			HaffNode temp = new HaffNode();
			// 初始化n个叶子结点,就是输入的节点。0、1、2、3是叶子节点也是输入的节点
			if (i < n) {
				temp.name = names[i];
				temp.weight = weight[i];
			} else {
				temp.name = ' ';
				temp.weight = 0;
			}
			temp.parent = 0;
			temp.flag = 0;
			temp.leftChild = -1;
			temp.rightChild = -1;
			nodes[i] = temp;
		}
		
		for (int i = 0; i < n - 1; i++) {// 初始化n-1个非叶子结点,n-1表示要循环n-1次求的n-1个数
			m1 = m2 = MAXVALUE;
			x1 = x2 = 0;
			// 求得这n-1个数时,每次都是从0到n+i-1,并且flag=0的,flag=1表示已经加入到二叉树。
			// 以下是找出权值最小的2个
			for (int j = 0; j < n + i; j++) {
				if (nodes[j].weight < m1 && nodes[j].flag == 0) {
					// m1,x1初始值为第一个元素,后面如果比m1要小,则m1指向更小的,原来m1指向的现在由m2指向,
					// 如果后面比m1大比m2小,则m2指向这个比m1大比m2小的,
					// 也就是说m1指向最小的,m2指向第2小的。
					m2 = m1;
					x2 = x1;
					m1 = nodes[j].weight;
					x1 = j;
				} else if (nodes[j].weight < m2 && nodes[j].flag == 0) {
					m2 = nodes[j].weight;
					x2 = j;
				}
			}
			// 将权值最小的2个组合成一个二叉树
			nodes[x1].parent = n + i;
			nodes[x2].parent = n + i;
			nodes[x1].flag = 1;
			nodes[x2].flag = 1;
			nodes[n + i].weight = nodes[x1].weight + nodes[x2].weight;
			nodes[n + i].leftChild = x1;
			nodes[n + i].rightChild = x2;
		}
	}
	
	
	public void haffmanCode(HaffNode[] nodes, Code[] haffCode) {//哈弗曼编码
		int n = this.nodeNum;
		Code code = new Code(n);
		int child, parent;

		for (int i = 0; i < n; i++) {// 给前面n个输入的节点进行编码
			code.start = n - 1;
			code.weight = nodes[i].weight;
			code.name = nodes[i].name;
			child = i;
			parent = nodes[child].parent;
			while (parent != 0) {// 从叶子节点向上走来生成编码。
				if (nodes[parent].leftChild == child) {
					code.bit[code.start] = 0;
				} else {
					code.bit[code.start] = 1;
				}

				code.start--;
				child = parent;
				parent = nodes[child].parent;
			}

			Code temp = new Code(n);
			for (int j = code.start + 1; j < n; j++) {
				temp.bit[j] = code.bit[j];
			}
			temp.weight = code.weight;
			temp.name = code.name;
			temp.start = code.start;
			haffCode[i] = temp;
		}
	}
	
	public void decode(String res, HaffNode[] nodes){
		int index = 2*this.nodeNum-2;// 从根节点出发
		for(int k = 0; k < res.length(); k++){// 依次读取哈夫曼编码,遇0则遍历当前节点的左孩子
			if(res.charAt(k)=='1'){// 遇1则遍历当前节点的右孩子
				index = nodes[index].rightChild;
				// 如果当前节点的右孩子为-1是证明其为叶子节点直接输出字符(由于哈夫曼树只存在出度为0或2的节点,因此只判断右孩子即可)
				if(nodes[index].rightChild==-1){
					System.out.print(nodes[index].name);
					index = 2*this.nodeNum-2;// 重新从根节点出发
				}
			}else{// 遇1则遍历当前节点的右孩子
				index = nodes[index].leftChild;
				// 如果当前节点的右孩子为-1是证明其为叶子节点直接输出字符(由于哈夫曼树只存在出度为0或2的节点,因此只判断右孩子即可)
				if(nodes[index].rightChild==-1){
					System.out.print(nodes[index].name);
					index = 2*this.nodeNum-2;// 重新从根节点出发
				}
			}
		}
	}
}

class HaffNode {// 哈夫曼树的结点类
	public char name; // 字符名
	public int weight; // 权值
	public int parent; // 双亲
	public int flag; // 是否为叶子节点的标志
	public int leftChild; // 左孩子
	public int rightChild; // 右孩子

	public HaffNode() {
		
	}
}

class Code {// 哈夫曼编码类
	public int[] bit; // 编码的数组
	public int start; // 编码的开始下标
	public int weight; // 权值
	public char name; // 字符名
	public Code(int n) {
		bit = new int[n];
		start = n - 1;
	}
}

public class Compress_file {
	public char[] names;
	public int[] weights;
	
	public static void main(String[] args) throws Exception {
		Compress_file test = new Compress_file();
		while(true){
			String s = test.readfile();// 读取文本中的一行字符串
			Map<Character, Integer> map = test.getCharMaps(s);// 统计文本中不同字符出现的频率
			test.names = new char[map.size()];		// 创建数组用于储存字符及出现频率
			test.weights = new int[map.size()];
			int i = 0;
			Set set = map.keySet();// 将map转化为set 并将统计的字符及其对应的频次放入到数组中
			for (Iterator iter = set.iterator(); iter.hasNext();) {
				char key = (Character)iter.next();
				test.names[i] = key;
				test.weights[i] = map.get(key);
				i++;
			}
			System.out.println("文本中的字符及其出现的频率:");
			for (int j = 0; j < test.names.length; j++) {// 打印字符
				System.out.print(test.names[j] + " ");
			}
			System.out.println();
			for (int j = 0; j < test.weights.length; j++) {// 打印频次
				System.out.print(test.weights[j] + " ");
			}
			System.out.println();
	
			HaffmanTree haffTree = new HaffmanTree(map.size());// 建立哈夫曼树
			HaffNode[] nodes = new HaffNode[2 * map.size() - 1];
			Code[] codes = new Code[map.size()];
			haffTree.haffman(test.names, test.weights, nodes);// 构造哈夫曼树
			haffTree.haffmanCode(nodes, codes);// 生成哈夫曼编码
			System.out.println("哈夫曼编码:");// 打印哈夫曼编码
			for (int k = 0; k < map.size(); k++) {
				System.out.print("Name=" + codes[k].name + " Weight=" + codes[k].weight + " Code=");
				for (int j = codes[k].start + 1; j < map.size(); j++) {
					System.out.print(codes[k].bit[j]);
				}
				System.out.println();
			}
			System.out.print("原始文件:");
			System.out.println(s);
			System.out.print("压缩后的文件:");
			String res = s;
			String bit = "";
			// 根据哈夫曼编码表替换相应字符
			for(int k = 0; k < test.names.length; k++){
				for (int j = codes[k].start + 1; j < map.size(); j++) {
					bit += codes[k].bit[j];
				}
				res = res.replace(String.valueOf(test.names[k]), bit);
				bit = "";
			}
			System.out.println(res);
			System.out.print("解压后的文件:");
			haffTree.decode(res, nodes);
			System.out.println();
		}
	}

	public String readfile() throws Exception {
		System.out.println("请输入测试文件的编号(1~5):");
		Scanner sc = new Scanner(System.in);
		int num = sc.nextInt();
		BufferedReader br = new BufferedReader(new FileReader("D:/Program Files (x86)/MyStudy/src/compress_file/input/input_assign03_0" + num + ".dat"));
		return (br.readLine()); 		
	}

	public Map<Character, Integer> getCharMaps(String s) {//统计文本中不同字符对应的出现频率
		Map<Character, Integer> map = new HashMap<Character, Integer>();
		for (int i = 0; i < s.length(); i++) {
			Character c = s.charAt(i);
			Integer count = map.get(c);
			map.put(c, count == null ? 1 : count + 1);
		}
		return map;
	}
}

根据提示信息输入要测试的文件的编号(1-5),输入要压缩的文件的编号后程序开始运行,依次输出文本文件中的字符和字符出现的频率、哈夫曼编码结果、原始文件内容、压缩后的文件内容、解压后的文件内容。下图为第1组测试数据的结果。
在这里插入图片描述

发布了32 篇原创文章 · 获赞 23 · 访问量 1万+

猜你喜欢

转载自blog.csdn.net/qq_35443700/article/details/102996371
今日推荐