`
什么世道
  • 浏览: 222469 次
  • 性别: Icon_minigender_1
  • 来自: 长沙
社区版块
存档分类
最新评论

哈夫曼压缩

阅读更多

上一篇博文简要地谈了一下哈夫曼编码,此文主要说说用哈夫曼编码实现的简单的的最常用的静态无损压缩--哈夫曼压缩。

 

压缩(Compression)

(麻麻说过中西结合效果好...)

 

首先,建立哈夫曼树的结点类

 

package com.Tree20131009;


/**
 * Class node of Huffman tree
 * 
 * @author YangKang
 * 
 */
public class HfmNode {

	int data = 0; // 存储的字符
	int weight = 0;//节点数据出现的次数(权值)
	HfmNode leftChild = null;// 左边子结点
	HfmNode rightChild = null;// 右边子结点
	HfmNode parent = null;// 父结点

	/**
	 * HfmNode constructor
	 * @param data
	 * @param weight
	 */
	public HfmNode(int data, int weight) {
		this.data = data;
		this.weight = weight;
	}
}
 

 

 

接下来就是读取文件,统计字符出现的频率,并排序。

 

为了简便自定义如下存储格式基本信息

saveCode , Code []  //哈夫曼编码表

 

 

由于文件输入流中可能因为有汉字的存在,而在read()方法返回负数。故要对汉子进行相应的处理。

但暂时没有加入汉字的编码和解码。

 

	// 获取统计字节频率表
	int[] byteCount;

	/**
	 * 读取文件,统计文件中元素出现的频率
	 * 
	 * @param path 文件路径名
	 * @return 统计的字符元素和频率的数组
	 */
	public int[] readFile(String path) throws IOException {
		// 创建一个文件输入流对象
		java.io.FileInputStream fis = new java.io.FileInputStream(path);
		java.io.BufferedInputStream bis = new java.io.BufferedInputStream(fis);

		// LogTool.INFO("文件输入流创建成功");
		// LogTool.INFO(fis.toString());

		int[] byteCount = new int[256];
		// 读入 所有的文件字节
		while (bis.available() > 0) {
			int i = bis.read();
			// LogTool.INFO(new Integer(i).toString());

			if (i < 0) {// 越界处理
				// byteCount[-i + 127]++;
				LogTool.INFO("统计汉字成功");
				if ((-i + 127) > 256) { // 汉字处理
					LogTool.INFO("汉字,编码大于了256!!!");
				}
			} else {
				// 统计字符的频率
				byteCount[i]++;
				//LogTool.INFO("统计字符成功" + i);
				if (i > 255) {
					LogTool.ERROR("数组越界,大于了256!!!");
				}
			}
		}
		// 关闭文件输入流
		fis.close();
		return byteCount;
	}
 

 

 

字符频率排序的方法。

 

 鄙人图简单用的是优先队列,优先队列比较符合哈夫曼树的构造方法;其特点为数字越小,优先权越高。也可以用冒泡排序等自定义排序,一定要重写比较器中的compare(o1, o2)方法,因为自然排序只是对于基本数据类型而非对象。否则会报java.lang.ClassCastException异常(好高端的异常,之前都没有见过天真)。

 

	/**
	 * 将频率存入优先队列
	 * 
	 * @throws IOException
	 */
	public PriorityQueue<HfmNode> saveList(String scrpath) throws IOException {

		// 获取统计字节频率表
		byteCount = readFile(scrpath);
		
		for (int i = 0; i < byteCount.length; i++) {
			System.out.println("ASCII  "+i+"\t次数  "+byteCount[i]);
		}

		// 创建优先队列
		PriorityQueue<HfmNode> nodeQueue = new PriorityQueue<HfmNode>(256,
				new Comparator<HfmNode>() {

					/**
					 * 实现比较器内的方法
					 * 
					 * @param node1
					 * @param node2
					 * @return
					 */
					public int compare(HfmNode node1, HfmNode node2) {
						return node1.weight - node2.weight;
					}
				});

		// 把所有结点放入优先队列中去
		for (int i = 0; i < byteCount.length; i++) {
			if (byteCount[i] != 0) {
				// 创建哈夫曼二叉树的叶子节点
				HfmNode newnode = new HfmNode(i, byteCount[i]);
				// LogTool.INFO("叶子结点创建成功");
				nodeQueue.add(newnode);
			}
		}
		return nodeQueue;
	}
 

 

再然后,我们就要种树啦...

	/**
	 * the method of creating a huffman tree
	 * 
	 * @return root of tree
	 * @throws IOException
	 */
	private HfmNode creatTree(String srcpath) throws IOException {

		// 获取统计字节频率表
		byteCount = readFile(srcpath);

		// 放入优先队列排序
		PriorityQueue<HfmNode> nodeQueue = saveList(srcpath);

		// 构建哈夫曼树
		while (nodeQueue.size() > 1) {
			// 获取了队首的两个最小的结点,并将两结点移出队列
			HfmNode min1 = nodeQueue.poll();
			HfmNode min2 = nodeQueue.poll();
			// 父节点的权值是左右子节点的权值之和
			HfmNode parentNode = new HfmNode(0, min1.weight + min2.weight);
			// LogTool.INFO("构建哈夫曼度为2的结点成功");

			// 建立树枝和树叶之间的连接关系
			parentNode.leftChild = min1;
			min1.parent = parentNode;
			parentNode.rightChild = min2;
			min2.parent = parentNode;
			nodeQueue.add(parentNode);
		}
		// 返回父结点
		HfmNode root = nodeQueue.peek();
		return root;
	}
 

种完树后就可以收获果实啦。  不过之前,那个竹篮来装岂不是更好。

 

哈夫曼字符编码类

package com.Tree20131009;

/**
 * 哈夫曼编码类
 * 
 * @author YangKang
 * 
 */
public class Code {
	public String node;// 哈夫曼编码
	public int length; // 哈夫曼编码长度
}
 

获取字符的哈夫曼编码,并存储到编码表中

	/**
	 * 获取叶子节点的哈夫曼编码的方法
	 * 
	 * @param root 根节点
	 * @param sWeight 由权值的编码
	 */
	private void getMB(HfmNode root, String sWeight) {
		if ((root.leftChild == null) && (root.rightChild == null)) {// 叶子节点
			Code hc = new Code();
			// 得到叶子节点的哈夫曼编码
			hc.node = sWeight;
			// 读取叶子节点的长度
			hc.length = sWeight.length();
			LogTool.INFO("叶子节点:" + root.data + "  编码:" + hc.node);
			// 将叶子节点的数据保存到数组中
			saveCode[root.data] = hc;
		}

		if (root.leftChild != null) {// 左边权值0,右边权值1
			// 递归遍历
			getMB(root.leftChild, sWeight + '0');
		}
		if (root.rightChild != null) {// 左边权值0,右边权值1
			// 递归遍历
			getMB(root.rightChild, sWeight + '1');
		}
	}

 

 然后就是将文件数据以哈夫曼编码的方式存储为压缩文件

 

压缩文件存储格式如下,前面2部分的左右方便解压缩。


 前面2部分写入压缩文件就不详细说明了,重点在源文件的字符转码。

将源文件数据经过哈夫曼编码后写入到压缩文件中

	/**
	 * 将源文件按照哈弗曼编码将其写入压缩文件
	 * 
	 * @param saveCode 哈弗曼编码表
	 * @param bos 缓冲输出流
	 * @throws IOException
	 */
	private void writeScrHfmFile(Code[] saveCode, BufferedOutputStream bos)
			throws IOException {

		// 创建源文件输入流,准备读取源文件中的信息,转换成哈夫曼编码将其写入压缩文件
		FileInputStream fis = new FileInputStream(srcpath);
		BufferedInputStream bis = new BufferedInputStream(fis);
		String tranString = "";
		while (bis.available() != 0) {// 文件读取结束标志 available()
			int ch = (int) bis.read();
			if (ch < 0) {// 汉字处理
				tranString += saveCode[-ch + 127].node;
			} else {
				tranString += saveCode[ch].node;
			}
			while (tranString.length() > 8) {// 加入的编码长度大于8时,将前8位写入文件转换成byte存入文件
				int byteCode = trans2int((tranString.substring(0, 8)));
				bos.write(byteCode); // 写入文件

				// 删除前8位已存入文件的编码
				String tempString = tranString
						.substring(8, tranString.length());
				tranString = "";
				// 以8为一段,分段截取字符串
				tranString = tempString.substring(0, tempString.length());
				tempString = "";
			}
		}

		// 将剩余不足8位的用0补足8位,写入文件 ,并且将补0个数用一个byte再写入
		int izerofill = tranString.length();
		for (int i = 0; i < 8 - izerofill; i++) {
			tranString += '0';
		}

		// 补足后再写入,并写入补足0的个数
		bos.write(trans2int(tranString));
		bos.write(8 - izerofill);// 写入补足0的个数
		// 压缩文件写完结束,关闭文件输入、输出流
		bos.flush();
		fis.close();
	}
}
 
由于java中没有系统定义的二进制的写入,所以,需要自己定义字符串满8位就转换为一个byte类型并写入文件。文件流读写参数或返回值通常是int类型。所以只要转换为int类型就好。
	/**
	 * 8位01字符转换成8位二进制的byte
	 * 
	 * @param byteString 8位01字符串
	 */
	private int trans2int(String byteString) {
		char[] charCode = byteString.toCharArray();
		if (byteString.length() == 8) {
			int byteCode = ((int) (charCode[0] - 48) * 128)
					+ ((int) (charCode[1] - 48) * 64)
					+ ((int) (charCode[2] - 48) * 32)
					+ ((int) (charCode[3] - 48) * 16)
					+ ((int) (charCode[4] - 48) * 8)
					+ ((int) (charCode[5] - 48) * 4)
					+ ((int) (charCode[6] - 48) * 2)
					+ ((int) (charCode[7] - 48) * 1);
			return byteCode;
		} else {
			return 0;
		}
	}
 这样,一个简单的哈夫曼压缩就OK啦
 
接下来是解压缩(Decompression)的过程

 

获取压缩文件的译码表

 

	/**
	 * 获取哈夫曼译码表方法
	 * 
	 * @param scrpath2  压缩文件路径
	 * @return 哈夫曼译码表
	 * @throws IOException
	 */
	private Code[] getSaveCode(String scrpath2) throws IOException {

		// 创建哈夫曼译码表
		Code[] saveCode = new Code[256];

		// 打开压缩文件
		FileInputStream fis = new FileInputStream(scrpath2);
		BufferedInputStream bis = new BufferedInputStream(fis);

		// 读取哈夫曼译码表的长度
		int codeTableSize = 0; // 表的总长度
		// 将编码表转换成译码表
		for (int n = 0; bis.available() > 0 && n < 256; n++) {
			int i = bis.read();
			Code code = new Code();
			code.length = i;
			code.node = null;
			saveCode[n] = code;
			codeTableSize += i;
		}

		String codeString = ""; // 取出编码表中的编码
		int length = bis.available(); // 文件的总长度
		int codeByteSize = codeTableSize / 8 + 1;// 译码表的字节总长度

		// 读取哈夫曼编码表
		if (length > 0) {
			for (int t = 0; t < codeByteSize; t++) {
				Integer i = new Integer(bis.read());

				// 将字符串序列转化为二进制序列
				String readString = Integer.toBinaryString(i);
				if (readString.length() < 8) {// 补前导0,因为toBinaryString转化出来的二进制数会略去前导0
					readString = zeroFill(readString);// 补0机制
				}
				codeString += readString;// 收集译码表中的译码
			}
		} else {
			LogTool.INFO("文件为空,读取无效");
		}

		// 还原编码表为译码表
		for (int i = 0, j = 0; i < 256 && j < codeString.length(); i++) {
			// 截取译码表中的每个译码信息
			saveCode[i].node = codeString.substring(j, saveCode[i].length + j);
			j += saveCode[i].length;
		}
		fis.close();// 关闭输入流
		return saveCode; // 返回译码表
	}

 

 

 将压缩文件的数据部分编码与译码表一一判断,判断成立就写入哈夫曼译码之后的字符

 

 

/**
	 * 进行判断,比较对应的哈夫曼编码,遍历找出对应的原始数据
	 * 
	 * @param saveCode 哈夫曼译码表
	 * @param data 从文件中读取的数据
	 * @param codeMax  哈夫曼译码的最大长度
	 * @param codeMin 哈夫曼译码的最小长度
	 * @param bos 缓冲输出流
	 * @return 截取匹配字符后的字符串
	 * @throws IOException
	 */
	private String compareCode(Code[] saveCode, String data, int codeMax,
			int codeMin, BufferedOutputStream bos) throws IOException {

		LogTool.INFO("文件数据部分的长度:" + data.length());

		// 从最短的哈夫曼编码取起,取到最长的哈夫曼编码,跟哈夫曼编码表对比
		for (int j = codeMin; j <= codeMax && data.length() >= codeMin
				&& j <= data.length(); j++) {

			String tempData = data.substring(0, j); // 取编码
			
			for (int k = 0; k < saveCode.length; k++) {// 将取来的编码与哈夫曼编码表逐个进行比较
				
				if (!saveCode[k].node.equals("")
						&& tempData.length() == saveCode[k].length
						&& tempData.equalsIgnoreCase(saveCode[k].node)) {// 比较成立时,写入相应的原始数据,删除已匹配的编码
					LogTool.INFO("saveCode[" + k + "].node = "
							+ saveCode[k].node + "\ttempData = " + tempData
							+ "  \t比较结果:" + tempData.equalsIgnoreCase(tempData));

					bos.write(k);// 将哈夫曼对应的原码写进目标文件
					LogTool.INFO("ASCII对应  " + k);
					counter++;
					tempData = "";// 清除原始数据
					LogTool.INFO("哈夫曼编码长度 " + j + " 当前字符串长度 " + data.length());

					tempData = data.substring(j, data.length());
					data = "";// 清除原始数据
					data = tempData; // 截去已匹配的数据
					break;
				}

			}
		}
		LogTool.INFO("译码后写入的文件数据-->" + data);
		return data;
	}

 

 

接下来就是写入文件啦

 

	/**
	 * 根据哈弗曼编码表还原数据,写入目标文件
	 * 
	 * @param saveCode 哈弗曼编码表
	 * @param despath2 目标文件路径
	 * @throws IOException
	 */
	private void restore(Code[] saveCode) throws IOException {

		int codeMax = Integer.MIN_VALUE; // 自定义的值,只是为了方便程序
		int codeMin = Integer.MAX_VALUE;// 自定义的值,只是为了方便程序
		int codeLength = 0;// 统计哈夫曼码表的总长度

		// 统计哈夫曼编码表的长度,以及最大编码长度和最小编码长度
		for (int i = 0; i < saveCode.length; i++) {
			if (!saveCode[i].node.equals("")) {
				if (saveCode[i].length > codeMax) {
					codeMax = saveCode[i].length; // 获得编码最大长度
				}
				if (saveCode[i].length < codeMin) {
					codeMin = saveCode[i].length; // 获得编码最小长度
				}
				codeLength += saveCode[i].length;// 获得哈夫曼表的总长度
			}

		}
		codeLength = codeLength / 8 + 1;// 获取哈夫曼表的字节总长度
		// 打开目标文件
		FileOutputStream fos = new FileOutputStream(despath);
		BufferedOutputStream bos = new BufferedOutputStream(fos);

		// 打开压缩文件
		FileInputStream fis = new FileInputStream(scrpath);
		BufferedInputStream bis = new BufferedInputStream(fis);
		bis.skip(saveCode.length + codeLength);// 跳过哈夫曼编码表

		// 开始读取压缩数据,还原原始数据
		String data = ""; // 取出正文的编码
		int dataLength = bis.available();
		for (int t = 0; t < dataLength; t++) {
			Integer i = new Integer(bis.read());
			LogTool.INFO("解压后字符信息-->" + i.intValue());
			String readString = Integer.toBinaryString(i);
			if (readString.length() < 8) {// 补前导0,因为toBinaryString转化出来的二进制数会略去前导0
				readString = zeroFill(readString);// 补0机制
			}
			LogTool.INFO("解压后字符的二进制序列 --> " + readString);
			data += readString; // 取出正文的编码

			LogTool.INFO("译码前data --> " + data);
			while (data.length() > codeMax) {//
				// 当取出的编码超过最长哈夫曼编码长度时,进行判断,比较对应的哈夫曼编码,找出对应的原始数据
				data = compareCode(saveCode, data, codeMax, codeMin, bos);// 比较对应的哈夫曼编码,如果匹配成功,删除已匹配的编码段,返回未匹配的编码段

				LogTool.INFO("译码后data --> " + data);
			}
		}

		// 将最后一个还没有匹配的编码最后一次匹配(长度介于codeMin和codeMax),写入文件
		Integer i = new Integer(bis.read());
		LogTool.INFO("解压后字符信息" + i.intValue());
		String readString = Integer.toBinaryString(i);
		readString = zeroFill(readString);// 补0机制

		int size = bis.read(); // 补0的个数
		LogTool.INFO("读取的字符编码字符 -->" + readString);
		readString = readString.substring(0, 8 - size);
		data += readString;
		LogTool.INFO("最后的data= " + data + "被补了" + size + "个0!!!");
		while (data.length() > 0) {
			data = compareCode(saveCode, data, codeMax, codeMin, bos);// 比较对应的哈夫曼编码,如果匹配成功,删除已匹配的编码段,返回未匹配的编码段
		}

		bos.flush(); // 强行输出
		fos.close(); // 关闭输出流
		fis.close(); // 关闭输入流
		LogTool.INFO("counter=" + counter);
	}

 

 

由于Integer.toBinaryString(i)方法返回的二进制序列会略去前导0,所以需要添上。

/**
	 * 给字节补前导0,转成字符串返回
	 * 
	 * @param s 二进制字符串序列
	 * @return 补零后的字符串
	 */
	private String zeroFill(String s) {
		String str = "";
		for (int j = 0; j < 8 - s.length(); j++) {
			str += "0";
		}
		str += s;// 把前导0补回来啦
		return str;
	}

 

这样一个哈夫曼解压过程也就完成了

 

运行示例




 



 

 

源代码打包上传地址:http://download.csdn.net/detail/u011458382/6393211
 

希望大家多多支持和指正。

 

  • 大小: 12.2 KB
  • 大小: 25.4 KB
  • 大小: 61.2 KB
  • 大小: 63.4 KB
分享到:
评论

相关推荐

    哈夫曼压缩解压算法-C语言

    mypage文件可能包含了实现哈夫曼压缩和解压缩算法的C源代码文件,以及相关的测试数据或结果。通过阅读和理解这些代码,你可以深入学习哈夫曼编码的工作原理,以及如何在C语言中实现这一算法。同时,还可以了解到如何...

    哈夫曼压缩解压缩

    总之,哈夫曼压缩解压缩是数据压缩领域的一个重要方法,通过MFC和VC++,我们可以构建出直观且高效的压缩工具,便于学习和研究。在这个过程中,理解哈夫曼编码的原理、掌握MFC的使用以及实现压缩和解压缩的算法,都是...

    我的《哈夫曼压缩》

    《哈夫曼压缩》是一种广泛应用于数据压缩领域的高效算法,由大卫·艾尔·哈夫曼在1952年提出。它属于一种基于字符频率的无损压缩方法,特别适用于压缩那些存在大量重复字符的数据。哈夫曼编码是哈夫曼压缩的核心,...

    java实现哈夫曼压缩的实例

    在Java中实现哈夫曼压缩涉及到的主要步骤包括统计字节频率、构建哈夫曼树以及生成哈夫曼编码。首先,我们需要创建一个字节类(`NodeData`)来表示每个字节及其对应的权重(频率)。下面我们将详细讲解这些步骤: 1....

    java哈夫曼压缩和解压

    在Java中实现哈夫曼压缩和解压涉及到以下几个关键知识点: 1. **哈夫曼树**: 哈夫曼树(Huffman Tree)是一种带权路径长度最短的二叉树,也称为最优二叉树。它通过将频率较低的字符编码为较短的位序列,而频率较...

    vc++哈夫曼压缩算法

    vc++哈夫曼压缩算法 vc++哈夫曼压缩算法

    哈夫曼压缩与解压缩设计

    哈夫曼压缩是一种高效的数据压缩方法,它基于字符出现频率构建一种特殊的二叉树——哈夫曼树。在计算机科学中,尤其是信息处理和文件压缩领域,哈夫曼编码是广泛应用的技术之一。ASC II码是计算机中用8位二进制数...

    哈夫曼压缩与解压缩程序(JAVA)

    在Java编程环境中实现哈夫曼压缩与解压缩程序,我们可以利用面向对象的特性,设计多个类来完成不同部分的功能。 1. **FileChooserDemo.java**:这是一个用户界面类,通常包含用于让用户选择输入和输出文件的控件。...

    哈夫曼压缩和解压c++(源码)

    在C++中实现哈夫曼压缩和解压,主要涉及到数据结构(如优先队列、二叉树)和文件操作(读写)。`huffmain`可能是这个C++项目的主程序文件,其中可能包含了构建哈夫曼树、生成编码、压缩和解压等核心功能的实现。具体...

    哈夫曼压缩与解压缩源码.zip

    哈夫曼压缩与解压缩源码.zip哈夫曼压缩与解压缩源码.zip哈夫曼压缩与解压缩源码.zip哈夫曼压缩与解压缩源码.zip哈夫曼压缩与解压缩源码.zip哈夫曼压缩与解压缩源码.zip哈夫曼压缩与解压缩源码.zip哈夫曼压缩与解压缩...

    c++编写的哈夫曼压缩软件

    在C++中实现哈夫曼压缩软件,我们需要理解以下几个核心概念和技术: 1. **哈夫曼树**:哈夫曼树是一种带权路径长度最短的二叉树,也称为最优二叉树。构建哈夫曼树的过程是通过合并频度最低的两个节点来逐渐构建整个...

    哈夫曼压缩与解压算法(可以直接运行)

    本压缩包文件包含了一个可以直接运行的哈夫曼压缩与解压程序,是用C++语言编写的。C++是一种通用的、面向对象的编程语言,具有高效、灵活和丰富的库支持,非常适合实现这样的算法。 在压缩过程中,首先需要统计输入...

    java 哈夫曼压缩算法的分析与实现[源码][附图]

    在Java中实现哈夫曼压缩涉及到以下几个关键步骤: 1. **统计字符频率**:首先,需要遍历输入文本,统计每个字符出现的次数,生成一个字符频率表。这是构建哈夫曼树的基础。 2. **构建哈夫曼树**:使用字符频率表,...

    哈夫曼压缩与解压文件课程设计(代码+实习报告)

    哈夫曼编码是一种高效的数据压缩方法,由大卫·哈夫曼在1952年提出。它是基于一种称为哈夫曼树(也叫最优二叉树)的数据结构。在这个课程设计中,你将深入理解哈夫曼编码的原理,并通过C++编程语言实现文件的压缩与...

    哈夫曼压缩算法

    哈夫曼压缩算法,全称为哈夫曼编码(Huffman Coding),是一种高效的无损数据压缩方法,由美国科学家大卫·艾尔·哈夫曼在1952年提出。它是基于字符频率(权重)构建最优二叉树的思想,通过创建一棵特殊的二叉树——...

    java哈夫曼压缩

    在Java中实现哈夫曼压缩通常包括以下几个关键步骤: 1. **构建哈夫曼树**:首先,需要统计输入文本中每个字符出现的频率。然后,根据这些频率创建一个哈夫曼树。哈夫曼树是一种特殊的二叉树,其特点是叶子节点代表...

    哈夫曼压缩实现

    哈夫曼压缩是一种高效的数据编码方法,主要用于无损数据压缩,其原理是基于字符出现频率构建最优的二叉树(哈夫曼树),并以此进行编码。在C++实现哈夫曼压缩的过程中,我们需要理解以下几个关键知识点: 1. **...

    c++ 哈夫曼压缩

    在C++中实现哈夫曼压缩,我们需要理解以下几个关键知识点: 1. **哈夫曼树(Huffman Tree)**:哈夫曼树是一种特殊的二叉树,也称为最优二叉树,其叶子节点代表待编码的字符,非叶子节点表示字符的组合。树的构建...

    huff.rar_MATLAB 图片压缩_huff_哈夫曼 压缩_哈夫曼压缩_图片压缩matlab

    哈弗曼编码(Huffman Coding)是一种数据压缩算法,由美国计算机科学家大卫·艾尔文·哈弗曼在1952年提出。这个算法基于一种称为“最优二叉树”(也称哈弗曼树)的数据结构,主要用于对频率不同的字符进行编码,从而...

Global site tag (gtag.js) - Google Analytics