文件压缩源码

文件压缩:VS2013采用C/C++

/******************** HuffmanTree.h *********************/
#pragma once
#include <iostream>
using namespace std;
#include <vector>
#include <queue>

template <class W>
struct HuffmanTreeNode//构造哈夫曼树的节点  三叉链 
{
    W _w;//数据值
    HuffmanTreeNode<W>* _left;//左孩子
    HuffmanTreeNode<W>* _right;//右孩子
    HuffmanTreeNode<W>* _parent;//父亲

    HuffmanTreeNode(const W& w)
        :_left(NULL)
        , _right(NULL)
        , _w(w) //权值  次数
        , _parent(NULL)
    {}

};



template <class W>
class HuffmanTree //构造哈夫曼树
{
    typedef HuffmanTreeNode<W> Node;

    struct PtrNodeCompare  // 迭代器 比较方法-->权值比较
    {
        bool operator()(Node* left, Node* right)//重载()
        {
            return left->_w > right->_w;
        }
    };

public:
    HuffmanTree()
        :_root(NULL)
    {}
    HuffmanTree(W* w, size_t n,const W& invalid)//invalid用来阻止没必要构建的字符(即为空)
    {
        //优先级队列构造小堆,每次拿出最小元素去构建HuffmanTree节点
        priority_queue <Node*, vector<Node*>, PtrNodeCompare> minheap; 
        for (size_t i = 0; i < n; ++i)
        {
            if (w[i] != invalid)
            //两个模板类型(不确定类型)比较,采用W._count比较,类中已经重载过 !=
            {
                minheap.push(new Node(w[i]));
            }
        }//小堆构建完成
        if (minheap.size() == 0)
        {
            cout << "无效文件" << endl;
            exit(0);

        }else if (minheap.size() == 1)
        {
            Node* right = minheap.top();
            _root = new Node(right->_w);
            right->_parent = _root;
            _root->_right = right;
        }
        else
        {
            while (minheap.size() > 1)//每次拿到最小的元素后调整堆 
            {
                Node* left = minheap.top();
                minheap.pop();
                Node* right = minheap.top();//huffman模型 right比left大
                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();
        }
    }//Huffman树构造完成后,所有的元素均位于叶子节点

    Node* GetRoot()
    {
        return _root;
    }

    ~HuffmanTree() //递归销毁不能直接在类内直接操作(类内销毁,死循环) 类外定义递归函数
    {
        Destory(_root);
        _root = NULL;
    }

    void Destory(Node* root) // 递归销毁 时间复杂度O(N)
    {
        if (root == NULL)
        {
            return;
        }
        Destory(root->_left);
        Destory(root->_right);

        delete root;
    }




private://压缩文件用不到此操作  防止类外被调用 定义为私有  
    HuffmanTree(const HuffmanTree<W>& t); // 防止拷贝构造
    HuffmanTree<W>& operator= (const HuffmanTree<W>& t); //防拷贝 只声明 不定义 防止系统合成


protected:
    Node* _root; //哈夫曼树节点
};

void TestHuffmanTree()//测试是否构成Huffman树 
{
    int a[] = { 1, 2, 3, 4, 5, 6, 7, 8, 9 };
    HuffmanTree<int> t (a, sizeof(a) / sizeof(a[0]),0);
} 


/*********************** FileCompress.h ***********************/

#pragma once
//#include <cassert>
#include <string>
#include <stdio.h>
#include <windows.h>
#include <fstream>
#include <algorithm>
#include <assert.h>

#pragma warning (disable: 4996)

using namespace std;
#include "HuffmanTree.h"

typedef long long LongType;


//compress 压缩

struct CharInfo
{
    char _ASCII; //字符
    LongType _count; //次数
    string _code; //权值   

    CharInfo operator+(const CharInfo& info)
    {
        CharInfo ret;
        ret._count = _count + info._count;
        return ret;
    }
    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;
    }
    bool operator==(const CharInfo& info)
    {
        return _count == info._count;
    }
};



class FileCompress
{
    typedef HuffmanTreeNode<CharInfo> Node;

