文件压缩

过程

  1. 统计文件中字符出现的次数,利用数据结构中的堆建造Huffman树,字符出现次数多的编码短,出现次数少的编码长;
  2. 根据建造好的Huffman树形成编码,以对文件进行压缩;
  3. 将文件中出现的字符以及他们出现的次数写入配置文件,以便后续的解压缩;
  4. 根据配置文件读取相关信息,重建Huffman树,对压缩后的文件进行译码。
  5. 首先观察解压的文件和原文件是否相同,再通过Beyond Compare 4软件进行对比,验证程序的正确性。

    这里写图片描述
    可以发现一个规律:权值越小的,它的哈夫曼编码越长,权值越大的,哈夫曼编码越短

文件压缩的过程:

首先要统计待压缩文件中各字符出现的次数,然后构造哈弗曼编码,把编码写入压缩文件,不够一个字节的就在后面补零,因为要解压缩,所以还得写一个配置文件,配置文件里面写每个字符出现的次数。

具体过程:
a.读取文件,将每个字符,该字符出现的次数和权值构成哈夫曼树;
b.哈夫曼树是利用小堆构成,字符出现次数少的节点指针存在堆顶,出现次数多的在堆底;
c.每次取堆顶的两个数,再将两个数相加进堆,直到堆被取完,这时哈夫曼树也建成;
d.从哈夫曼树中获取哈夫曼编码,然后再根据整个字符数组,来获取出现了的字符的编码;
e.获取编码后每次凑满8位就将编码串写入到压缩文件;
f.写好配置文件,统计每个字符及其出现次数,保存到配置文件中。

文件解压的过程:

先去读配置文件,构建哈弗曼树和哈弗曼编码,用压缩文件里的编码去哈弗曼树里面找,找到叶子结点,就把叶子节点的字符写入解压缩文件中。

具体过程:
a.读取配置文件,统计所有字符的个数;
b.构建哈夫曼树,读解压缩文件,将所读到的编码字符的这个节点所含的字符,写入到解压缩文件中,直到将压缩文件读完;
c.解压缩完成之后,利用Beyond Compare 4软件,进行文件的测试。

项目测试:

通过Beyond Compare 4软件,对文件压缩前和压缩后的内容进行对比,验证程序的正确性。 性能测试,在release版本下会更高效一些,时间会缩短很多,因为 Debug 通常称为调试版本,它包含调试信息,并且不作任何优化,便于程序员调试程序,也就是博主编程用的版本;Release 称为发布版本,它往往是进行了各种优化,使得程序在代码大小和运行速度上都是最优的,以便用户很好地使用。

源代码:
FileCompress.h

#pragma once
#define _CRT_SECURE_NO_WARNINGS 1
#include<stdlib.h>
#include"HuffmanTree.h"
#include<algorithm>
#include<string>
#include<stdio.h>
#define __DEBUG__

struct CharInfo
{
    char _ch;//出现的字符
    long long _count;//字符出现的次数
    string _code; //huffuman code

    CharInfo(long long count = 0)
        :_count(count)
    {}

    CharInfo operator +(const CharInfo& info)
    {
        CharInfo ret;
        ret = _count + info._count;
        return ret;
    }
    //比较的是次数 次数不等于0则入堆
    bool operator != (const CharInfo& info)
    {
        return _count != info._count;
    }
    bool operator >(const CharInfo& info)
    {
        return _count > info._count;
    }

    bool operator <(const CharInfo& info)   
    {
        return _count < info._count;
    }
};

class FileCpmoress
{
    typedef HuffumanTreeNode<CharInfo> Node;
public:
    FileCpmoress()
    {
        //依次初始化
        for (size_t i = 0; i < 256; ++i)
        {
            //汉字的编码是两个字节 char是0-127
            //会发生数组越界,解决方法是将char强转为unsigned char,可表示范围为0~255。
            _infos[(unsigned char)i]._ch = i;//i既是下标也是Ascall码
            _infos[(unsigned char)i]._count = 0;//出现的次数
        }
    }

    //内部类
    struct Config
    {
        char _ch;
        long long _count;
    };

    //压缩

    //input.txt->input.txt.huffuman

    void Compress(const char* file)

