文件压缩

文件压缩原理:
1. 建立最小堆
2. 利用最小堆的数据特性建立huffman树结构
3. 统计要压缩的文件中字符出现次数
4. 以字符出现次数为树的权重建立哈夫曼树
5. 遍历huffman树获取字符编码和行数(真正要存储的数据)
6. 创建一个文件(test.compress)存储压缩数据(行数、配置信息)
7. 将字符编码(用字符在huffman树从根节点到叶子结点的路径编码)以位图方式存储在test.compress文件中
文件解压原理:
1. 根据压缩文件test.compress内的配置信息重建huffman树
2. 根据行数跳过配置信息,读取压缩后的数据本体
3. 根据压缩位图中的二进制信息遍历重建的huffman树
4. 建立解压后的文件test.uncompress文件存储遍历得到的字符
这里写图片描述

源码:
heap.h

#pragma once
#include <iostream>
using namespace std;
#include <vector>
#include <assert.h>

template<class T>
struct Less
{
    bool operator()(const T& left,const T& right)
    {
        return left<right;
    }
};
template<class T>
struct Great
{
    bool operator()(const T& left,const T& right)
    {
        return left>right;
    }
};
template<class T,class Compare=Less>
class Heap
{
public:
    Heap()
        :_heap(NULL)
    {}
    Heap(const T* array,size_t size)
        :_heap(NULL)
    {
        _heap.resize(size);
        for(size_t i=0; i<size; i++)
        {
            _heap[i]=array[i];
        }
        for (int root=(size-2)/2; root>0; root--)
        {
            BuildDown(root);
        }
    }

    void Push(const T& data)
    {
        _heap.push_back(data);
        for (int root=(_heap.size()-1)/2; root>=0; root--)
        {
            BuildDown(root);
        }
    }

    void Pop()
    {
        assert(!_heap.empty());
        size_t size=_heap.size()-1;
        swap(_heap[size],_heap[0]);
        _heap.pop_back();
        for (int root=_heap.size(); root>=0; root--)
        {
            BuildDown(root);
        }
    }
    bool Empty()
    {
        return _heap.empty();
    }
    size_t Size()
    {
        return _heap.size();
    }
    T Top()
    {
        return _heap[0];
    }
private:
    void BuildDown(size_t root)
    {
        size_t parent=root;//倒数第一个非叶子节点
        size_t size=_heap.size();
        size_t child=root*2+1;
        while (child<size)
        {
            if ((child+1<size)&&Compare()(_heap[child+1],_heap[child]))//右孩子存在情况下于左孩子比较找出最小值
            {
                child=child+1;
            }
            if (Compare()(_heap[child],_heap[parent]))//若小于父母节点则交换
            {
                swap(_heap[child],_heap[parent]);
                parent=child;
                child=parent*2+1;
            }
            else 
                return;
        }
    }
private:
    vector<T> _heap;
};
void test()
{
    int arr[]={12,18,24,3,32,74,5};
    size_t size=sizeof(arr)/sizeof(arr[0]);
    Heap<int,Less<int>> h(arr,sizeof(arr)/sizeof(arr[0]));
    h.Push(1);
    h.Pop();
}

HuffmanTree.hpp

