zoukankan      html  css  js  c++  java
  • 文件的压缩与解压(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 }
  • 相关阅读:
    [转载] 美团-云鹏: 写给工程师的十条精进原则
    Docker测试一个静态网站
    Docker容器访问外部世界
    Docker容器间通信
    Docker网络(host、bridge、none)详细介绍
    Docker的资源限制(内存、CPU、IO)详细篇
    esxi中CentOS7不停机加磁盘并扩容现有分区
    ESXI6.5安装CentOS7教程
    Linux查看占用CPU和内存的 的程序
    Centos7使用脚本搭建LVS的DR模式。
  • 原文地址:https://www.cnblogs.com/g414056667/p/13673861.html
Copyright © 2011-2022 走看看