    {
        assert(file);

        //1,统计字符出现的次数

        //文本方式打开,会对‘\n’进行特殊处理,

        //那如果这个字符本身就是'\n'.这就会出现问题,

        //所以使用二进制打开,特点:不进行任何处理,是什么就是什么。

        FILE* fout = fopen(file, "rb");

        assert(fout);

        char ch = fgetc(fout);

        //feof是C语言标准库函数,其原型在stdio.h中,

        //其功能是检测流上的文件结束符,如果文件结束,则返回非0值,

        //否则返回0,文件结束符只能被clear()清除。

        while (!feof(fout))
        {

            _infos[(unsigned char)ch]._count++;

            ch = fgetc(fout);

        }
        //用charInfo构建的树里面既有权值又有次数(为了得到huffuman编码)

        //出现次数大于0的才比较大小,所以需要重载次数比较的运算符

        //2,构建huffuman tree

        CharInfo invalid;//非法值

        invalid._count = 0;//出现次数0次的为非法值

        HuffumanTree<CharInfo> tree(_infos, 256, invalid);


        //3.生成huffuman code(叶子节点)

        string code;

        _GetHuffumanCode(tree.GetRoot(), code);



        //4,压缩(8位)

        //fseek设置文件指针的位置,相对于当前位置

        string compressFile = file;

        compressFile += ".huffuman";

        FILE* fin = fopen(compressFile.c_str(), "wb");

        assert(fin);


        ////配置---解压缩是重建huffuman tree

        Config info;

        for (size_t i = 0; i < 256; ++i)

        {

            if (_infos[i]._count > 0)

            {

                info._ch = _infos[i]._ch;

                info._count = _infos[i]._count;

                fwrite(&info, sizeof(Config), 1, fin);

            }

        }
        ////分隔  多写8个字节

        info._count = 0;

        fwrite(&info, sizeof(Config), 1, fin);

        //文件指针 偏移量 基准位置

        char value = 0;//8个位

        long long count = 0;

        fseek(fout, 0, SEEK_SET);

        ch = fgetc(fout);

        /*EOF的16进制为0xFF(十进制为 - 1),特用在文本文件中,

        因为在文本文件中数据是以ASCⅡ码值的形式存放,普通字符的ASCⅡ码的范围是32到127(十进制)

        与EOF不冲突,因此可以直接使用;但是在二进制文件中,数据有可能出现 - 1

        因此不能用EOF来作为二进制文件的结束标志,可以通过feof函数来判断。

        也就是说feof这个函数可用于判断文件是否结束(包括文本文件和二进制文件)。*/

        while (!feof(fout))//

        {

            string& code = _infos[(unsigned char)ch]._code;

            //用位运算 左移是往高位移与方向无关

            for (size_t i = 0; i < code.size(); ++i)

            {

                value <<= 1;

                if (code[i] == '0')

                {

                    value |= 0;

                }

                else if (code[i] == '1')

                {

                    value |= 1;

                }

                ++count;

                if (count == 8)

                {

                    fputc(value, fin);

                    value = 0;

                    count = 0;

                }

            }

            ch = fgetc(fout);

        }

        if (count != 0)

        {

            value <<= (8 - count);

            fputc(value, fin);

        }

        fclose(fin);

        fclose(fout);

    }

    //解压缩

    //input.txt.huffuman.txt->input.txt

    void Uncompress(const char* file)