#include "heap.hpp"
template<class T>
struct HuffmanNode
{
    HuffmanNode(const T& weight)
        :_Lchild(NULL)
        ,_Rchild(NULL)
        ,_weight(weight)
    {}
    //HuffmanNode* _Parent;
    HuffmanNode* _Lchild;
    HuffmanNode* _Rchild;
    T _weight;
};
template<class T>
class Tree
{
    typedef HuffmanNode<T> Node;
public:
    Tree()
    {}
    Tree(T* array,size_t size,const T& invalid)
    {
        CreatHuffmanTree(array,size,invalid);
        //invalid防止只有一个节点的情况
    }
    ~Tree()
    {
        Destory(_pRoot);
    }
    Node* Root()
    {
        return _pRoot;
    }
private:
    void CreatHuffmanTree(T* array,size_t size,const T& invalid)
    {
        struct Compare//重载比较器
        {
            bool operator()(const Node* left,const Node* right)
            {
                return left->_weight<right->_weight;        
            }
        };
        Heap<Node*,Compare> h;
        for(size_t i=0; i<size; i++)
        {
                if (array[i] != invalid)
                {
                    Node* pRoot=new Node(array[i]);
                    h.Push(pRoot);
                }
        }
        if (h.Size())
        {
            while (h.Size()>1)
            {
                Node *Left=h.Top();
                h.Pop();
                Node *Right=h.Top();
                h.Pop();
                Node* Parent=new Node(Left->_weight+Right->_weight);
                Parent->_Lchild=Left;

                Parent->_Rchild=Right;
                h.Push(Parent);
            }
            _pRoot=h.Top();
        }
        else
            return ;

        //int tmp=(int)_pRoot->_weight._ch;
        //int tmp1=(int)_pRoot->_Lchild->_weight._ch;
        //int tmp2=(int)_pRoot->_Rchild->_weight._ch;

    }
    void Destory(Node* _pRoot)
    {
        if (_pRoot)
        {
            Destory(_pRoot->_Lchild);
            Destory(_pRoot->_Rchild);
            delete _pRoot;
            _pRoot=NULL;
        }
    }
private:
    Node* _pRoot;
};

file_compress.h

#define  _CRT_SECURE_NO_WARNINGS
#include "HuffmanTree.hpp"
#include <string>
#include <windows.h>
#include <cstdlib>
struct File_Info
{
    unsigned char _ch;//字符
    long long _count;//出现次数
    string _code;//字符编码

    File_Info(const long long& count=0)
        :_count(count)
    {}

    File_Info(const char ch)
        :_ch(ch)
    {}

    File_Info operator+(const File_Info& f)const
    {
        return _count+f._count;
    }

    bool operator<(const File_Info& f)const
    {
        return _count<f._count;
    }

    bool operator!=(const File_Info& f)const
    {
        return _count!=f._count;
    }
};

class FileCompress
{
    typedef HuffmanNode<File_Info> Node;
public:

    void Compress(const char* filename)//文件压缩
    {
        FILE* pfr=fopen(filename,"r");
        if (pfr==NULL)
        {
            perror("fail to open the file");
        }
        //统计文件中字符出现次数
        for (int i=0; i<256; i++)
        {
            _Info[i]._ch = i;//所有字符种类
        }

        int ch = fgetc(pfr);
        while (ch!=EOF)
        {
            _Info[ch]._count++;
            ch=fgetc(pfr);
        }
        fclose(pfr);//重置文件指针
        FILE* fpr=fopen(filename,"r");
        Tree<File_Info> tree(_Info, 256, File_Info());//以File_Info结构体为树的权重建立哈夫曼树
        Node* root=tree.Root();

        string code;
        int line=0;
        _GetTreeNode(root,code,line);//获取字符编码和行号

        //添加文件后缀,并重置文件指针
        fseek(pfr,0,SEEK_SET);
        string write(filename);
        write=write+".compress";//存放压缩后数据

        FILE* pfw=fopen(write.c_str(),"w");

        fputs(write.c_str(),pfw);//写入文件名
        fputc('\n',pfw);
        char Line[4];//存储行数
        _itoa(line,Line,10);//转化行数为十进制字符放入文件
        fputs(Line,pfw);
        fputc('\n',pfw);
        for(int i=0; i<256; i++)//写入文件配置信息
        {
            if(_Info[i]._count)//排除没有出现的字符
            {
                fputc(_Info[i]._ch,pfw);
                char arr[126];
                _itoa(_Info[i]._count,arr,10);//转化_count为十进制字符放入文件
                fputc(' ',pfw);
                fputs(arr,pfw);
//              fputc(':',pfw);
//              fputs(_Info[i]._code.c_str(),pfw);
                fputc('\n',pfw);
            }
        }

        unsigned char data = 0;
        int pos=7;
        ch=fgetc(pfr);

        while (ch!=EOF)//写入编码信息
        {
            const char*ptr=_Info[ch]._code.c_str();
            while (*ptr)
            {
                if (pos>=0)
                {
                    data=data | ((*ptr-'0')<<pos);
                    pos--;
                    ptr++;
                }
                else if(pos<0)
                {
                    fputc(data,pfw);//读满8位写入
                    pos=7;
                    data=0;
                }
            }
            ch=fgetc(pfr);//读取下一个字符
        }
        //最后一个字符不管有没有写满都要放进去
        fputc(data,pfw);
        //fputc(EOF,pfw);//写入文件结束符
        fputs("\r\n",pfw);

        fclose(pfr);
        fclose(pfw);
        cout<<"压缩完成"<<endl;
    }

