文件的压缩与解压(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 }