    {

        //1.读取配置文件信息--字符出现的次数

        assert(file);

        string uncompressFile = file;

        size_t position = uncompressFile.rfind(".");//倒着找到.的位置

        assert(position != string::npos);//不存在

        uncompressFile = uncompressFile.substr(0, position);

        uncompressFile += ".unhuffuman";


        FILE* fout = fopen(file, "rb");//可以考虑用智能指针

        assert(fout);

        FILE* fin = fopen(uncompressFile.c_str(), "wb");

        assert(fin);


        Config info;

        while (1)

        {

            fread(&info, sizeof(Config), 1, fout);

            if (info._count == 0)

            {

                break;

            }

            else

            {

                _infos[(unsigned char)info._ch]._ch = info._ch;

                _infos[(unsigned char)info._ch]._count = info._count;

            }

        }


        //重建huffuman tree

        CharInfo invalid;

        invalid._count = 0;

        HuffumanTree<CharInfo> tree(_infos, 256, invalid);

        Node* root = tree.GetRoot();

        long long count = root->_w._count;


        //哈夫曼树的头结点的权值内的count存的是原来文件的字符个数


        //解压缩

        char ch = fgetc(fout);

        Node* cur = root;

        while (!feof(fout))

        {

            for (int pos = 7; pos >= 0; --pos)

            {

                if (ch &(1 << pos))

                {

                    cur = cur->_right;

                }

                else

                {

                    cur = cur->_left;

                }

                if (cur->_left == NULL && cur->_right == NULL)

                {

                    fputc(cur->_w._ch, fin);

                    cur = root;

                    --count;

                    if (count == 0)

                    {

                        break;

                    }

                }

            }

            ch = fgetc(fout);

        }

        uncompressFile.erase(position, uncompressFile.size() - position + 1);//删除.之后的字符串

        fclose(fin);

        fclose(fout);

    }

protected:

    //void _GetHuffumanCode(Node* root)

    //{

    //  if (root == NULL)

    //      return;

    //  //叶子节点

    //  if (root->_left == NULL && root->_right == NULL)

    //  {

    //      //root是叶子节点,节点的权值_w是CharInfo类型,这个字符就是CharInfo中的ch

    //      //直接用引用,减少系统开销



    //      string& code = _infos[root->_w._ch]._code;

    //      Node*  cur = root;//叶子节点

    //      Node* parent = cur->_parent;

    //      //循环到parent为NULL就结束

    //      while (parent)

    //      {

    //          if (cur == parent->_left)

    //          {

    //              code.push_back('0');

    //          }

    //          else if (cur == parent->_right)

    //          {

    //              code.push_back('1');

    //          }

    //          cur = parent;

    //          parent = cur->_parent;

    //      }

    //      //此时为逆置的编码

    //      reverse(code.begin(), code.end());

    //      return;

    //  }

    //  _GetHuffumanCode(root->_left);

    //  _GetHuffumanCode(root->_right);

    //}



    bool ReadLine(FILE *fOut, string& line)

    {

        char ch = fgetc(fOut);

        if (feof(fOut))

            return false;

        while (!feof(fOut) && ch != '\n')

        {

            line += ch;

            ch = fgetc(fOut);

        }

        return true;

    }



    void _GetHuffumanCode(Node* root, string& code)

    {

        //遍历

        if (root == NULL)

            return;

        if (root->_left == NULL  && root->_right == NULL)

        {

            _infos[(unsigned char)root->_w._ch]._code = code;

            return;

        }

        _GetHuffumanCode(root->_left, code + '0');

        _GetHuffumanCode(root->_right, code + '1');

    }

private:

    CharInfo _infos[256];//根据Ascall码统计字符出现的次数

};



void TestFileCompess()

{

    /*FileCpmoress fc;

    fc.Compress("ct.png");*/

    FileCpmoress f1;

    f1.Uncompress("ct.png.huffuman");

}

HuffmanTree.h

#pragma once

#include"Heap.h"

#include<functional>



template<class W>//权值

struct HuffumanTreeNode

{

    HuffumanTreeNode<W>* _left;

    HuffumanTreeNode<W>* _right;

    HuffumanTreeNode<W>* _parent;



    W _w;//权值



    //W不一定是整型,可能是自定义类型,所以要给引用

    HuffumanTreeNode(const W& w)

        :_w(w)

        , _left(NULL)

        , _right(NULL)

        , _parent(NULL)//构造三叉连

    {}

};



template<class W>

class HuffumanTree

{

public:

    typedef HuffumanTreeNode<W> Node;



    HuffumanTree()

        :_root(NULL)

    {}



    HuffumanTree(W* a, size_t n, const W& invalid)//根据权值构建树

