利用Huffman树实现文件压缩

项目名称

利用Huffman树实现文件压缩


项目概要

使用的编辑语言是C++,项目目的是能够实现对文件的压缩及解压,涉及到的技术主要有huffman树的实现,文件的IO操作,优先级队列等;

  • huffman算法原理: Huffman算法是一种无损压缩算法,Huffman需要得到每种字符出现概率,通过计算字符序列中每种字符出现的频率,为每种字符进行唯一的编码设计,使得频率高的字符占的位数短,而频率低的字符长,来达到压缩的目的。

  • 压缩原理:首先是处理源文件,统计源文件的字符数,将源文件以IO流的方式打开,每次从流中读取一个字符,进行处理,并将其记录到已有的字符哈希表中,由于一个字符占一个字节(0 ~ 255),因此哈希表的空间设置为256即可。完成统计之后,用哈希表来构建huffman数,生成huffman编码,根据01编码将每个字符以一个到多个比特位的方式来写入压缩文件即可。

  • 解压原理:在压缩文件中得到构建huffman树的所有数据(这里需要在压缩时将这些统计好的的数据同时写入压缩文件),根据这些数据将就可以将压缩时使用的huffman数还原,即可解析编码。之后读取压缩文件的所有编码,通过编码在huffman树上进行查找,得到原有的字符,并依次写入解压文件。


设计思路

整体思路

  1. 整个过程是依赖于huffman树,因此要构建出一个可供我们使用的Huffman树
  2. 压缩时,操作源文件,以字符形式读取文件信息,进行字符个数统计,将统计结果作为数据构建huffman树,生成huffman编码,进行压缩。
  3. 解压时,从压缩文件取出构建huffman树的数据,根据huffman树解析压缩文件。

具体实现

Huffman树的构建

  1. 将数据依次放入优先级队列中,构建成一个小堆。
  2. 每次从堆中拿出两个权值最小的数,进行操作,将操作后的权值再次放入堆中。
  3. 直到堆中的数据为空,就完成了huffman数的构造。

主要实现:

    //构建Huffman树需要三个参数,分别为:统计之后的数据数组,数据个数,非空元素
    HuffmanTree(T* data, size_t size, const T& end)
    {
        //使用优先级队列构建一个小堆   < 是大堆  > 是小堆
        priority_queue<Node*, vector<Node*>, compare> heap;

        //1.将所有数据构成结点,录入小堆中
        for (size_t i = 0; i < size; ++i)
        {
            if (data[i] != end)
                heap.push(new Node(data[i]));
        }

        //2. 开始构建HuffmanTree,每堆中取两个最小的数据,作为根节点的左子树和右子树,
        //   并将根节点录入堆中,直到堆中没数据
        Node* pN1;
        Node* pN2;
        Node* newNode=NULL;
        while (heap.size() > 1)
        {
            pN1 = heap.top();
            heap.pop();
            pN2 = heap.top();
            heap.pop();
            newNode = new Node(pN1->_data + pN2->_data);
            pN1->_parent = newNode;
            pN2->_parent = newNode;
            if (pN1->_data > pN2->_data)//出现频率高的放左边
            {
                newNode->_left  = pN1;
                newNode->_right = pN2;
            }
            else
            {
                newNode->_left = pN2;
                newNode->_right = pN1;
            }
            heap.push(newNode);
        }
        _root = newNode;
    }

压缩文件

文件压缩,首先统计源文件的所有字符,当完成字符统计之后,将数据传给huffman数的构造函数构造huffman树,之后便可以根据数进行生成huffman编码,将统计数据也要压入压缩文件,方便在进行解压之时,能够构造出同一棵huffman树。

在进行压缩时,从源文件读取一个字符,得到将字符的huffman编码,将编码以比特位的形式写入压缩文件,即够八位,进行一次写入。

主要实现:

//从压缩文件中读取一个字符,开始从HuffmanTree上开始查找,直到叶子结点,写入解压文件
    int ch = 0;
    int num = _root->_data._count;
    Node* cur = _root;
    while ( num > 0)
    {
        ch = fgetc(fp_r);
        for (int i = 0; i < 8; i++)
        {
            if ((ch & (1 << i)) ) //为1
                cur = cur->_left;
            else
                cur = cur->_right;
            if (cur && cur->_left == NULL && cur->_right == NULL)//找到叶子结点
            {
                --num;
                fputc((int)cur->_data._ch, fp_w);
                cur = _root;
                if (num == 0)
                    break;
            }
        }
    }

解压文件

解压时,向从压缩文件中读取统计数据,即:

//1.先从压缩文件中将要构成HuffmanTree的数据(字符和字符总数)取出
struct Data { char _ch; size_t _count; }data;
FILE* fp_r = fopen(file, "rb");
fread(&data, sizeof(data), 1, fp_r);
int num = 0;
while (data._count != 0)
{
    _hashtable[(unsigned char)data._ch]._count = data._count;
    fread(&data, sizeof(data), 1, fp_r);
    cout << num++ << endl;
}

之后,读取编码,开始从huffman树上解码,解码的过程是,从树的root开始,遇到1即走树的左子树,遇到0走树的右子树,直到走到叶子结点为止,将叶子结点上的字符,输入解压文件即可。

主要实现:

