文件压缩总结(哈夫曼压缩)在学习哈弗曼压缩之前,还是首先来了解什么是哈夫曼树,哈夫曼编码。
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++;// 使次数加17.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> nodeQueue){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){ //若长度大于等于813. write=writes.substring(0,8); //取其前八位14. ws[i]=changInt(write); //将这八位的01串转换成 byte15.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++){ //再后面补022. 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.}。