打开APP
userphoto
未登录

开通VIP,畅享免费电子书等14项超值服

开通VIP
压缩软件(哈夫曼算法实现) 项目总结

压缩软件(哈夫曼算法实现) 项目总结

文章分类:Java编程
  一、在讲具体代码实现之前,先给大家普及一下压缩软件的相关知识
引用
压缩软件是利用算法将文件有损或无损地处理,以达到保留最多文件信息,而令文件体积变小的应用软件。压缩软件一般同时具有解压缩的功能。压缩软件的的基本原理是查找文件内的重复字节,并建立一个相同字节的"词典"文件,并用一个代码表示,比如在文件里有几处有一个相同的词"中华人民共和国"用一个代码表示并写入"词典"文件,这样就可以达到缩小文件的目的。常见的压缩软件有WinRAR ,好压(Haozip),WinZip,7-Zip,WinMount,Peazip等等。

        哈夫曼树作为数据结构二叉树章节中最为华彩的一部分,有着其独特的魅力。给定n个权值作为n个叶子结点,构造一棵二叉树,若带权路径长度达到最小,这样的二叉树便是哈夫曼树,也称为最优二叉树。 
        二、哈夫曼算法 
引用
        Huffman算法是一种基于统计的压缩方法。它的本质就是对文本文件中的字符进行重新编码,对于使用频率越高的字符,其编码也越短。但是任何2个字符的编码, 是不能出现向前包含的。也就是说字符A(假设为00)的编码的前段,不可能为字符B(则B的编码不可能为001,因为这里B的编码中包含了A的前段00,这会给解码难带来不必要的困难,所以这是不允许的)的编码。经过编码后的文本文件,主要包含2个部分:Huffman码表部分和压缩内容部分。解压缩的时候,先把Huffman码表取出来,然后对压缩内容部分各个字符进行逐一解码,形成源文件。
        哈夫曼编码生成步骤:
        ①扫描要压缩的文件,对字符出现的频率进行计算。
        ②把字符按出现的频率进行排序,组成一个队列。
        ③把出现频率最低(权值)的两个字符作为叶子节点,它们的权值之和为根节点组成一棵树。
        ④把上面叶子节点的两个字符从队列中移除,并把它们组成的根节点加入到队列。
        ⑤把队列重新进行排序。重复步骤③④⑤直到队列中只有一个节点为止。
        ⑥把这棵树上的根节点定义为0(可自行定义0或1)左边为0,右边为1。这样就可以得到每个叶子节点的哈夫曼编码了。

        三、编码流程(大体思路)
        压缩:
        1、将要压缩的文件一个一个字节的读出来即扫描要压缩的文件,并统计每个字节的权值即出现的频率。
        2、以每个字节的权值来构造哈夫曼树,并给每个字节进行哈夫曼编码。
        3、将每个字节和其对应得哈夫曼编码存放进一个Map中,即码表。
        4、以这个码表为依照,将文件中的所有字节一一进行编码(生成10字符串),最后在把所有字节的编码依次末尾相加合成一个10字符串。
        5、将这个10字符串重新组合,8个为一组,若最后一组不足8个则补0,并记录补0的个数,将每一组的10字符串转化为一个字节,
并将所有的10字符串合成一个字节数组,数组的最后一个元素存放补0的个数。
        6、创建一个压缩文件,先将码表的大小写入文件,再将码表写入文件(码表里还有每个字节的哈夫曼编码长度的信息)。
        7、最后将之前生成的字节数组写入文件(文件的主要信息)。
        解压缩:
        1、将压缩的文件同样一个一个字节的读出来。
        2、先读出码表的大小,再通过码表的大小读出码表,并将码表的信息存放进一个Map。
        3、再接着读出后面的所有字节,并转化成一个10字符串。
        4、通过与码表的匹配,从10字符串的第一个字符开始读,若读到的子字符串与码表的某个字节的的编码相同,解压出相应的字节,把该字节保存起来。
并把上面的子字符串从编码中删除,重复上一步骤,直到该项编码解析完成,最后将此10字符串还原成原来的文件的一个个字节。
        5、再将这些字节写入一个新的文件,后缀名改成和原来文件一样,就能打开了。

       
四、核心代码