    struct TmpInfo
    {
        char _ch; //字符
        LongType _num;//数量
    };

public:
    FileCompress()
    {
        for (size_t i = 0; i < 256; ++i)
        {
            _infos[i]._ASCII = i;
            _infos[i]._count = 0;
        }
    }

    void Compress(const char* file)//压缩
    {

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

        FILE* f_out = fopen(file, "r");
        assert(f_out);

        //fgetc 从文件指针stream指向的文件中读取一个字符
        //读取一个字节后,光标位置后移一个字节
        char character = fgetc(f_out);//ASCII码
        while (character != EOF)
        {   
            _infos[(unsigned char)character]._count++; //ASCII码对应的编号用作下标
            character = fgetc(f_out);
        }
        //2.构建huffman tree

        CharInfo invalid;
        invalid._count = 0;
        //如果invalid._count = 0,说明次数是0,没有出现
        HuffmanTree<CharInfo> tree(_infos, 256, invalid);

        //3.生成code

        //直接放到_infos
        GenerateHuffmanCode(tree.GetRoot());

        /***************************************************************************/
        //测试  是否生成Huffman编码 小堆创建失败(变大堆) 会造成越压越大
        /***************************************************************************/




        //4.压缩
        string compressFile = file;  //file->传进来的文件(名)
        compressFile += ".huffman"; //string底层支持+=

        //
        FILE* f_in = fopen(compressFile.c_str(), "wb");//wb 二进制写
        //compressFile.c_str() 相当于 "file".huffman

        for (size_t i = 0; i < 256; ++i)
        {
            if (_infos[i]._count > 0)//tmpinfo只保存原文件含有的字符
            {
                TmpInfo tmpinfo; 
                tmpinfo._ch = _infos[i]._ASCII; //字符
                tmpinfo._num = _infos[i]._count;//次数

                //fwrite 二进制输出
                //fwrite 参数1 数据块  2 大小 3 写多少次块 4 去向(文件)
                fwrite(&tmpinfo, sizeof(TmpInfo), 1, f_in);//存备份 例如a4b4c3d2e2f1g1 
            }
        }

        /************************************************************/
        //下面 是 结尾标志
        /***********************************************************/

        TmpInfo tmpinfo;
        tmpinfo._num = -1; //_num初始化为-1  因为不可能有一个字符出现了-1次
        fwrite(&tmpinfo, sizeof(TmpInfo), 1, f_in);

        //文件已经读到尾部啦  重置文件指针
        rewind(f_out);//rewind函数作用等同于 (void)fseek(stream,0L,SEEK_SET)
        //fseek  fseek函数是设置文件指针stream的位置
        //参数1 文件指针   2 偏移位置(负数 反方向偏移) 
        //3 从哪儿开始偏移(SEEK_SET文件开头 SEEK_CUR当前位置  SEEK_END 文件结尾)

        //ifs._Seekbeg;
        character = fgetc(f_out); // 操作读取原始文件
        //ifs.seekg(0);

        size_t CeffectiveBit = 0;
        char value = 0;
        int pos = 0;

        while (character != EOF)
        {
            // 采用引用效率高,不生成临时变量
            string& code = _infos[(unsigned char)character]._code;//code是每个ch的huffman编码
            for (size_t i = 0; i < code.size(); ++i)
            {
                if (code[i] == '0')//Huffman编码的第i位为0的写入方法
                {
                    value &= ~(1 << pos);
                }
                else if (code[i] == '1')
                {
                    value |= (1 << pos);
                }
                else
                {
                    assert(false);
                }
                ++pos;
                if (pos == 8)
                {
                    fputc(value, f_in);//8位Huffman编码生成一个value 存入文件 因此实现压缩
                    value = 0;
                    pos = 0;
                }
                CeffectiveBit++;

            }
            character = fgetc(f_out); //接着读数据
        }
        cout << "文件压缩有效位数 = " << CeffectiveBit << endl;

        //不满8 剩余位以0补齐
        if (pos > 0)
        {
            fputc(value, f_in);
        }
        fclose(f_out);
        fclose(f_in);
    }
    /******************************************************************/
    //完成后 huffman里边应该存储的信息为a4b4c3d2e2f1g1?-1 而后再跟编码
    //
    //存储举例:
    //(低位)0000 1010 | 1011 1111 | 110 (补位5个0)
    //  计算机存值   0   5 (十进制 80)| D  F() |3  0 ()  即 50 FD 03
    /******************************************************************/


