`
jcs130
  • 浏览: 131681 次
  • 性别: Icon_minigender_1
  • 来自: Ottawa
社区版块
存档分类
最新评论

菜鸟说:哈夫曼压缩的问题

 
阅读更多

搞了一天终于把哈夫曼压缩搞好了,自己想了很多,也参照了别人的代码,终于把自己的做出来了,

 

关于代码,就不多做说明了,因为思路都是差不多的,代码会在文章最后面贴出来,那我就讲讲几个我遇到的几个问题吧:

 

1.static尽量不要用:以前我编写什么程序,静态变量太好用了~加个点一引用就好了,不需要传来传去~但是这次我算得到了教训,为了方便,一开始我爸很多变量都设成了静态的,但是邓树构造完成以后,获得叶节点编码的时候,就是一串000000000……找了半天错误不知道错在哪,就把所有的静态变量都设为了private 来回传,结果这样就对了……所以静态变量要少用,尤其是在需要进行递归的场合,更要多加注意!

 

2.缓冲流可以加快写入速度,但是一定要记着flush:在不用缓冲流的时候,压缩小文件的时候速度还是可以接受的,但是要压缩一个3兆的图片的时候就要等很长时间了,这是把文件输出流变成缓冲流就能节省时间,但是我在一开始用的时候,忘记了在每次写入缓冲流的时候强制刷新(flush),结果导致压缩后的文件异常的小,本来几百K的文件压缩完了只剩下一百多K,一开始我还挺高兴~想这个压缩的效率还挺高~但是当我压缩一个只有4Kb的小文件的时候……压缩出来的文件竟然是0Kb……囧……经过同学的提醒意识到了应该强制刷新一下,这下就正常了。

 

3.关于压缩的效率:哈夫曼压缩是无损压缩,所以当我压缩一个色彩比较丰富的BMP位图(3M)



 压缩完以后,大小没变多少


但是当我压缩一个我自己建的一个重复数据很多的文件的时候,大小的改变就很明显了:


 

压缩前的大小:



 
 压缩后的大小:


4.最后补0的个数:因为不一定哈弗曼编码的总长度是8的倍数,所以要在后面补0,这样就需要一个用来记录补了多少个0的数据,方便再解压缩的时候读取。


 

解压缩就是压缩的逆过程(说起来简单啊……╮(╯▽╰)╭),下一步就是要把我压缩的文件还原出来~加油~~~

 

 

压缩的代码很长……如下:

 

package 哈夫曼压缩;

/**
 * 哈夫曼树节点类
 * 
 * @author Micro
 * 
 */
public class HafNode implements Comparable<HafNode> {
	private HafNode father;
	private HafNode left;
	private HafNode right;
	// 字节
	private int num;
	// 出现的频率
	private int pl;

	// 重载构造器
	public HafNode(int num, int pl) {
		this.num = num;
		this.pl = pl;
	}

	// get set方法
	public int getPl() {
		return pl;
	}

	public void setPl(int pl) {
		this.pl = pl;
	}

	public HafNode getFather() {
		return father;
	}

	public void setFather(HafNode father) {
		this.father = father;
	}

	public HafNode getLeft() {
		return left;
	}

	public void setLeft(HafNode left) {
		this.left = left;
	}

	public HafNode getRight() {
		return right;
	}

	public void setRight(HafNode right) {
		this.right = right;
	}

	public int getNum() {
		return num;
	}

	public void setNum(int num) {
		this.num = num;
	}

	//设置按照什么顺序排序
	@Override
	public int compareTo(HafNode o) {
		// 按照频率的自然顺序排序
		int P = o.getPl();
		return pl - P;
	}

}

 

记录编码的类

 

package 哈夫曼压缩;

/**
 * 存储每一个字节的哈弗曼编码和哈夫曼编码的长度
 * 
 * @author Micro
 * 
 */
public class Code {
	// 哈夫曼编码
	private String node;
	// 编码长度
	private int n;

	// 重载构造器
	public Code(int n, String node) {
		this.n = n;
		this.node = node;
	}

	public String getNode() {
		return node;
	}

	public void setNode(String node) {
		this.node = node;
	}

	public int getN() {
		return n;
	}

	public void setN(int n) {
		this.n = n;
	}

}

 

创建哈夫曼树,获得叶节点编码及读取文件写入文件的方法等:

 

package 哈夫曼压缩;

import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.PriorityQueue;

/**
 * 读取文件,统计每个字节出现频率
 * 
 * @author Micro
 * 
 */
public class readFile {
	private static int[] by = new int[256];
	private Code SaveCode[] = new Code[256];
	private static HafNode root;

	/**
	 * 1.统计各字节频率,创建节点,并存入优先队列中 2.把优先队列创建成哈夫曼树
	 * 
	 * @param path
	 *            :路径
	 * @throws IOException
	 */
	public void readF(String path) throws IOException {
		// 创建文件输入流——用来获取频率
		FileInputStream ins = new FileInputStream(path);
		// 创建缓冲流
		BufferedInputStream bis = new BufferedInputStream(ins);
		while (bis.available() > 0) {
			int t = bis.read();
			by[t]++;
		}
		// 创建优先队列
		PriorityQueue<HafNode> queue = new PriorityQueue<HafNode>();
		// 把所有节点都放入队列中
		for (int i = 0; i < by.length; i++) {
			if (by[i] != 0) {
				HafNode node = new HafNode(i, by[i]);
				queue.add(node);
			}
		}
		// 构建哈夫曼树
		HafTree(queue);
	}

	// 创建哈夫曼树
	public void HafTree(PriorityQueue<HafNode> queue) {
		while (queue.size() > 1) {
			HafNode min1, min2;
			min1 = queue.poll();
			min2 = queue.poll();
			// 创建合并的节点
			HafNode result = new HafNode(min1.getNum() + min2.getNum(),
					min1.getPl() + min2.getPl());
			result.setLeft(min1);
			result.setRight(min2);
			min1.setFather(result);
			min2.setFather(result);
			queue.add(result);
		}
		root = queue.peek();
	}

	// 获得各叶节点的哈弗曼编码
	public void getCode(HafNode a, String Code) {

		if (a.getLeft() == null && a.getRight() == null) {
			System.out.println("字节" + a.getNum() + "的哈夫曼编码为:\t" + Code);
			Code b = new Code(Code.length(), Code);
			SaveCode[a.getNum()] = b;
		}
		if (a.getLeft() != null) {
			getCode(a.getLeft(), Code + '0');
		}
		if (a.getRight() != null) {
			getCode(a.getRight(), Code + '1');
		}
	}

	/**
	 * 将读取的文件的字节转换成哈夫曼编码写入压缩文件 1.把每个字节对应的编码长度写入文件,长度256
	 * 2.从Code数组中依次读取代表字节的哈弗曼编码,并八位八位的转化成int写入文件
	 * 
	 * @param path1
	 *            :要压缩的文件
	 * @param path2
	 *            :压缩后的文件路径
	 * @throws IOException
	 */
	public void writeFile(String path1, String path2) throws IOException {

		// 新建文件输出流
		FileOutputStream fos = new FileOutputStream(path2);
		// 包装为缓冲流
		BufferedOutputStream bos = new BufferedOutputStream(fos);
		// 先把每个字节的编码的长度写入文件(长度为256)
		for (int i = 0; i < SaveCode.length; i++) {
			if (SaveCode[i] == null) {
				bos.write(0);
				bos.flush();
			} else {
				bos.write(SaveCode[i].getN());
				bos.flush();
			}
		}
		// 写出每一个字节对应的编码
		int count = 0;// 记录中专的字符个数
		int i = 0;// 第i个字节
		String writes = "";
		String tranString = "";// 中转的字符串
		String waiteString;// 保存所转化的所有字符串
		while ((i < 256) || (count >= 8)) {
			// 如果缓冲区的字节数大于等于8
			if (count >= 8) {
				waiteString = "";// 清空要转化的码
				for (int t = 0; t < 8; t++) {
					waiteString = waiteString + writes.charAt(t);
				}
				// 将writes前八位删掉
				if (writes.length() > 8) {
					tranString = "";
					for (int t = 8; t < writes.length(); t++) {
						tranString = tranString + writes.charAt(t);
					}
					writes = "";
					writes = tranString;
				} else {
					writes = "";
				}
				count = count - 8;// 写出一个8位的字节
				int intw = changeString(waiteString);// 转化为int
				// 写入文件
				bos.write(intw);
				bos.flush();
				// System.out.println("写入了->"+waiteString);
			} else {
				// 得到第i个字节的编码信息,等待写入
				if (SaveCode[i] != null) {
					count = count + SaveCode[i].getN();
					writes = writes + SaveCode[i].getNode();
				}
				i++;
			}
		}
		// 把编码没有足够8的整数倍的String补0凑8,再写入
		if (count > 0) {
			// 编码最后补的0的个数
			int endz = 0;
			waiteString = "";// 清空要转化的代码
			for (int t = 0; t < 8; t++) {
				if (t < writes.length()) {
					waiteString = waiteString + writes.charAt(t);
				} else {
					waiteString = waiteString + '0';
					endz++;
				}
			}
			bos.write(changeString(waiteString));
			bos.flush();
			bos.write(endz);
			bos.flush();
			System.out.println("编码区写入了->" + endz + "个0");
		} else {
			System.out.println("编码区写入了->" + 0 + "个0");
			bos.write(0);
			bos.flush();
		}
		/************************ 再次读入文件信息,对应每一个字节写入编码 *********************/

		// 再次读入文件信息,对应每一个字节写入编码
		// 用来读取数据的文件输入流
		FileInputStream ins = new FileInputStream(path1);
		// 包装成缓冲流
		BufferedInputStream bis = new BufferedInputStream(ins);

		count = 0;
		writes = "";
		tranString = "";
		int idata = bis.read();
		// 文件没有读完的时候
		while ((bis.available() > 0) || (count >= 8)) {
			// 如果缓冲区等待字符大于等于8
			// System.out.println(count+"<<>>"+writes.length());
			if (count >= 8) {
				waiteString = "";// 清空要转化的码
				for (int t = 0; t < 8; t++) {
					waiteString = waiteString + writes.charAt(t);
				}
				// 删除前八位
				writes = writes.substring(8);

				count -= 8;// 写出一个8位的字节
				int intw = changeString(waiteString);
				bos.write(intw);// 写入到文件中
				bos.flush();
			} else {
				// 读入idata字节 ,对应编码写出信息
				count += SaveCode[idata].getN();
				writes += SaveCode[idata].getNode();
				// System.out.println(SaveCode[idata].getN()+"<<>>"+SaveCode[idata].getNode().length());
				idata = bis.read();
			}
		}
		count += SaveCode[idata].getN();
		writes += SaveCode[idata].getNode();
		// 把count 剩下的写入
		int endsint = 0;
		if (count > 0) {
			waiteString = "";// 清空要转化的码
			for (int t = 0; t < 8; t++) {
				if (t < writes.length()) {
					waiteString += writes.charAt(t);
				} else {
					waiteString += '0';
					endsint++;
				}
			}
			// System.out.println(waiteString.length());
			bos.write(changeString(waiteString));// 写入最后补的0
			bos.flush();
			bos.write(endsint);// 写入最后补的0的个数
			bos.flush();
			System.out.println("压缩区写入了->" + endsint + "个0");
		}
	}

	/**
	 * 将八位字符串转化为一个整数
	 * 
	 * @param s
	 * @return
	 */
	public int changeString(String s) {
		return ((int) s.charAt(0) - 48) * 128 + ((int) s.charAt(1) - 48) * 64
				+ ((int) s.charAt(2) - 48) * 32 + ((int) s.charAt(3) - 48) * 16
				+ ((int) s.charAt(4) - 48) * 8 + ((int) s.charAt(5) - 48) * 4
				+ ((int) s.charAt(6) - 48) * 2 + ((int) s.charAt(7) - 48);
	}

	// 程序入口
	public static void main(String[] args) throws IOException {
		readFile rf = new readFile();
		// 要压缩的文件路径
		String path = "D:\\新建文本文档.txt";
		// 压缩后的文件路径
		String path1 = "D:\\123.txt";
		rf.readF(path);
		// 获得各叶节点的哈弗曼编码
		String Code = "";
		rf.getCode(root, Code);
		// 写入压缩文件
		rf.writeFile(path, path1);
	}
}
  • 大小: 6.2 KB
  • 大小: 6.9 KB
  • 大小: 186.5 KB
  • 大小: 6 KB
  • 大小: 6 KB
  • 大小: 4.8 KB
0
2
分享到:
评论

相关推荐

    哈夫曼压缩——GUI

    这个项目“哈夫曼压缩——GUI”是基于Visual Studio 2010开发的一个图形化界面工具,对于学习数据结构和算法的学生来说,是一个很好的实践案例。 1. **哈夫曼编码**:哈夫曼编码是一种变长的前缀编码方式,通过构建...

    数据压缩技术:哈夫曼树的理论与Python实现

    内容概要:本文介绍了哈夫曼树的基本概念及其在数据压缩中的应用。首先解释了何为哈夫曼树,即带权路径长度最短的二叉树,适用于前缀编码。接着详细描述了构建哈夫曼树的步骤:初始化节点、选择并合并最小频率节点...

    哈夫曼树压缩算法实现

    总结来说,哈夫曼树压缩算法利用了数据的统计特性,通过构建最优二叉树实现高效的数据编码,进而完成数据的压缩。在实际应用中,这种算法广泛应用于文本、图像和音频等数据的压缩,提高了存储和传输的效率。

    用哈夫曼树实现压缩解压.pdf

    * compress:压缩算法的实现 * decompress:解压缩算法的实现 * create_filename:生成输出文件名 * write_compress_file:将哈夫曼编码写入输出文件 * write_decompress_file:将解压缩后的数据写入输出文件 6. ...

    数据库压缩算法:哈夫曼编码与哈夫曼树构建的实现及优化

    内容概要:本文详细介绍了哈夫曼编码在数据库压缩中的应用,包括哈夫曼树的构建算法、编码生成、数据压缩与解压缩的具体实现。通过构建哈夫曼树,根据字符频率生成唯一的二进制编码,从而有效减少存储空间和提高数据...

    java哈夫曼压缩和解压

    Java哈夫曼编码是一种数据压缩技术,它基于哈夫曼树进行编码,通过构建最优的二叉树结构来实现高效的数据编码与解码。在Java中实现哈夫曼压缩和解压涉及到以下几个关键知识点: 1. **哈夫曼树**: 哈夫曼树...

    哈夫曼压缩算法

    总的来说,哈夫曼压缩算法是一种重要的数据压缩技术,它基于字符频率统计和最优二叉树构建,通过为字符分配不同的二进制编码实现数据的高效压缩。在C#编程环境下,我们可以利用面向对象的特性,设计和实现哈夫曼压缩...

    实验七:哈夫曼树.docx

    实验七:哈夫曼树.docx

    哈夫曼编码(56页).pdf

    哈夫曼编码是一种变长前缀编码技术,广泛应用于数据压缩和通信领域。哈夫曼树是哈夫曼编码的核心数据结构,它是一种特殊的二叉树结构。 哈夫曼树的定义: 哈夫曼树是一种特殊的二叉树结构,它的每个叶子结点对应一...

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

    哈夫曼编码(Huffman Coding)是一种数据压缩算法,由美国计算机科学家大卫·艾尔曼在1952年提出。它的主要原理是基于字符出现频率构建最优的二叉树,进而为每个字符生成最短的唯一编码,使得常用字符在编码过程中...

    C++哈夫曼压缩

    10. **注意事项**:在实现哈夫曼压缩程序时,需要注意编码的前缀冲突问题,以及在解压缩时如何正确地识别每个字符的边界,这通常需要用到一些辅助信息,比如编码表和原始数据的长度。 综上所述,"C++哈夫曼压缩...

    哈夫曼压缩实现

    哈夫曼压缩是一种高效的数据编码方法,主要用于无损数据压缩,其原理是基于字符出现频率构建...在实际编程中,可能会遇到如何优化树的构建、如何高效地存储和读取编码等问题,这些都是进一步提升哈夫曼压缩性能的关键。

    vc++哈夫曼压缩算法

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

    java哈夫曼压缩

    哈夫曼编码是一种高效的数据压缩方法,由大卫·哈夫曼在1952年提出。它是基于频率的变字长编码技术,适用于无损数据压缩。在Java中实现哈夫曼压缩通常包括以下几个关键步骤: 1. **构建哈夫曼树**:首先,需要统计...

    哈夫曼压缩解压缩

    哈夫曼编码是一种高效的数据压缩算法,由大卫·哈夫曼在1952年提出。它是基于贪心策略的,主要用于无损数据压缩。在本文中,我们将深入探讨哈夫曼编码的基本原理、实现过程以及如何在VC++环境下利用MFC(Microsoft ...

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

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

    哈夫曼压缩

    ### 哈夫曼压缩详解 #### 一、哈夫曼压缩概述 哈夫曼压缩是一种广泛应用于数据压缩领域的无损压缩技术。无损压缩意味着压缩后的数据在解压后可以完全恢复到原始状态,不丢失任何信息。哈夫曼编码通过构建一个特殊...

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

    哈夫曼编码(Huffman Coding)是一种数据压缩算法,由美国计算机科学家大卫·哈夫曼在1952年提出。这种编码方法基于频率最小的字符优先的原则,通过构建一棵特殊的二叉树(哈夫曼树)来实现对数据的高效压缩。在C++...

Global site tag (gtag.js) - Google Analytics