int ch = 0;
    int num = _root->_data._count;
    Node* cur = _root;
    while ( num > 0)
    {
        ch = fgetc(fp_r);
        for (int i = 0; i < 8; i++)
        {
            if ((ch & (1 << i)) ) //为1
                cur = cur->_left;
            else
                cur = cur->_right;
            if (cur && cur->_left == NULL && cur->_right == NULL)//找到叶子结点
            {
                --num;
                fputc((int)cur->_data._ch, fp_w);
                cur = _root;
                if (num == 0)
                    break;
            }
        }
    }

测试阶段:

经过多次的测试,虽然能够保证无损压缩,但是还是存在两个缺点:

  1. 压缩率不能保证,有些文件压缩之后比压缩之前的文件下不了多少。
  2. 速度慢,仅仅是4M左右的文件,也会耗费几秒,算法的处理还是不够。

如果大家有什么好的想法,可以想我提出。

项目源码地址https://github.com/zuo369301826/FileCompress-project

综合实验: 1. 问题描述 利用哈夫曼编码进行通信可以大大提高信道利用率,缩短信息传输时间,降低传输成本。这要求在发送端通过一个编码系统对待传输数据预先编码,在接收端将传来的数据进行译码(复原)。对于双工信道(即可以双向传输信息的信道),每端都需要一个完整的编/译码系统。试为这样的信息收发站编写一个哈夫曼码的编/译码系统。 2. 基本要求 一个完整的系统应具有以下功能: (1) I:初始化(Initialization)。从终端读入字符集大小n,以及n个字符和n个权值,建立哈夫曼,并将它存于文件hfmTree中。 (2) E:编码(Encoding)。利用已建好的哈夫曼(如不在内存,则从文件hfmTree中读入),对文件ToBeTran中的正文进行编码,然后将结果存入文件CodeFile中。 (3) D:译码(Decoding)。利用已建好的哈夫曼文件CodeFile中的代码进行译码,结果存入文件Textfile中。 (4) P:印代码文件(Print)。将文件CodeFile以紧凑格式显示在终端上,每行50个代码。同时将此字符形式的编码文件写入文件CodePrin中。 (5) T:印哈夫曼(Tree printing)。将已在内存中的哈夫曼以直观的方式(比如)显示在终端上,同时将此字符形式的哈夫曼写入文件TreePrint 中。 3. 测试数据 用下表给出的字符集和频度的实际统计数据建立哈夫曼,并实现以下报文的编码和译码:“THIS PROGRAME IS MY FAVORITE”。 字符 A B C D E F G H I J K L M 频度 186 64 13 22 32 103 21 15 47 57 1 5 32 20 字符 N O P Q R S T U V W X Y Z 频度 57 63 15 1 48 51 80 23 8 18 1 16 1
Java实现压缩与解压缩ZIP   import java.io.BufferedInputStream;   import java.io.BufferedOutputStream;   import java.io.File;   import java.io.FileInputStream;   import java.io.FileOutputStream;   import java.util.zip.ZipEntry;   import java.util.zip.ZipOutputStream;   public class Zip {   static final int BUFFER = 2048;   public static void main(String argv[]) {   try {   BufferedInputStream origin = null;   FileOutputStream dest = new FileOutputStream("E:\\test\\myfiles.zip");   ZipOutputStream out = new ZipOutputStream(new BufferedOutputStream(   dest));   byte data[] = new byte[BUFFER];   File f = new File("e:\\test\\a\\");   File files[] = f.listFiles();   for (int i = 0; i < files.length; i++) {   FileInputStream fi = new FileInputStream(files[i]);   origin = new BufferedInputStream(fi, BUFFER);   ZipEntry entry = new ZipEntry(files[i].getName());   out.putNextEntry(entry);   int count;   while ((count = origin.read(data, 0, BUFFER)) != -1) {   out.write(data, 0, count);   }   origin.close();   }   out.close();   } catch (Exception e) {   e.printStackTrace();   }   }   }   解压缩的   import java.io.BufferedInputStream;   import java.io.BufferedOutputStream;   import java.io.File;   import java.io.FileOutputStream;   import java.util.Enumeration;   import java.util.zip.ZipEntry;   import java.util.zip.ZipFile;   public class UnZip {   static final int BUFFER = 2048;   public static void main(String argv[]) {   try {   String fileName = "E:\\test\\myfiles.zip";   String filePath = "E:\\test\\";   ZipFile zipFile = new ZipFile(fileName);   Enumeration emu = zipFile.entries();   int i=0;   while(emu.hasMoreElements()){   ZipEntry entry = (ZipEntry)emu.nextElement();   //会把目录作为一个file读出一次,所以只建立目录就可以,之下的文件还会被迭代到。   if (entry.isDirectory())   {   new File(filePath + entry.getName()).mkdirs();   continue;   }   BufferedInputStream bis = new BufferedInputStream(zipFile.getInputStream(entry));   File file = new File(filePath + entry.getName());   //加入这个的原因是zipfile读取文件是随机读取的,这就造成可能先读取一个文件   //而这个文件所在的目录还没有出现过,所以要建出目录来。   File parent = file.getParentFile();   if(parent != null && (!parent.exists())){   parent.mkdirs();   }   FileOutputStream fos = new FileOutputStream(file);   BufferedOutputStream bos = new BufferedOutputStream(fos,BUFFER);   int count;   byte data[] = new byte[BUFFER];   while ((count = bis.read(data, 0, BUFFER)) != -1)   {   bos.write(data, 0, count);   }   bos.flush();   bos.close();   bis.close();   }   zipFile.close();   } catch (Exception e) {   e.printStackTrace();   }   }   }
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值