    void GenerateHuffmanCode(Node* root)
    {
        if (root == NULL)
            return;
        //递归往上寻找  叶子开始  叶子位于父节点的左+0
        if (root->_left == NULL && root->_right == NULL)  //叶子节点 出口
        {
            string code; //存放huffman编码
            Node* cur = root;
            Node* parent = cur->_parent;
            while (parent) // 左'0'  右'1'
            {
                if (cur == parent->_left)
                {
                    code.push_back('0');
                }
                else
                {
                    code.push_back('1');
                }
                //处理完当前节点,向上返完
                cur = parent;
                parent = cur->_parent;
            }
            //因为从下往上数,编码是反的,需要翻转
            reverse(code.begin(), code.end());
            //用unsigned char 是为了写入汉字
            _infos[(unsigned char)root->_w._ASCII]._code = code;
            //root->._w._ASCII  root指向的节点是Node类型是CharInfo,_ASCII(字符值)是CharInfo里边定义的
            return;
        }
        GenerateHuffmanCode(root->_left);
        GenerateHuffmanCode(root->_right);
    }


/**************************   解压缩   *******************************/


    void Uncompress(const char* file)
    {
        size_t n = 0;
        assert(file);

        string uncompressFile = file;

        size_t pos = uncompressFile.rfind('.');    //倒序查找'.' 返回'.'的位置(从前数)
        //第一个参数是内容  第二个参数是从位置 可缺省 默认最后
        assert(pos != string::npos);  //没有找到返回npos(最大的无符号整形)  避免二义性

        uncompressFile.erase(pos, uncompressFile.size() - pos);
        //第一个参数 从哪个位置开始删  二 大小 (删多少) 缺省 删到结尾


        uncompressFile += ".unhuffman";//测试所用  分辨与源文件的区别

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

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

        //读取压缩文件中的数据,解压缩
        TmpInfo info;
        fread(&info, sizeof(TmpInfo), 1, fout);
        while (info._num != -1)
        {
            _infos[(unsigned char)info._ch]._ASCII = info._ch;
            _infos[(unsigned char)info._ch]._count = info._num;
            fread(&info, sizeof(TmpInfo), 1, fout);
            //每读取一定的数据块 文件指针后移
        }

        //1.重建huffman tree 
        CharInfo invalid;
        invalid._count = 0;
        HuffmanTree<CharInfo> tree(_infos, 256, invalid);
        Node* root = tree.GetRoot();
        Node* cur = root;
        LongType fileSize = root->_w._count;//跟等于所有叶子之和
        cout << fileSize << endl;
        //2.解压缩
        char value = fgetc(fout);
        while (1)
        {
            for (size_t pos = 0; pos < 8; ++pos)
            {
                // 判断value是'1' 还是'0'
                if (value & (1 << pos)) //1与任何数等于任何数
                    cur = cur->_right;
                else
                    cur = cur->_left;

                ++n;


                if (NULL == cur->_left && NULL == cur->_right)
                {
                    fputc(cur->_w._ASCII, fin); //还原完一个回到根节点,继续
                    cur = root;

                    if (--fileSize == 0)//8位没用完 文件已经读完
                    {
                        break;
                    }
                }

            }
            if (fileSize == 0)//
            {
                break;
            }
            value = fgetc(fout);
        }
        cout << n << endl;
        fclose(fout);
        fclose(fin);
    }




protected:

    CharInfo _infos[256];

};//类结束

void TestFilecompress()
{
    FileCompress fc;
    size_t start = GetTickCount();
    fc.Compress("aa.txt");
    size_t end = GetTickCount();
    cout << "UseTime:" << end - start << endl;


    fc.Uncompress("aa.txt.huffman");
}

/****************** test.cpp *************/


#include "FileCompress.h"


int main()
{
    TestFilecompress();

    //TestHuffmanTree();


    system("pause");
    return 0;
}

猜你喜欢

转载自blog.csdn.net/Romantic_C/article/details/81630263