文件的压缩与解压(java实现)

压缩与解压文件

题目要求

实现一个基于哈夫曼树的文件压缩程序和文件解压程序。

基本要求:

(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 import java.io.FileInputStream;
  2 import java.io.FileOutputStream;
  3 import java.io.InputStream;
  4 import java.io.ObjectInputStream;
  5 import java.io.ObjectOutputStream;
  6 import java.io.OutputStream;
  7 import java.nio.channels.FileChannel;
  8 import java.text.NumberFormat;
  9 import java.util.ArrayList;
 10 import java.util.Collections;
 11 import java.util.HashMap;
 12 import java.util.List;
 13 import java.util.Map;
 14 import java.util.Set;
 15 
 16 public class HumanCodeTest
 17 {
 18 
 19     public static void main(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 void unZipFile(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<Byte,String> map=(Map<Byte,String>)objectInputStream.readObject();// 把每个字节对应的哈夫曼编码读入
 38             byte[] decode = decode(map, array);
 39             outputStream=new FileOutputStream(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 void zipFile(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=new FileInputStream(srcFile);
 72             byte [] b=new byte[inputStream.available()];  //获取文件的所有字节,这个方法可以在读写操作前先得知数据流里有多少个字节可以读取
 73             fs = inputStream.getChannel();
 74             inputStream.read(b);
 75             byte[] huffmanZip = huffmanZip(b);
 76             outputStream=new FileOutputStream(dstFile);
 77             objectOutputStream=new ObjectOutputStream(outputStream);  //对象操作流:该流可以将一个对象写出,或者读取一个对象到程序中,也就是执行了序列化和反序列化操作。
 78             objectOutputStream.writeObject(huffmanZip);
 79             objectOutputStream.writeObject(map);
 80 
 81             zipfile = new FileInputStream(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<Byte, String> map,byte [] array)
113     {
114         StringBuilder stringBuilder = new StringBuilder();
115         for(int i=0;i<array.length;i++)
116         {
117             boolean flag=(i==array.length-1);
118             stringBuilder.append(byteToBitString(!flag, array[i])); //文件内容的二进制编码
119         }
120 
121         Map<String, Byte> map2=new HashMap<String, Byte>();//反向编码表
122         Set<Byte> 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<Byte> list=new ArrayList<Byte>();
131         for (int i = 0; i < stringBuilder.length();)
132         {
133             int count=1;
134             boolean flag=true;
135             Byte byte1=null;
136             while (flag)
137             {
138                 String substring = stringBuilder.substring(i, i+count);
139                 byte1 = map2.get(substring);
140                 if(byte1==null)
141                 {
142                     count++;
143                 }
144                 else
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<by.length;i++)
156         {
157             by[i]=list.get(i);
158         }
159         return by;
160     }
161 
162     private static String byteToBitString(boolean flag, byte b)
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             return binaryString;
178         }
179 
180     }
181 
182     private static byte[] huffmanZip(byte [] array)   //整个文件的字节数组
183     {
184         List<Node> nodes = getNodes(array);  //获取节点,内容是字节及其对应的个数
185         Node createHuffManTree = createHuffManTree(nodes); //利用获取的节点创建一个哈夫曼树
186         Map<Byte, String> m=getCodes(createHuffManTree);  //把哈夫曼的根节点传入,获取存放每个字节(key)和它对应的哈夫曼编码(value)
187         byte[] zip = zip(array, m);
188         return zip;
189     }
190 
191     //
192     private static byte[] zip(byte [] array,Map<Byte,String> 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         int len;
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;i<sBuilder.length();i+=8)//
214         {
215             String string;
216             if((i+8)>sBuilder.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         return by;
231 
232     }
233 
234 
235     //重载
236     private static Map<Byte, String> getCodes(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         return map;
245     }
246 
247 
248 
249     //获取哈夫曼编码
250     static Map<Byte, String> map=new HashMap<>();  //创建一个map集合,存放每个字节(key)和它对应的哈夫曼编码(value)
251     static StringBuilder sBuilder=new StringBuilder();
252     public static void getCodes(Node node,String code,StringBuilder stringBuilder)
253     {
254         StringBuilder stringBuilder2=new StringBuilder(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<Node> getNodes(byte [] array)
275     {
276         List<Node> list=new ArrayList<Node>();
277         Map<Byte, Integer> map=new HashMap<Byte, Integer>();
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<Byte> set=map.keySet();   //吧map中所有的key取出来,放到set集合中,方便一会儿构造节点
292         for(Byte key:set)  //遍历set集合获取对应字节的个数,并创建一个node对象,并且把它放到list里面
293         {
294             int value=map.get(key);
295             Node node=new Node(key, value);
296             list.add(node);
297         }
298         return list;
299     }
300 
301     private static Node createHuffManTree(List<Node> list)
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<Node>
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     @Override
338     public int compareTo(Node o)
339     {
340         return this.weight-o.weight;
341     }
342 
343     @Override
344     public String toString()
345     {
346         return "Node [data=" + data + ", weight=" + weight + "]";
347     }
348 
349     //前序遍历
350     public void preOrder()
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 }

 

posted @ 2020-09-15 16:34  17_Xtreme  阅读(548)  评论(0编辑  收藏  举报