哈夫曼压缩算法
- 1、下载文档前请自行甄别文档内容的完整性,平台不提供额外的编辑、内容补充、找答案等附加服务。
- 2、"仅部分预览"的文档,不可在线预览部分如存在完整性等问题,可反馈申请退款(可完整预览的文档不适用该条件!)。
- 3、如文档侵犯您的权益,请联系客服反馈,我们会尽快为您处理(人工客服工作时间:9:00-18:30)。
文件压缩总结(哈夫曼压缩)
在学习哈弗曼压缩之前,还是首先来了解什么是哈夫曼树,哈夫曼编码。
1.哈夫曼树是一种最优二叉树,它的带权路径长度达到最小。
树的带权路径长度为所有叶子结点带权路径长度之和。
而结点的带权路径长度是结点的路径长度乘以结点的权值。
2.哈夫曼编码是依据字符出现概率来构造异字头的平均长度最短的码字。
从哈弗曼树的根结点开始,按照左子树代码为“0”,右子树代码为“1”的规则,直到树的叶子结点,每个叶子结点的哈弗曼编码就是从根结点开始,将途中经过的枝结点和叶子结点的代码按顺序串起来。
哈夫曼压缩是字节符号用一个特定长度的01序列替代,在文件中出现频率高的符号,使用短的01序列,而出现频率少的字节符号,则用较长的01序列表示。
这里的文件压缩,我还只能做简单的文件A-->压缩为文件B--->解压为文件C,看文件A和文件C是不是相同。
那么就要分两个大步骤,小步骤:
不过,根据哈弗曼树的特点,我们首先还是要定义结点类型
结点类型代码
1.public class TreeNode {
2. public TreeNode parent; //双亲结点
3. public TreeNode left; //左孩子结点
4. public TreeNode right; //右孩子结点
5.
6. public byte con;// 结点的数据
7. public int rate;
8. public String bian="";
9. public int count=0;
10. public TreeNode(byte con, int rate) {
11. super();
12. this.con= con;
13. this.rate = rate;
14. }
15. }
然后分两大步骤
一. 首先压缩文件
1. 将源文件A中数据按字节读取,然后用MAP统计每个字节出现的次数(Key--不同的字节,value--次数)。
统计频率代码
1.while (t != -1) {// 如果未到达结尾
2. byte b = (byte) t;
3.
4. if (map.containsKey(b)) {// 如果map里面包含number键
5. int value = map.get(b);// 就可以通过get函数得到number对应
的value的值
6. value++;// 使次数加1
7.
8. map.put(b, value);
9. } else {// 如果map里面不包含number键
10. map.put(b, 1);// number的次数只有一次
11. }
12. // 继续读取下一个
13. t = bis.read();
14.
15. }
2. 将Map中的value值作为权值,建哈夫曼树,并得到每个字节的哈弗曼编码。
创建哈树代码
1./**
2. * 创建哈树
3. * @param nodeQueue 已经得到的优先队列
4. * @return 创建哈树后,返回树的根结点
5. */
6. public static TreeNode creatTree(PriorityQueue<TreeNode> nodeQ
ueue){
7. byte a=0; //定义一个byte,用来表示枝节点的字节
8. TreeNode root=null; //用来表示树的根结点
9. while(nodeQueue.size()>=2){ //当优先队列中的元素大于2时,
还可以使它们组成新的跟结点
10. TreeNode left=nodeQueue.poll(); //获取当前队列中的最
小元素,并将队列中的该元素删除。
是该节点作为左孩子
11. TreeNode right=nodeQueue.poll(); //获取当前队列中
的最小元素,作为右孩子
12. root=new TreeNode(a,left.rate+right.rate); //左右孩子的
频率之和作为根结点的频率
13. root.left=left; //连接孩子结点
和根结点的关系
14. root.right=right;
15. if(nodeQueue.size()==0){
16. return root;
17. }
18. nodeQueue.add(root);
19. }
20. return root;
21. }
得到哈夫曼编码代码
1./**
2. * 得到哈树叶子结点的哈弗曼编码
3. * @param node 哈树的根结点
4. * @return 将叶子结点的《字节,编码》存放入 map 中返回
5. */
6.public static HashMap<Byte,String> getCode(TreeNode node){
7. if(node.con!=(byte)0){
8. System.out.println((char)node.con+"--zuo--"+node.bian);
9. codemap.put(node.con, node.bian);
10. }
11. TreeNode left=node.left; // 获得左孩子结点
12. if(left!=null){ //若为非空则获得它的哈弗曼
编码
13. left.bian=node.bian+"0";
14. left.count++;
15. getCode(left);
16. }
17. TreeNode right=node.right; // 获得右孩子结点
18. if(right!=null){ //若为非空则获得它
的哈弗曼编码
19. right.bian=node.bian+"1";
20. right.count++;
21. getCode(right);
22. }
23. return codemap;
24.}
3. 压缩关键(压缩文件的格式:码表+文件信息)
再构造Map(Key--每个不同的字节,value--哈弗曼编码)。
将文件中的字符串转换成对应的哈弗曼编码(即 0 1串)。
将得到的0 1 串转换成字节(当最后不够构成一个字节时,在后面添加0),并最后用一个字节记录添加的0的个数。
将整个Map的信息写进文件B,就是码表。
同时将得到的字节写进文件B中。
Java代码
1.length=writes.length(); //得到当前01串文件的大小
2. if(length%8==0){
3. length=length/8+1;
4. }
5. else{
6. length=length/8+2;
7. }
8. byte[] ws=new byte[length];
9. System.out.println("数组的长度是:"+length);
10. while(writes.length()>0){ //将01串文件写到文件中
11. length=writes.length(); //得到当前01串文件的大小
12. if(length>=8){ //若长度大于等于8
13. write=writes.substring(0,8); //取其前八位
14. ws[i]=changInt(write); //将这八位的01串转换
成 byte
15.
16. writes=writes.substring(8); //同时得到原文件第8位后面
的01串
17. System.out.println(write+"--ws-->"+(int)ws[i]);
18. i++;
19. }else{ //当01文件的长度不够8位时
20. int bu0=8-length;
21. for(int j=0;j<bu0;j++){ //再后面补0
22. writes=writes+"0";
23. }
24. ws[ws.length-1]=(byte)bu0;
25. }
26. }
27.
28. bos.write(ws, 0,ws.length);
29. bos.flush();
30. bos.close();
其实将码表写进文件中有两种方法,不过,龙哥为了让我们对文件的格式有更深刻的了解,叫我们使用最原始的方法,就是将码表里的信息一个一个的写进文件。
具体将字节,哈弗曼编码及每个编码的长度同时记录在文件中。
其实在写码表的时候还可以用ObjectInputStream和ObjectOutputStream流,直接将整个码表作为一个对象一次就可以写进文件中,这样就简单多了。
不过,初学时,还是使用前者好些,这样有利于自己对整个项目的深层掌握。
等对这个项目完全非常了解掌握后,就可以做精简版的文件压缩了。
二.解压过程
4.根据码表得到Map(Key--每个不同的字节,value--哈弗曼编码),将得到的Map 转换成Map2(Key--哈弗曼编码,value--每个不同的字节)。
并将文件B中的文件信息部分转换成01串
Java代码
1.int MapL=dis.readInt(); //得到 map 的长度
2. codess=new String[MapL];
3. for(int t=0;t<MapL;t++){
4. byte zij=dis.readByte(); //得到每个字节
5. byte coL=dis.readByte();//得到每个编码的长度
6. byte[] codes=new byte[coL]; //定义一个 byte 数组,用来存
储每个哈夫曼编码
7.
8. dis.read(codes, 0, coL);
9. //将一个字节的哈弗曼编码转换成字符串形式
10. String cos=new String(codes); //得到一个整的哈夫曼编
码
11. codess[t]=cos; //
12. System.out.println(zij+"--对应点->"+cos);
13. map.put(cos,zij); //每次获得的一个字节于它对应的编
码,就将它们放入 map 中
14. }
15.
16.
17.读取文件中的字节,并将其转换成01串
18. int length=dis.readInt();
19. System.out.println("-文件的长度->"+length);
20.
21. for(int j=0;j<length-1;j++){
22. b=dis.readByte(); //读取文件中的字节
23. System.out.println("---重新输出-->"+b);
24.
25. String s=Integer.toBinaryString(b);
26. System.out.println("-b的二进制
-->"+s+"--->"+s.length());
27. if(s.length()>8){
28. s0="";
29. s0=s.substring(24,s.length());
30. }else if(s.length()<8){
31. int len=8-s.length();
32. for(int k=0;k<len;k++){
33. s1=s1+"0";
34. }
35. s0=s1+s;
36. }
37.
38. code=code+s0; //调用toBinaryString 方法,将十进制转换成 01
二进制
39. System.out.println(code+"-总长度为:"+code.length());
40.
41. }
42. b=dis.readByte();
43. n=b;
44. System.out.println("-n的值是:-->"+n);
45.
46. dis.close();
47. fis.close();
48.
49. code=code.substring(0, (code.length()-n));
50. System.out.println("-新的code ->"+code);
51.
5.根据Map2将得到的01串拆分,也就是将01串中头部分与Map中的Key值匹配,依次将整个01串还原成字节,同时将得到的所有字节写进文件C中
Java代码
1.rWrite(code,fHou);
2.
3.
4.public void rWrite(String code,String fHou){
5. System.out.println("--->看不懂错误啊!!");
6. String path="D:\\1000\\A"+fHou;
7. FileOutputStream fos;
8. try {
9. fos = new FileOutputStream(path);// 申明一个文件输出流
10. // 转成缓冲流
11. DataOutputStream dos = new DataOutputStream(fos);
12.
13.
14.
15.
16. int m=1; //标志获取01 串的最后位置
17. String code2=""; //存储每次获得的原文件01 串的一个子集
18.
19. while(code.length()>=m){
20. code2=code.substring(0,m);//获取当前01 字符串的前m 位
21. System.out.println("???进来没?"+"-->"+code2);
22.
23. for(int j=0;j<codess.length;j++){
24.
25. if(code2.equals(codess[j])){
26. byte b2=map.get(codess[j]); //由编码,获取他的字节
27. System.out.println("--->"+(char)b2);
28. dos.writeByte(b2);
29.
30. code=code.substring(m);
31.
32. m=0;
33. System.out.println("code的长度为:"+code.length());
34. break;
35. }
36. }
37. m++;
38. }
39. dos.close();
40. fos.close();
41. } catch (IOException e) {
42.
43. e.printStackTrace();
44. }
45.
46.}。