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

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

 

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

 

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);
	}
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值