利用huffman编码的思想对文件进行压缩,主要原理是通过huffman编码来重新表示字符,使得出现频率高的字符编码短,出现少的字符编码长。整体下来的话,所需的总的bit位是减少的。但是要注意当大部分字符出现的频率都差不多时,huffman压缩的压缩效率会很低。
一、利用huffman树对文件进行压缩主要分为以下两部分:
压缩:
1、统计字符出现的次数
因为文件的底层都是有256个字符存储的,所以我们使用一个256的数组来统计字符出现的次数。可以使用结构体,将次数,字符,huffman编码对应起来。要注意,不管是文件、图片、音频、视频他们的底层都是以字符形式存储的。我们读的时候不管是以文本形式还是以二进制形式都是按字符读取的。
2、构建huffman树
采用huffman算法,将一组集合中权值最小的两个树拿出来构建成一棵树,将他们的权值之和作为父节点插入到这个集合中,不断重复这个过程,直到集合中只有一颗树,这棵树就是huffman树,由于每次都要寻找最小的两个数,我们可以使用最小堆来寻找最小的两个数。在这里,我们可以用字符出现的次数作为权值。
3、得到huffman编码
从根节点出发,向左走位0,向右走为1,找到一个叶子结点,就将他对应字符的huffman编码存到数组里面。
4、将huffman编码写入文件
使用哈希表,可以在O(1)时间内找到字符所对应的编码,将每8位编码构成一个字符写入文件中。如果最后的几位编码不够8位,那么我们就要在后面几位中补0.
5、编写配置文件
由于在解压时往往是没有源文件的,而我们要解压的话必须要知道这棵huffman树,所以在我们压缩的时候编写一个配置文件来存储huffman树的信息。在配置文件里面将:字符+出现的次数存在一行。在这里要使用itoa这个函数将次数转换成一个字符串存储。
解压缩:
1、读取配置文件
由于解压和压缩往往不是一起的,所以在解压之前我们要先还原huffman这棵树,读取配置文件时我们是一行一行进行存储的,所以读的时候我么最好一行一行的读。但是这里要注意两个问题。第一:因为string底层是char*,而每一行的第一个字符是0—255,所以这一块我们要进行处理,可以单独读取。第二:将字符串转换为数字,使用atoi函数。
2、构建huffman树
构建huffman树和压缩时是一样的。
3、解压缩
首先在压缩文件里面读取一个字符,然后解析这个字符的每一位,采用贪心法,只要遇到一个叶子结点,就代表我们还原了一个字符,这时候我们就要将这个字符写到解压缩文件里面。但是在这里要注意,有可能最后的几位编码是我们补上去的,所以在这里我们要以源文件中字符出现的次数来控制解压缩,根据huffman的性质可知,根节点的权重就是字符出现的次数。
二、在项目中遇到的问题
1、解压缩时解压不完全
由于使用文本形式读取压缩文件,有可能提前遇到文件结束标志,所以要改为二进制形式读写。二进制形式是读取二进制编码。
如果以文本形式读取的话,回车会被当成一个字符'\n',而二进制形式则会认为它是两个字符即'\r'回车、'\n'换行;如果在文本形式中遇到0x1B的话,文本形式会认为这是文本结束符,而二进制模型则不会对它产生处理。
2、得到huffman编码
在压缩时我们要求解huffman编码,在这里可以使用stl中的string和reverse求解。也可以使用后序递归直接求解。
3、二次压缩效率很低
因为压缩一次之后,这时因为配置文件中字符出现的次数相差都不是很大,体现不出来huffman的特性。所以这时候再压缩的话效率会非常低下。
4、压缩汉字时出现问题
因为汉字是由多个字符表示,这些字符的范围是0—255,所以在结构体中要用unsigned char表示。
三、项目改进
1、解压缩的时候有可能要解压缩文件的不是用huffman进行压缩的文件。所以再解压文件之前先判断是不是用huffman进行压缩的。
2、不使用配置文件时如何解压
可以将huffman树的信息写入到压缩文件中。
四、项目扩展
1、实现对文件夹的压缩
对文件夹进行压缩实际上还是对文件夹中的内容进行压缩,所以得到一个文件夹之后我们就一直向子文件夹中找,直到找到文件后进行压缩就可以了。
2、解压缩的时候要将文件夹还原出来。
//heap
- #pragma once
- #include<cassert>
- #include<vector>
- using namespace std;
-
- template<typename T>
- struct Less
- {
- bool operator()(const T& l, const T& r)
- {
- return l < r;
- }
- };
-
- template<typename T>
- struct Great
- {
- bool operator()(const T& l, const T& r)
- {
- return l>r;
- }
- };
-
-
-
-
- template<typename T, class Compare = Less<T>>
- class Heap
- {
- public:
- Heap()
- {}
- Heap(T *a, int size)
- {
- _a.reserve(size);
- for (int i = 0; i < size; i++)
- {
- _a.push_back(a[i]);
- }
-
-
- for (int i = (size - 2) / 2; i >= 0; --i)
- {
- AdjustDown(i, size);
- }
- }
-
- void Push(const T& x)
- {
-
- _a.push_back(x);
- AdjustUp(_a.size() - 1);
- }
-
- void Pop()
- {
-
- assert(!_a.empty());
- swap(_a[0], _a[_a.size() - 1]);
- _a.pop_back();
- if (_a.size()>1)
- {
- AdjustDown(0, _a.size());
- }
- }
-
- size_t Size()
- {
- return _a.size();
- }
-
- bool Empty()
- {
- return _a.empty();
- }
-
- const T& Top()
- {
- assert(!_a.empty());
- return _a[0];
- }
- protected:
- void AdjustDown(int root, int size)
- {
- assert(!_a.empty());
- int parent = root;
- int child = parent * 2 + 1;
- while (child<size)
- {
-
- if ((child + 1) < size
- &&Compare()(_a[child + 1], _a[child]))
- child++;
- if (Compare()(_a[child], _a[parent]))
- {
- swap(_a[parent], _a[child]);
- parent = child;
- child = parent * 2 + 1;
- }
- else
- {
- break;
- }
- }
- }
-
- void AdjustUp(int child)
- {
- assert(!_a.empty());
- while (child>0)
- {
- int parent = (child - 1) / 2;
- if (Compare()(_a[child], _a[parent]))
- {
- swap(_a[parent], _a[child]);
- child = parent;
- }
- else
- {
- break;
- }
- }
- }
- private:
- vector<T> _a;
- };
//huffman
//file compress
- #pragma once
- #include<string>
- #include<cstdio>
- #include<cassert>
- #include <direct.h>
- #include <stdlib.h>
- #include<cstdlib>
- #include<algorithm>
- #include<vector>
- #include<io.h>
- #include<cstring>
- using namespace std;
- #include"HuffmanTree.h"
-
-
-
- typedef long long LongType;
-
- struct CharInfo
- {
- unsigned char _data;
- LongType _count;
- string _Code;
- CharInfo(LongType count=0)
- :_count(count)
- {}
-
- CharInfo operator+(CharInfo& ch)
- {
- return CharInfo(_count+ch._count);
- }
-
- bool operator<(CharInfo &ch)
- {
- return _count < ch._count;
- }
- };
-
-
- class HuffFileCompress
- {
- public:
- HuffFileCompress()
- {
- for (int i = 0; i < 256; i++)
- {
- _Infos[i]._data = i;
- }
- }
-
- const string CompressFile(string filename)
- {
- vector<string> file;
- string path = filename.c_str();
- getFiles(path, file);
-
- if (file.empty())
- {
- return _CompressFile(filename);
- }
- else
- {
-
- string newpath=path;
- newpath += ".huf";
- _mkdir(newpath.c_str());
-
- for (int i = 0; i < (int)file.size(); i++)
- {
- _CompressFile(file[i],newpath);
- }
- return newpath;
- }
- }
-
- const string UnCompressFile(string filename)
- {
- vector<string> file;
- string path = filename.c_str();
- getFiles(path, file);
-
- if (file.empty())
- {
- return _UnCompressFile(filename);
- }
- else
- {
-
- string newpath =filename;
- for (int i = (int)filename.size() - 1; i >= 0; i--)
- {
- if (filename[i] == '.')
- {
- newpath.resize(i);
- break;
- }
- }
- newpath += ".uhuf";
- _mkdir(newpath.c_str());
-
- for (int i = 0; i < (int)file.size(); i++)
- {
- _UnCompressFile(file[i], newpath);
- }
- return newpath;
- }
- }
- protected:
-
- const string _CompressFile(const string filename,const string path=string())
- {
- CreatHuffmanTree(filename.c_str());
-
- string CompressFileName = filename;
- CompressFileName += ".huf";
-
- FILE *fInput = fopen(filename.c_str(),"rb");
- assert(fInput);
- FILE *fOut=NULL;
-
- string configFileName = filename;
- configFileName += ".config";
- FILE *configOut=NULL;
-
- if (path.empty())
- {
- fOut = fopen(CompressFileName.c_str(), "wb");
- if (fOut == NULL)
- {
- fclose(fOut);
- exit(EXIT_FAILURE);
- }
-
- configOut = fopen(configFileName.c_str(), "wb");
- assert(configOut);
- }
- else
- {
-
- string FileName;
- int i = filename.size()- 1;
- for (; i >=0;i--)
- {
- if (filename[i] == '\\')
- break;
- FileName += filename[i];
- }
- reverse(FileName.begin(), FileName.end());
-
- string newpath=path;
- newpath += '\\';
- newpath += FileName;
- newpath += ".huf";
- fOut = fopen(newpath.c_str(), "wb");
- if (fOut == NULL)
- {
- fclose(fOut);
- exit(EXIT_FAILURE);
- }
-
-
- newpath = path;
- newpath += '\\';
- newpath += FileName;
- newpath += ".config";
- configOut = fopen(newpath.c_str(), "wb");
- assert(configOut);
- }
-
- string line;
- for (int i = 0; i < 256; i++)
- {
- if (_Infos[i]._count!=0)
- {
- int c=_Infos[i]._data;
- fputc(c,configOut);
- line += ",";
- char buffer[25] = { 0 };
- line+=_itoa((int)_Infos[i]._count, buffer, 10);
- line += '\n';
- fwrite(line.c_str(),1,line.size(),configOut);
- line.clear();
- }
- }
- fclose(configOut);
-
- int c=0;
- int pos =7;
- int ch = fgetc(fInput);
- while (ch!=EOF)
- {
- string &code=_Infos[ch]._Code;
- for (size_t i = 0; i < code.size(); i++)
- {
- c |= ((code[i]-'0') << pos);
- pos--;
- if (pos<0)
- {
- fputc(c,fOut);
- pos = 7;
- c = 0;
- }
- }
- ch = fgetc(fInput);
- }
-
- if (pos<7)
- {
- fputc(c, fOut);
- }
- fclose(fOut);
- fclose(fInput);
- memset(_Infos, 0, 256 * sizeof(CharInfo));
- return CompressFileName;
- }
-
-
-
- const string _UnCompressFile(string filename,const string path=string())
- {
- assert(!filename.empty());
-
-
- string name;
- name= filename;
- int i = 0;
- string posfix;
- for (i =(int)filename.size()-1; i>=0; --i)
- {
- posfix.push_back(filename[i]);
- if (filename[i] == '.')
- break;
- }
- reverse(posfix.begin(),posfix.end());
-
- if (posfix!= ".huf")
- {
- return string();
- }
-
- name.resize(i);
-
- string UnCompressFileName = name;
- UnCompressFileName += ".uhuf";
-
- string configName = name;
- configName+=".config";
-
-
- FILE *fInput = fopen(filename.c_str(),"rb");
- assert(fInput);
-
- FILE *configInput=fopen(configName.c_str(), "rb");
- assert(configInput);
-
- FILE *fOut = NULL;
- if (path.empty())
- {
-
- fOut = fopen(UnCompressFileName.c_str(), "wb");
- if (fOut == NULL)
- {
- fclose(fInput);
- exit(EXIT_FAILURE);
- }
- }
- else
- {
- string FileName;
- for (int i = (int)name.size() - 1; i >= 0; i--)
- {
- if (name[i] == '\\')
- {
- break;
- }
- FileName += name[i];
- }
- reverse(FileName.begin(), FileName.end());
-
- string newpath = path;
- newpath += "\\";
- newpath += FileName;
- newpath += ".uhuf";
-
-
- fOut = fopen(newpath.c_str(), "wb");
- if (fOut == NULL)
- {
- fclose(fInput);
- exit(EXIT_FAILURE);
- }
- }
-
- string line;
- int c = 0;
- while ((c = fgetc(configInput))!=EOF)
- {
- GetLine(configInput, line);
- _Infos[c]._count = atoi((&line[1]));
- line.clear();
- }
- fclose(configInput);
-
- int ch = 0;
-
- CharInfo invalid;
- for (int i = 0; i < 256; i++)
- {
- _Infos[i]._data = i;
- }
- HuffmanTree<CharInfo> ht(_Infos, 256, invalid);
- LongType count = ht.GetRoot()->_data._count;
-
- int pos=7;
- c = 1;
- HuffmanNode<CharInfo> *root= ht.GetRoot();
- HuffmanNode<CharInfo> *cur = root;
- while (count > 0)
- {
- ch = fgetc(fInput);
-
- while (pos >= 0 && count > 0)
- {
-
- if (ch&(c << pos))
- {
- cur = cur->_right;
- }
- else
- {
- cur = cur->_left;
- }
- if (cur->_left == NULL&&cur->_right == NULL)
- {
-
-
-
- fputc(cur->_data._data, fOut);
- cur = root;
- count--;
- }
- pos--;
- }
- pos = 7;
- }
- fclose(fInput);
- fclose(fOut);
- memset(_Infos, 0, 256 * sizeof(CharInfo));
- return UnCompressFileName;
- }
-
- protected:
- void CreatHuffmanTree(const char * filename)
- {
- assert(filename != NULL);
- for (int i = 0; i < 256; i++)
- {
- _Infos[i]._data = i;
- }
-
- FILE *fInput = fopen(filename, "rb");
- assert(fInput);
- int ch = 0;
-
- while ((ch = fgetc(fInput)) != EOF)
- {
- _Infos[ch]._count++;
- }
-
-
- CharInfo invalid;
- HuffmanTree<CharInfo> ht(_Infos, 256, invalid);
-
-
- string str;
- GetHufCode(ht.GetRoot(), str);
- fclose(fInput);
- }
-
-
-
- void GetHufCode(HuffmanNode<CharInfo>* root, string str)
- {
- if (root == NULL)
- return;
- if (root->_left == NULL&&root->_right == NULL)
- {
- _Infos[root->_data._data]._Code = str;
- return;
- }
- GetHufCode(root->_left, str + '0');
- GetHufCode(root->_right, str + '1');
- }
-
- bool GetLine(FILE*& Input,string &line)
- {
- assert(Input);
- int ch = 0;
- ch = fgetc(Input);
- if (ch == EOF)
- return false;
- while (ch != EOF&&ch != '\n')
- {
- line += ch;
- ch = fgetc(Input);
- }
- return true;
- }
-
- void getFiles(string path, vector<string>& files)
- {
-
- long hFile = 0;
-
- struct _finddata_t fileinfo;
- string p;
- if ((hFile = _findfirst(p.assign(path).append("\\*").c_str(), &fileinfo)) != -1)
- {
- do
- {
-
-
- if ((fileinfo.attrib & _A_SUBDIR))
- {
- if (strcmp(fileinfo.name, ".") != 0 && strcmp(fileinfo.name, "..") != 0)
- getFiles(p.assign(path).append("\\").append(fileinfo.name), files);
- }
- else
- {
- files.push_back(p.assign(path).append("\\").append(fileinfo.name));
- }
- } while (_findnext(hFile, &fileinfo) == 0);
- _findclose(hFile);
- }
- }
- private:
- CharInfo _Infos[256];
- };