压缩与解压文件
题目要求
实现一个基于哈夫曼树的文件压缩程序和文件解压程序。
基本要求:
(1) 要求压缩程序读入源文件,分析每种字符的频度,然后建立相应的哈夫曼树,再求出相应哈夫曼编码,根据编码对源文件进行压缩,得到源文件对应的压缩文件。
(2) 解压程序读入压缩文件,根据相应的哈夫曼编码解压还原,得到对应的源文件。
(3) 求出压缩率;
需求分析
1) 创建一个文件输入流,并把它和指定目录下的文件建立联系。
2) 判断有多少个字节可以读取,并创建一个字节数组,再将每个字节的个数进行统计存入map集合,将其作为创建哈夫曼树的权重值。
3) 遍历map集合,用字节和个数逐个创建Node节点,并将Node节点存入一个list里面。
4) 遍历list集合创建哈夫曼树,返回哈夫曼树的根节点。
5) 利用根节点递归遍历获取每个字节的哈夫曼编码,再将哈夫曼编码转为十进制数存到一个字节数组之中。
6) 创建一个对象输出流(ObjectOutputStream)和文件输出流,用文件输出流与指定目录下的zip文件建立联系,再用对象输出流将上面求出来的字节数组和map集合输出到文件输出流方便之后用对象读入流来读取(重构)对象。
7) 利用对象读入流(ObjectInputStream)取出之前存入的对象,即:字节数组和map集合。
8) 利用byteToBitString函数将字节数组中的十进制数转为二进制即哈夫曼编码。
9) 将所有字节的哈夫曼编码拼接在一起存入一个字符串中,从第一位开始逐位读取,并判断map集合中是否有对应的字节,如果有将其存入一个list集合中。
10) 所有字节读取完毕之后,将list转存在一个字节数组中并返回。用文件输出流将其写入文件。
思路分析
题目的要求基于哈夫曼树的原理,那么首先要做的就是创建一个哈夫曼树,从而求出对应字节的哈夫曼编码。在此之前要统计出文件里面有多少个字节可以读取,相同字节的个数有多少,将其作为创建哈夫曼树时候的权重值,这样遍历哈夫曼树就可以得到整个文件的哈夫曼编码,然后每8个一位求出对应的10进制数将其存入一个字节数组之中。这时候我们得到一个map和一个字节数组zip。map之中存放的是键值对,键就是不同的字节,值便是字节对应的哈夫曼编码。字节数组中存放的是哈夫曼编码的10进制表示方式。将其存入对象字节流
ObjectOutputStream 将 Java 对象的基本数据类型和图形写入 OutputStream。可以使用 ObjectInputStream 读取(重构)对象。通过在流中使用文件可以实现对象的持久存储。如果流是网络套接字流,则可以在另一台主机上或另一个进程中重构对象。
代码实现
1 importjava.io.FileInputStream;2 importjava.io.FileOutputStream;3 importjava.io.InputStream;4 importjava.io.ObjectInputStream;5 importjava.io.ObjectOutputStream;6 importjava.io.OutputStream;7 importjava.nio.channels.FileChannel;8 importjava.text.NumberFormat;9 importjava.util.ArrayList;10 importjava.util.Collections;11 importjava.util.HashMap;12 importjava.util.List;13 importjava.util.Map;14 importjava.util.Set;15
16 public classHumanCodeTest17 {18
19 public static voidmain(String[] args)20 {21 String srcFile="e://code//123.txt";//要压缩的文件
22 String dstFile="e://code//123.zip";//压缩后的文件
23 zipFile(srcFile, dstFile);//压缩文件
24 unZipFile(dstFile,"e://code//unzip.txt");//对刚才的文件进行解压,解压后的文件名称叫做unzip.txt
25 }26
27 public static voidunZipFile(String zipFile,String dstFile)28 {29 InputStream inputStream=null;30 ObjectInputStream objectInputStream=null;31 OutputStream outputStream=null;32 try
33 {34 inputStream=new FileInputStream(zipFile); //将压缩文件读入
35 objectInputStream=new ObjectInputStream(inputStream); //对象操作流,讲一个对象读入
36 byte [] array= (byte [])objectInputStream.readObject(); //把每个字节的哈夫曼编码的对应十进制数读入
37 Map map=(Map)objectInputStream.readObject();//把每个字节对应的哈夫曼编码读入
38 byte[] decode =decode(map, array);39 outputStream=newFileOutputStream(dstFile);40 outputStream.write(decode);41 System.out.println("解压文件成功!");42 } catch(Exception e)43 {44 System.out.println(e);45 }finally
46 {47 try{48 outputStream.close();49 objectInputStream.close();50 inputStream.close();51
52 } catch(Exception e2) {53 System.out.println(e2);54 }55
56 }57
58
59 }60
61 public static voidzipFile(String srcFile,String dstFile)62 {63 FileInputStream inputStream=null;64 OutputStream outputStream=null;65 ObjectOutputStream objectOutputStream=null;66 FileInputStream zipfile = null;67 FileChannel fs=null;68 FileChannel zip=null;69 try
70 {71 inputStream=newFileInputStream(srcFile);72 byte [] b=new byte[inputStream.available()]; //获取文件的所有字节,这个方法可以在读写操作前先得知数据流里有多少个字节可以读取
73 fs =inputStream.getChannel();74 inputStream.read(b);75 byte[] huffmanZip =huffmanZip(b);76 outputStream=newFileOutputStream(dstFile);77 objectOutputStream=new ObjectOutputStream(outputStream); //对象操作流:该流可以将一个对象写出,或者读取一个对象到程序中,也就是执行了序列化和反序列化操作。
78 objectOutputStream.writeObject(huffmanZip);79 objectOutputStream.writeObject(map);80
81 zipfile = newFileInputStream(dstFile);82 zip =zipfile.getChannel();83 NumberFormat numberFormat =NumberFormat.getInstance();84 numberFormat.setMaximumFractionDigits(2);85 String result = numberFormat.format((float)zip.size()/(float)fs.size()*100);86 System.out.println("压缩率:"+result+"%");87 System.out.println("压缩成功!");88 } catch(Exception e)89 {90 System.out.println(e);91 }92 finally
93 {94 if(inputStream!=null)95 {96 try
97 {98 objectOutputStream.close();99 outputStream.close();100 inputStream.close();//释放资源
101 zipfile.close();102
103 } catch(Exception e2)104 {105 System.out.println(e2);106 }107
108 }109 }110 }111
112 private static byte[] decode(Map map,byte[] array)113 {114 StringBuilder stringBuilder = newStringBuilder();115 for(int i=0;i
119 }120
121 Map map2=new HashMap();//反向编码表
122 Set keySet =map.keySet();123 for(Byte b:keySet)124 {125 String value=map.get(b);126 map2.put(value, b);127 }128
129
130 List list=new ArrayList();131 for (int i = 0; i
145 {146 flag=false;147 }148
149 }150 list.add(byte1);151 i+=count;152 }153
154 byte [] by=new byte[list.size()];155 for(int i=0;i
162 private static String byteToBitString(boolean flag, byteb)163 {164 int temp=b;165 if(flag)166 {167 temp|=256; //与256做位运算 256的二进制形式为 11111111
168 }169
170 String binaryString = Integer.toBinaryString(temp);//他的作用是把一个10进制数转为32位的2进制数。同时对负数,会用补码表示。
171 if(flag)172 {173 return binaryString.substring(binaryString.length()-8);174 }175 else
176 {177 returnbinaryString;178 }179
180 }181
182 private static byte[] huffmanZip(byte [] array) //整个文件的字节数组
183 {184 List nodes = getNodes(array); //获取节点,内容是字节及其对应的个数
185 Node createHuffManTree = createHuffManTree(nodes); //利用获取的节点创建一个哈夫曼树
186 Map m=getCodes(createHuffManTree); //把哈夫曼的根节点传入,获取存放每个字节(key)和它对应的哈夫曼编码(value)
187 byte[] zip =zip(array, m);188 returnzip;189 }190
191 //192 private static byte[] zip(byte [] array,Map map) //压缩,将每一个字符的哈夫曼编码变为十进制数
193 {194 StringBuilder sBuilder=new StringBuilder(); //整个文件的哈夫曼编码
195 for(byte item:array) //遍历整个文件的字节数组,并且将其哈夫曼编码拼接成字符串
196 {197 String value=map.get(item);198 sBuilder.append(value);199 }200 //System.out.println(sBuilder);
201 intlen;202 if(sBuilder.toString().length()%8==0)//如果可以整除,
203 {204 len=sBuilder.toString().length()/8;205 }206 else //如果不能整除
207 {208 len=sBuilder.toString().length()/8+1;209 }210
211 byte [] by=new byte[len];212 int index=0;213 for(int i=0;isBuilder.length())217 {218 string=sBuilder.substring(i);219 }220 else
221 {222 string=sBuilder.substring(i, i+8);223 }224
225 by[index]=(byte)Integer.parseInt(string,2); //输出2进制数string在十进制下的数.
226 index++;227 }228
229
230 returnby;231
232 }233
234
235 //重载
236 private static MapgetCodes(Node root)237 {238 if(root==null)239 {240 return null;241 }242 getCodes(root.leftNode,"0",sBuilder);243 getCodes(root.rightNode,"1",sBuilder);244 returnmap;245 }246
247
248
249 //获取哈夫曼编码
250 static Map map=new HashMap<>(); //创建一个map集合,存放每个字节(key)和它对应的哈夫曼编码(value)
251 static StringBuilder sBuilder=newStringBuilder();252 public static voidgetCodes(Node node,String code,StringBuilder stringBuilder)253 {254 StringBuilder stringBuilder2=newStringBuilder(stringBuilder);255 stringBuilder2.append(code);256 if(node!=null)257 {258 if(node.data==null)//非叶子结点
259 {260 //向左递归
261 getCodes(node.leftNode,"0",stringBuilder2);262 //向右递归
263 getCodes(node.rightNode,"1",stringBuilder2);264 }265 else //如果是叶子结点
266 {267 map.put(node.data,stringBuilder2.toString());268 }269 }270 }271
272
273
274 public static List getNodes(byte[] array)275 {276 List list=new ArrayList();277 Map map=new HashMap();278 for(Byte data:array) //遍历字节数组,目的为了统计相同字节的个数
279 {280 Integer count=map.get(data);//通过键获取值
281 if(count==null)//说明此时map集合中还没有此字符
282 {283 map.put(data, 1);284 }285 else
286 {287 map.put(data,count+1);288 }289 }290 //遍历map集合
291 Set set=map.keySet(); //吧map中所有的key取出来,放到set集合中,方便一会儿构造节点
292 for(Byte key:set) //遍历set集合获取对应字节的个数,并创建一个node对象,并且把它放到list里面
293 {294 int value=map.get(key);295 Node node=newNode(key, value);296 list.add(node);297 }298 returnlist;299 }300
301 private static Node createHuffManTree(Listlist)302 {303 while(list.size()>1)304 {305 Collections.sort(list);//先对集合进行排序,排序关键字是value即字节的个数
306 Node leftNode=list.get(0);307 Node rightNode=list.get(1);308
309 Node parentNode=new Node(null, leftNode.weight+rightNode.weight);310 parentNode.leftNode=leftNode;311 parentNode.rightNode=rightNode;312
313 list.remove(leftNode);314 list.remove(rightNode);315
316 list.add(parentNode);317 }318 return list.get(0); //返回哈夫曼树的根。
319
320 }321
322 }323
324 class Node implements Comparable
325 {326 Byte data;//字符
327 int weight;//字符出现的次数
328 Node leftNode;329 Node rightNode;330
331 public Node(Byte data,int weight)//构造器
332 {333 this.data=data;334 this.weight=weight;335 }336
337 @Override338 public intcompareTo(Node o)339 {340 return this.weight-o.weight;341 }342
343 @Override344 publicString toString()345 {346 return "Node [data=" + data + ", weight=" + weight + "]";347 }348
349 //前序遍历
350 public voidpreOrder()351 {352 System.out.println(this);353 if(this.leftNode!=null)354 {355 this.leftNode.preOrder();356 }357 if(this.rightNode!=null)358 {359 this.rightNode.preOrder();360 }361 }362
363
364 }