哈夫曼算法实现文件夹的压缩与解压

哈夫曼算法实现文件夹的压缩与解压

这是学校数据结构与算法设计课程的PJ,旨在实现类似zip软件的压缩与解压功能。我在几乎有空就在写代码的情况下两周完成了这个项目。
目前网上能够搜索到的资料对于单个文件和文本文件的压缩与解压较多,而对文件夹与大文件的压缩与解压较少。所以想在这里把自己的一些想法和思路写下来,供大家交流与参考。因为自己目前正在读大二,这也是自己写的第一篇CSDN博客,之前只学过C与C++,java是纯自学的,并且这也是用java写的第一个PJ,所以写得比较烂,希望有前辈能够指点指点!

程序运行结果

压缩与解压1G大小的文件夹文件需要1分40秒,压缩率为35.0%。

主要思路

单个文件

压缩时,用FileInputstream字节流读取文件后,得到一个文件字符与出现频率对应的字符频率表,用该表构造哈夫曼树之后,通过遍历的方法找到每个函数的哈夫曼编码,再得到一个字符与哈夫曼编码对应的字符哈夫曼编码表,再次读取文件,建立一个后缀为.hrj(这里是我中文名的拼音首字母)的压缩文件,先存入文件格式,再存入文件中字符频率表,再通过索引获取每个字符(字节)的哈夫曼编码,翻译文件内容,以字节流写入压缩文件,得到压缩后的文件。写文件用的是FileOutputstream字节流。
解压时,先利用压缩文件名和压缩文件中保存的文件格式新建一个解压文件,然后读取文件中的字符与频率,得到一个和读取文件时一样的字符频率表,用该表建立一个新的哈夫曼树,通过遍历二进制字符串,查找到字符串所对应的字符,写入解压文件。解压完成。

文件夹

对于文件夹文件的压缩,用文件夹名新建一个压缩文件,然后用遍历文件夹中的所有文件:如果是文件夹,则存入文件名。如果是文件,则存入文件名和文件内容。解压时,一次读取每一个文件,如果是文件夹,则创建路径,然后新建文件夹,如果是文件,则新建文件。实现过程和单个文件的压缩解压差不多。但实现起来是比较困难的。

过程中的一些问题与优化

一些关键环节

哈夫曼编码的获得

这一步通过建立哈夫曼树得到,在其他很多资料中有详细的讲解,我在这里就略过了。

文件的读取

我这里使用的是BufferedInputStream,最开始我是以字节为单位读取的,后来发现文件大小达到100M时读取文件就会花掉很多的时间,于是在查阅资料后改用以字节数组为单位读取,之后通过遍历字节数组的方式再读取单个字节,这样优化过后,我的文件读取速度快了一百倍(没有夸张)。

文件的解压

写文件的解压的代码时仿佛没有遇到很大的困难。
在解压时获取二进制码的方法我最开始是采用的新建arraylist然后一一处理每个字节,然后连接arraylist,再截取arraylist.做了如下优化:

 static int findchar(Haffmantree tree, ArrayList str) {
        Unit help = tree.root;
        int l = str.size();
        int m = 0;

        while (help.Lchild != null && m < str.size()) {
            if (str.get(m++) == 0)
                help = help.Lchild;
            else help = help.Rchild;
        }

        if (help.content == null)
            return -1;
        else if (l - m == 0) //这个步骤也是我进行优化的一个地方,可以对比下面几行代码,这使得我在处理str时不至于remove整个str长度,将大文件解压时间缩短到了原来的三分之一。
            str.clear();
        else {
            while (str.size() > l - m)
                str.remove(0);  //这一步的remove主要造成了运行速度较慢,因为对于一个str来说,可能会remove十多次,我目前还没有找到很好的优化方法。

        }
        return (int) help.content;
    }

但1G文件的处理事件仍然需要6分钟,造成速度不够快的原因主要是由哈夫曼编码查找字符时的remove和add操作较慢。
在这几天内一直卡在处理1G文件需要花费6分钟而无法提速,后来经过助教的提醒,用位移方法进行了优化。

{//通过这样的方式对字节直接进行操作,遍历哈夫曼树找到哈夫曼编码对应字符
    by = temp >> 7;
    by = by & 0x1;
    temp = temp << 1;
    if (by == 0)
      help = help.Lchild;
      else help = help.Rchild;
}

直接对字节进行位移运算求最高位是0还是1,以获取二进制编码,省去了一系列arraylist操作,将处理1G文件的时间缩短到了1分40秒。

文件夹的处理

我在处理文件夹的问题时,主要遇到的困难时如何判断“一个文件是否结束,我是否该进行下一个文件的读取”,我之前有想到用储存一个串字节作为结束符,但是这样的方法总不能排除文件中含有这个结束符的特殊情况,有可能造成bug,所以我最终用了保存文件字节长度的方法,来判断一个文件是否读取结束。
我在这里使用了六个boolean变量:
boolean isnewfile//是否需要新建文件,并进行下一个文件的读取
boolean isreadingname//是否正在读取文件名
boolean isreadingnamelength//是否正在读取文件名长度
boolean isreadingfilelengthstringlength//是否正在读取文件长度的二进制码的长度
boolean isreadingfilelength//是否正在读取文件长度的二进制码
boolean isreadingfile//是否正在读取文件
通过判断变量为true or false 来判断进行到哪一个环节了,从而进行不同的操作。虽然这样看上去比较麻烦,但是确实可以让代码思路清晰,不会出错。

代码实现

  • main函数

  • 用户指引

  • 一些辅助函数

  • 树的节点类

  • 实现压缩与解压操作函数

完整代码下载链接:哈夫曼算法实现文件夹的压缩与解压

你可能感兴趣的:(项目)