    //文件解压
    void Extract(const char* filename)
    {
        assert(filename);
        FILE* pfr=fopen(filename,"rb");
        File_Info _Info[256];

        string code;
        string write(filename);
        _GetLine(write.c_str(),_Info,code);//重写树节点

        int index=write.rfind(".",write.size());
        if (write.substr(index+1,write.size())!="Compress")//检查文件后缀是否正确
        {
            return ;
        }
        write=write.substr(0, index);//除去文件后缀

        write=write+".UnCompress";
        FILE* pfw=fopen(write.c_str(),"wb");//添加新的后缀并打开解压文件

        Tree<File_Info> newtree(_Info,256,File_Info());
        HuffmanNode<File_Info> *root=newtree.Root();
        HuffmanNode<File_Info> *cur=root;
        if (root==NULL)
        {
            return ;
        }

        long long charcount=root->_weight._count;//字符总数,控制循环条件
        int pos=8;
        const char* ch=code.c_str();//压缩后的数据

        while (charcount)//解压
        {
            --pos;
            unsigned char v=1;
            int tmp=((*ch)&(v<<pos));
            if (tmp)
            {
                cur=cur->_Rchild;
            }
            else if (tmp==0)
            {
                cur=cur->_Lchild;
            }
            if (cur->_Lchild==NULL && cur->_Rchild==NULL)
            {
                fputc(cur->_weight._ch,pfw);//找到编码对应叶节点的字符信息_ch
                cur=root;
                if (--charcount==0)//总字符数
                {
                    break;
                }               
            }
            if (pos==0)
            {
                pos=8;
                ch++;
            }
        }
        fclose(pfr);
        fclose(pfw);
        cout<<"解压结束"<<endl;
    }
protected:
    void _GetTreeNode(Node* root, string code,int& line)//获取字符编码和行数
    {
        if (root==NULL)
            return;
        _GetTreeNode(root->_Lchild, code+'0',line);
        _GetTreeNode(root->_Rchild, code+'1',line);

        if (root->_Lchild==NULL && root->_Rchild==NULL)
        {
            _Info[root->_weight._ch]._code=code.c_str();
            if (root->_weight._count!=0)
            {
                line++;//行数为叶节点个数
            }
        }
    }
    void _GetLine(string filename,File_Info* _Info,string& code)
    {
        FILE* pfr=fopen(filename.c_str(),"r");
        unsigned char ch=fgetc(pfr);
        while(ch!='\n')//最后一位'\n'已被读取
        {
            ch=fgetc(pfr);
        }
        string Line;//存储行数

        for (int i=0; i<4;i++)//行数最位4多
        {
            ch=fgetc(pfr);//读行数
            if (ch=='\n')
                break;
            Line.push_back(ch);
        }

        int line=atoi(Line.c_str());        //将行数转化为整型
        while (line--)
        {
            string buf;     //读取每一行信息
            char ch=fgetc(pfr);
            while (ch!='\n')
            {
                buf.push_back(ch);
                ch=fgetc(pfr);
            }
            //建立叶节点信息
            int index=buf.rfind(" ",buf.size());    //找到space对应下标
            unsigned char c=buf[0];                 //第一个字符为_ch
            _Info[c]._ch=c;
            string str=buf.substr(index,buf.size());//space之后为出现次数_count
            int count=atoi(str.c_str());
            _Info[c]._count=count;
        }

        unsigned char h=fgetc(pfr);//读取压缩后的数据本体

        while (h!=EOF)
        {
            code.push_back(h);
            h=fgetc(pfr);
        }

        //char *rdbuf=new char[1024];
        //int rdsize=fread(rdbuf,1,1024,pfr);
        //while (rdsize!=0)
        //{
        //  break;
        //  code+=fread(rdbuf,1,1024,pfr);
        //}

        //const unsigned char *cur=(const unsigned char *)code.c_str();
        fclose(pfr);
    }

private:
    File_Info _Info[256];
};
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值