1、压缩文件
Java代码
  1. /**  
  2.  * 压缩的文件操作  
  3.  *   
  4.  * @author king  
  5.  *   
  6.  */  
  7. public class CompressFileOption {   
  8.   
  9.     /**  
  10.      * 读取文件  
  11.      *   
  12.      * @param path  
  13.      *            :文件路径  
  14.      * @return:将文件的内容以字节数组的样式返回  
  15.      */  
  16.     public static byte[] readFile(String path) {   
  17.         byte[] dataByte = null;   
  18.         try {   
  19.             java.io.FileInputStream fis = new java.io.FileInputStream(path);   
  20.             int size = fis.available();// 可读的字节数   
  21.             dataByte = new byte[size];   
  22.             fis.read(dataByte);   
  23.   
  24.         } catch (Exception e) {   
  25.             // TODO Auto-generated catch block   
  26.             e.printStackTrace();   
  27.         }   
  28.         return dataByte;   
  29.     }   
  30.   
  31.     /**  
  32.      * 将码表的相关信息写入文件  
  33.      *   
  34.      * @param fileSize  
  35.      *            :原文件大小  
  36.      * @param map  
  37.      *            :存放码表的map  
  38.      * @param listCh  
  39.      *            :存放关键码的字符队列  
  40.      * @param path  
  41.      *            :文件路径  
  42.      * @throws Exception  
  43.      */  
  44.     public static void writeMap(int fileSize,   
  45.             java.util.HashMap<Byte, String> map, List<Byte> listBy, String path)   
  46.             throws Exception {   
  47.   
  48.         java.io.FileOutputStream fos = new java.io.FileOutputStream(path);   
  49.         java.io.DataOutputStream dos = new java.io.DataOutputStream(fos);   
  50.   
  51.         dos.writeInt(fileSize);// 将原文件大小写入文件   
  52.         int mapSize = map.size();// 码表的大小   
  53.         dos.writeInt(mapSize);// //将码表的大小写入文件   
  54.         for (int i = 0; i < mapSize; i++) {   
  55.             fos.write(listBy.get(i));// 将每个字节写入文件   
  56.             String hfmcode_next = map.get(listBy.get(i));// 得到每个字节对应的哈夫曼编码   
  57.             byte codeSize = (byte) hfmcode_next.length();// 每个字节对应的哈夫曼编码大小   
  58.             fos.write(codeSize);// 将每个字节对应的哈夫曼编码大小写入文件   
  59.             dos.writeChars(hfmcode_next);// 将每个字符对应的哈夫曼编码写入文件   
  60.         }   
  61.         dos.flush();   
  62.         fos.close();   
  63.     }   
  64.   
  65.     /**  
  66.      * 将压缩好的字节数组写入文件  
  67.      *   
  68.      * @param b  
  69.      *            :压缩好的字节数组  
  70.      * @param path  
  71.      *            :文件路径  
  72.      */  
  73.     public static void writeFile(byte[] b, String path) {   
  74.   
  75.         try {   
  76.             java.io.FileOutputStream fos = new java.io.FileOutputStream(path,   
  77.                     true);   
  78.             java.io.DataOutputStream dos = new java.io.DataOutputStream(fos);   
  79.             // 写入字节数组的大小   
  80.             dos.writeInt(b.length);   
  81.             fos.write(b);   
  82.             fos.flush();   
  83.             fos.close();   
  84.         } catch (Exception e) {   
  85.             // TODO Auto-generated catch block   
  86.             e.printStackTrace();   
  87.         }   
  88.     }   
  89.   
  90.     /**  
  91.      * 将10字符串转化为一个字节  
  92.      *   
  93.      * @param str  
  94.      *            :传入的字符串  
  95.      * @return:一个字节  
  96.      */  
  97.     private byte CharArrayToByte(String str) {   
  98.         char[] c = str.toCharArray();// 将字符串str转化为字符数组c   
  99.         int len = c.length;   
  100.         byte[] b = new byte[len];   
  101.         byte value = 0;   
  102.         byte value_next;   
  103.         for (int i = 0; i < len; i++) {   
  104.             b[i] = Byte.parseByte(c[i] + "");   
  105.             // System.out.println(b[i]);   
  106.         }   
  107.         for (int i = 0; i < len; i++) {   
  108.             value_next = (byte) (b[i] * Math.pow(2, len - i - 1));// 幂计算   
  109.             value = (byte) (value + value_next);   
  110.         }   
  111.         return value;   
  112.     }   
  113.   
  114.     /**  
  115.      * 将10字符串以8个为一组转化为一个字节数组  
  116.      *   
  117.      * @param str  
  118.      * @return  
  119.      */  
  120.     private byte[] StringToByteArray(String str) {   
  121.         char[] c = str.toCharArray();// 将字节串str转化为字符数组c   
  122.         int len = c.length;// 字符串字符的个数   
  123.         int lenByte;   
  124.         String s = "";   
  125.         char c_next;   
  126.         byte[] b;   
  127.         if (len % 8 == 0) {// 如果字符串的长度能被8整除   
  128.             lenByte = len / 8 + 1;   
  129.             b = new byte[lenByte];   
  130.             for (int i = 0; i < lenByte - 1; i++) {   
  131.                 for (int j = i * 8; j < (i + 1) * 8; j++) {   
  132.                     c_next = c[j];   
  133.                     s = s + c_next;   
  134.                 }   
  135.                 System.out.println("第" + i + "个字符串:" + s);   
  136.                 b[i] = CharArrayToByte(s);   
  137.                 s = "";   
  138.                 System.out.println("第" + i + "个字符串转化为字节后的值:" + b[i]);   
  139.             }   
  140.             b[lenByte - 1] = 0;// 字节数组的最后一个存放补0的个数   
  141.         } else {// 如果字符串的长度不能被8整除   
  142.   
  143.             lenByte = len / 8 + 2;   
  144.             b = new byte[lenByte];   
  145.             int remainder = len % 8;// 求出除8的余数   
  146.             int zeroNum = 8 - remainder;// 补0的个数   
  147.             System.out.println("补0数:" + zeroNum);   
  148.             System.out.println("原字符串:" + str);   
  149.             for (int i = 0; i < zeroNum; i++) {   
  150.                 str = str + '0';// 在字符串后面补0   
  151.             }   
  152.             System.out.println("补0后的字符串:" + str);   
  153.             c = str.toCharArray();   
  154.             System.out.println("补0后的字符串的字符个数:" + c.length);   
  155.             for (int i = 0; i < lenByte - 1; i++) {   
  156.                 for (int j = i * 8; j < (i + 1) * 8; j++) {   
  157.                     c_next = c[j];   
  158.                     s = s + c_next;   
  159.                 }   
  160.                 System.out.println("第" + i + "个字符串:" + s);   
  161.                 b[i] = CharArrayToByte(s);   
  162.                 s = "";   
  163.                 System.out.println("第" + i + "个字符串转化为字节后的值:" + b[i]);   
  164.             }   
  165.             b[lenByte - 1] = (byte) zeroNum;// 字节数组的最后一个存放补0的个数   
  166.         }   
  167.         return b;   
  168.     }   
  169.   
  170.     /**  
  171.      * 压缩文件  
  172.      *   
  173.      * @param path1  
  174.      *            :原文件路径  
  175.      * @param path2  
  176.      *            :压缩后的文件路径  
  177.      * @throws Exception  
  178.      */  
  179.     public void CompressFile(String path1, String path2) throws Exception {   
  180.         // 从文件中得到字节数组   
  181.         byte[] b = CompressFileOption.readFile(path1);   
  182.         int b_size = b.length;// 原文件大小   
  183.         byte[] b_compress;// 字节数组,存放压缩的字符串   
  184.   
  185.         String hfmcode = "";// 文件内所有字节的哈夫曼编码   
  186.         String hfmcode_next;// 文件中每个字节的哈夫曼编码   
  187.         // 计算字符串中每个字节的权值,并返回一个存放字节和它对应权值的节点队列   
  188.         List<TreeNode> list = calWeight.calweight(b);   
  189.         int size = list.size();   
  190.         Huffman hfm = new Huffman();   
  191.         // 构建哈夫曼树并返回根节点   
  192.         TreeNode root = hfm.createHuffman(list);   
  193.         // 创建哈夫曼编码使其与字符一一对应   
  194.         hfm.createHfmCode(root, "");   
  195.   
  196.         java.util.HashMap<Byte, String> map = hfm.getMap();// 得到码表   
  197.         List<Byte> listBy = hfm.getList();// 得到存放关键码队列   
  198.   
  199.         System.out.println("mapsize---->:" + map.size());   
  200.         System.out.println("b---->:" + b.length);   
  201.         for (int i = 0; i < b_size; i++) {   
  202.             // 得到每个字节的哈夫曼编码   
  203.             hfmcode_next = map.get(b[i]);   
  204.             System.out.println("第"+i+"个: " + b[i] + "的编码:" + hfmcode_next);   
  205.             hfmcode = hfmcode + hfmcode_next;// 将每个字节的哈夫曼编码依次相加为一个01字符串   
  206.         }   
  207.         System.out.println("01串大小:" + hfmcode.length());   
  208.         System.out.println("01串:" + hfmcode);   
  209.         char[] ch = hfmcode.toCharArray();   
  210.         System.out.println("01串的大小:" + ch.length);   
  211.   
  212.         b_compress = StringToByteArray(hfmcode);// 得到字节数组   
  213.         for (int i = 0; i < b_compress.length; i++) {   
  214.             System.out.println("第" + i + "个字节" + b_compress[i]);   
  215.         }   
  216.         // 将文件大小和码表相关信息写入文件   
  217.         writeMap(b_size, map, listBy, path2);   
  218.         // 将字节数组写入文件   
  219.         writeFile(b_compress, path2);   
  220.     }  


2、解压缩文件
Java代码
  1. /**  
  2.  * 解压缩的文件操作  
  3.  *   
  4.  * @author king  
  5.  *   
  6.  */  
  7. public class UncompressFileOption {   
  8.   
  9.     public static long fileSize;   
  10.   
  11.     /**  
  12.      * 将8位10字符串前面缺0的补上0  
  13.      *   
  14.      * @param str  
  15.      * @return  
  16.      */  
  17.     private String addZero(String str) {   
  18.         int strLen = str.length();   
  19.         int zeroNum;   
  20.         if (strLen < 8) {// 若字符串长度小于8则补0   
  21.             zeroNum = 8 - strLen;   
  22.             for (int i = 0; i < zeroNum; i++) {   
  23.                 str = "0" + str;   
  24.             }   
  25.         }   
  26.         return str;   
  27.     }   
  28.   
  29.     /**  
  30.      * 将整型数组还原成之前的10串,即文件内容的哈夫曼编码  
  31.      *   
  32.      * @param n  
  33.      * @return  
  34.      */  
  35.     private String InttoBinaryString(int[] n) {   
  36.         int len = n.length;   
  37.         String[] s = new String[len];// 一个字符串数组存放二进制数据   
  38.         String BinaryStr = "";   
  39.         for (int i = 0; i < len - 1; i++) {   
  40.             s[i] = Integer.toBinaryString(n[i]);   
  41.             s[i] = addZero(s[i]);   
  42.             BinaryStr = BinaryStr + s[i];   
  43.         }   
  44.         System.out.println("二进制形式表示:" + BinaryStr);   
  45.         int BinaryStrLen = BinaryStr.length();// 得到为减0前的字符串大小   
  46.         int zeroSub = n[len - 1];// 之前在末尾补0的个数,现在减去   
  47.         System.out.println("减0前的字符串大小:" + BinaryStrLen);   
  48.         System.out.println("需要在字符串末尾减0的个数表示:" + zeroSub);   
  49.         BinaryStr = BinaryStr.substring(0, BinaryStrLen - zeroSub);   
  50.         System.out.println("减0后的字符串大小:" + (BinaryStrLen - zeroSub));   
  51.         System.out.println("减0后的二进制形式表示:" + BinaryStr);   
  52.         return BinaryStr;   
  53.     }   
  54.   
  55.     /**  
  56.      * 字符串匹配,判断字符串child是否为parent的前子串  
  57.      *   
  58.      * @param parent  
  59.      * @param child  
  60.      * @return  
  61.      */  
  62.     private boolean StringMatch(String parent, String child) {   
  63.   
  64.         char[] p = parent.toCharArray();   
  65.         char[] c = child.toCharArray();   
  66.         // System.out.println("数组p的长度:" + p.length);   
  67.         // System.out.println("数组c的长度:" + c.length);   
  68.         boolean b = false;   
  69.         for (int i = 0; i < c.length; i++) {   
  70.             if (c[i] == p[i]) {   
  71.                 b = true;   
  72.             } else {   
  73.                 b = false;   
  74.                 break;// 有一个字符不匹配则跳出循环   
  75.             }   
  76.         }   
  77.         return b;   
  78.     }   
  79.   
  80.     /**  
  81.      * 解压缩文件  
  82.      *   
  83.      * @param path2  
  84.      *            :压缩后的文件路径  
  85.      * @param path3  
  86.      *            :解压缩后的文件路径  
  87.      * @throws Exception  
  88.      */  
  89.     public void UncompressFile(String path2, String path3) throws Exception {   
  90.   
  91.         HashMap<Byte, String> map = new HashMap<Byte, String>();   
  92.         java.io.FileInputStream fis = new java.io.FileInputStream(path2);   
  93.         java.io.DataInputStream dis = new java.io.DataInputStream(fis);   
  94.   
  95.         java.io.FileOutputStream fos = new java.io.FileOutputStream(path3);   
  96.   
  97.         fileSize = dis.readInt();// 得到原文件的大小   
  98.         int mapSize = dis.readInt();// 得到码表的大小   
  99.         byte[] mapKey = new byte[mapSize];// 创建一个字符数组,存放码表中的字节   
  100.         byte codeSize;// 每个字节对应的哈夫曼编码大小   
  101.         String hfmcode_next = "";   
  102.         // 读取码表内容   
  103.         for (int i = 0; i < mapSize; i++) {   
  104.             mapKey[i] = (byte) fis.read();// 得到第i个字节   
  105.             codeSize = (byte) fis.read();// 得到每个字节对应的哈夫曼编码大小   
  106.             char[] codeChar = new char[codeSize];   
  107.             for (int j = 0; j < codeSize; j++) {   
  108.                 codeChar[j] = dis.readChar();   
  109.                 hfmcode_next = hfmcode_next + codeChar[j];   
  110.             }   
  111.             map.put(mapKey[i], hfmcode_next);// 将键值对放入Map中   
  112.             hfmcode_next = "";   
  113.         }   
  114.         int len = dis.readInt();// 得到压缩好的字节数组的大小   
  115.         System.out.println("压缩好的字节数组的大小: " + len);   
  116.         byte[] b = new byte[len];// 字节数组,存放压缩的字节串   
  117.         int[] n = new int[len];// 整型数组   
  118.         fis.read(b);// 得到压缩好的文件的字节数组   
  119.         for (int i = 0; i < b.length; i++) {   
  120.             System.out.println("第" + i + "个字节:" + b[i]);   
  121.             // 将字节还原成原来的整型数据   
  122.             if (b[i] < 0) {   
  123.                 n[i] = b[i] + 256;   
  124.             } else {   
  125.                 n[i] = b[i];   
  126.             }   
  127.             System.out.println("第" + i + "个字节的整型表示:" + n[i]);   
  128.         }   
  129.         String formerStr = InttoBinaryString(n);// 得到原10串   
  130.   
  131.         System.out.println("formerStr:" + formerStr);   
  132.         int size = map.size();   
  133.         System.out.println("mapsize:" + size);   
  134.   
  135.         int endIndex = formerStr.length();   
  136.         System.out.println("endIndex:" + endIndex);   
  137.         int beginIndex;   
  138.   
  139.         // byte[] fileData = new byte[fileSize];   
  140.         int count = 0;// 记录文件当前是第几个字节   
  141.   
  142.         while (!formerStr.isEmpty()) {// 如果字符串不为空   
  143.   
  144.             String child;   
  145.             for (int i = 0; i < mapKey.length; i++) {   
  146.                 child = map.get(mapKey[i]);   
  147.                 if (formerStr.isEmpty()) {// 若字符串为空   
  148.                     break;   
  149.                 }   
  150.                 if (StringMatch(formerStr, child)) {// 若匹配   
  151.                     // 一个字节字节的写入文件   
  152.                     fos.write(mapKey[i]);   
  153.                     count++;   
  154.                     beginIndex = child.length();   
  155.                     formerStr = formerStr.substring(beginIndex, endIndex);   
  156.                     endIndex = endIndex - beginIndex;   
  157.                 }   
  158.             }   
  159.         }   
  160.         fos.flush();   
  161.         fos.close();   
  162.     }  


五、项目展示

1.压缩软件界面


2.浏览要压缩的文件并指定压缩后的文件路径


3.压缩文本文件的效果


4.解压缩文件及效果




5.压缩bmp格式图片文件效果明显,但是压缩和解压的时间都很长,需改进

本站仅提供存储服务,所有内容均由用户发布,如发现有害或侵权内容,请点击举报
打开APP,阅读全文并永久保存 查看更多类似文章
猜你喜欢
类似文章
【热】打开小程序,算一算2024你的财运
把byte转化成2进制字符串
C# 计算文件的 Hash 值
使用Java MD5 为文件和字符串加密
Jsp页面实现文件上传下载 -下载
精确截取字符串
16进制字符串与byte数组互转
更多类似文章 >>
生活服务
热点新闻
分享 收藏 导长图 关注 下载文章
绑定账号成功
后续可登录账号畅享VIP特权!
如果VIP功能使用有故障,
可点击这里联系客服!

联系客服