    {

        //贪心算法选最小的(建小堆)//也可以使用库里面的优先级队列

        struct NodeCompare

        {

            bool operator()(Node* l, Node* r)

            {

                return l->_w < r->_w;

            }

        };

        Heap<Node*, NodeCompare> minHeap;

        for (size_t i = 0; i < n; ++i)

        {

            if (a[i] != invalid)

                minHeap.Push(new Node(a[i]));//将数组的元素都入堆并建小堆

        }

        //当size >1 时,堆中的元素都要选出最小的两个相加后再入堆

        while (minHeap.Size() > 1)

        {

            Node* left = minHeap.Top();

            minHeap.Pop();

            Node* right = minHeap.Top();

            minHeap.Pop();



            Node* parent = new Node(left->_w + right->_w);

            parent->_left = left;

            parent->_right = right;

            left->_parent = parent;

            right->_parent = parent;



            minHeap.Push(parent);//相加的结果要再放回去

        }

        _root = minHeap.Top();

    }



    //释放节点

    ~HuffumanTree()

    {

        Destroy(_root);

        _root = NULL;

    }



    //二叉树的释放

    void Destroy(Node* root)

    {

        if (root == NULL)

            return;

        Destroy(root->_left);

        Destroy(root->_right);

        delete root;

    }



    Node* GetRoot()

    {

        return _root;

    }



protected:

    HuffumanTree(const HuffumanTree<W>&);//防拷贝

    HuffumanTree<W>& operator=(const HuffumanTree<W>&);

    Node* _root;

};



void TestHuffumanTree()

{

    int a[] = { 1, 2, 3, 4 };

    HuffumanTree<int> t(a, sizeof(a) / sizeof(a[0]), 0);

}

Heap.h

#pragma once

#include<vector>  



template<class T>

struct Greater

{

    bool operator()(const T& a, const T& b)

    {

        return a > b;

    }

};



template<class T>

struct Less

{

    bool operator()(const T& s, const T& b)

    {

        return a < b;

    }

};



template<class T, class Compare>

class Heap

{

public:

    Heap()

    {}



    Heap(T* a, int n)

    {

        _a.reserve(n);//增容(可以直接拷贝数据)  

        for (int i = 0; i < n; i++)

        {

            _a.push_back(a[i]);

        }

        //调整成堆  

        for (int j = (_a.size() - 2) / 2; j >= 0; --j)

        {

            //向下调整  

            _AjustDown(j);

        }

    }



    void Push(const T& x)

    {

        _a.push_back(x);

        _AjustUp(_a.size() - 1);

    }



    void Pop()

    {

        assert(!_a.empty());

        swap(_a[0], _a[_a.size() - 1]);

        _a.pop_back();

        _AjustDown(0);

    }





    T& Top()

    {

        assert(!_a.empty());

        return _a[0];

    }



    size_t Size()

    {

        return _a.size();

    }



    bool Empty()

    {

        return _a.empty();

    }

protected:

    //push_heap算法:向上调整

    void _AjustUp(int child)

    {

        assert(!_a.empty());

        int parent = (child - 1) >> 1;



        while (child>0)

        {

            //如果孩子节点的值大于父节点的值  

            Compare com;

            if (com(_a[child], _a[parent]))

            {

                swap(_a[child], _a[parent]);

                child = parent;

                parent = (child - 1) >> 1;

            }

            else

            {

                break;

            }

        }

    }

protected:

    //pop_heap:向下调整算法

    void _AjustDown(int root)

    {

        int parent = root;

        int child = parent * 2 + 1;

        while (child < _a.size())

        {

            Compare com;

            if ((child + 1 < _a.size()) && (_a[child + 1] > _a[child]))

            {

                ++child;

            }

            if (com(_a[child], _a[parent]))

            {

                swap(_a[child], _a[parent]);

                parent = child;

                child = parent * 2 + 1;

            }

            else

            {

                break;

            }

        }

    }

private:

    vector<T> _a;

};



//void Test()

//{

//  int a[] = { 1, 6, 8, 2, 3, 5, 7, 4, 9 };

//  int len = sizeof(a) / sizeof(a[0]);

//  Heap<int, Greater<int>>  h(a, len);

//  h.Top();

//  h.Push(25);

//  h.Pop();

//  while (!h.Empty())

//  {

//      cout << h.Top() << " ";

//      h.Pop();

//  }

//  cout << endl;

//}

Test.cpp

#define _CRT_SECURE_NO_WARNINGS 1

#include<iostream>

#include<assert.h>

using namespace std;

#include"HuffmanTree.h"

#include"FileCompress.h"







int main()

{

    //TestHuffumanTree();

    TestFileCompess();

    //Test();

    system("pause");

    return 0;

}
  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值