C++实现的Huffman压缩解压缩程序及相应程序框架的设计
前面用python实现了基于256个字符huffman及范式huffman压缩解压缩程序。Python确实适合快速实现算法,包括程序的框架设计的实现。
但是无奈虽然尝试优化但是速度仍然不尽如意,包括无法实现inline,以及动态语言的特性决定这种强调速度,处理大数据量的应用程序显然不适合用python实现。
正好看了关于基于模版的算法库设计的一些皮毛,以及以前看CGAL库的学到一些方法。这里尝试用C++写一个压缩解压缩的框架程序,当前实现的框架还很幼稚,
是考虑到能够实现Huffman,范式huffman的基于256个字符,以及基于word单词的压缩与解压缩。也就是能够实现
1. 基于字符编码的huffman压缩,解压缩
2.基于字符编码的范式huffman压缩,解压缩
3.基于单词编码的huffman压缩,解压缩
4.基于单词编码的范式hufman压缩,解压缩
这些都是基于频率的(字符频率,单词频率)的压缩算法。
TODO
考虑系统能方便的加入其它算法
如LZ77(不是基于频率的压缩)等。。。。
OK,目前仅仅初步搭起了框架,实现了上面提到的1,基于字符编码的huffman压缩,解压缩。
当前程序在http://golden-huffman.googlecode.com/svn/trunk/glzip_c++/
同时框架尽可能考虑到后序加入不同算法的方便性,不同算法的共同与不同之处,层次关系,相同部分的复用,避免重复代码。程序里面写了一个Buffer类,用于提供基于缓冲的的读写
byte操作,以减少fread,fwrite的调用次数。实践证明能够提高效率,相比每次fread,fwrite 1个byte不带缓冲,同时使得代码清晰。
还没有尝试算法的优化,比如当前解码的时候一个unsigend int 转换成相应的bit,采用最笨的动态计算。
可以考虑查表法优化。
不过当前的速度可python实现的比已经快多了。
在我的1G内存虚拟机上跑,
压缩,然后解压缩一个24M的文本,总共用时大概7-8秒。用GCC编译器优化选项-O2能够达到3-4秒完成。
程序的框架上,首先考虑定义两个类
Compressor,Decompressor
提供压缩,解压缩的流程框架。对于用户而言
Compressor<> compressor(infile_name, outfile_name);
compressor.compress();
即完成压缩工作。解压缩类似。
template< template<typename> class _Encoder = HuffEncoder, typename _KeyType = unsigned char > class Compressor { public: Compressor(const std::string& infile_name, std::string& outfile_name) : encoder_(infile_name, outfile_name) {} /**The overall process of compressing,compressing framework*/ void compress() { encoder_.caculate_frequency(); encoder_.gen_encode(); //-------------------------------write the compressed file encoder_.write_encode_info(); encoder_.encode_file(); } private: _Encoder<_KeyType> encoder_; //using enocder_ right now can be HuffEncoder or CanonicalEncoder }; template< template<typename> class _Decoder = HuffDecoder, typename _KeyType = unsigned char > class Decompressor { public: Decompressor(const std::string& infile_name, std::string& outfile_name) : decoder_(infile_name, outfile_name) {} /**The overall process of decompressing, decompressing framework*/ void decompress() { //-----------------------------read header-------- decoder_.get_encode_info(); //-----------------------------read file content--- decoder_.decode_file(); } private: _Decoder<_KeyType> decoder_; };
采用复合的设计方法,Compressor类使用encoder_完成压缩工作。Decompressor类使用decoder_完成解压缩的工作。
模版参数上,template<typename> class _Encoder = HuffEncoder, 标明可以采用HuffEncoder完成基于传统huffman的压缩,
同时我们可以定义CanonicalEncoder类,令template<typename> class _Encoder = CanonicalEncoder就可以方便的使得
Compressor类使用基于范式huffman的encoder_完成压缩工作。
而 typename _KeyType = unsigned char标明默认Compressor是采用基于字符(256个字符)作为key的,进行编码解码。
那么后面我们加入基于单词的编码方法,就可以采用使得typename _KeyType = std::string即可,或者也可以用char*这里暂时都按照单词用string存储考虑。
压缩的过程就是
计算字符(或单词)频率
进行编码,利用构造的huffman tree
写编码信息到输出文件
编码输入文件输出到输出文件
在这个过程中我们需要保存
1.得到频率hash表 字符(或单词) –> 频率
2.得到的编码hash表 字符(或单词) –> 编码
另外我们需要处理输入输出,需要FILE* infile_,FILE* outfile_所有这些我们作为encoder所拥有的变量。
HuffEncoder is a Encoder,继承Encoder,同样的以后加入的CanonicalEnocder is also a Encoder也会继承Encoder.
Encoder所提供的非虚函数接口,caculate_frequency()和encode_file()等,标明这些由Encoder类提供实现,也即对于
传统Huffman还是范式Huffman来说,这些操作的实现是相同的,复用基类的实现。
但是其它的虚函数则要他们提供各自不同的实现。
另外当前的处理,对于基于字符编码的情况,_KeyType = unsigned char,
使用的所谓哈希表并不是STL里面的哈希表,而就是一个数组,例如 frequency_map_对于基于字符编码的情况,它就是 long long (&)[256]类型的数组。
这样利用了unsigned char 不超过256个byte,并且转int值之后正好当成一个hash函数,用STL的hash就小题大作了,速度也慢多了。
但是考虑兼容以后的string,基于单词编码的情况就得用STL hash了,或者你自己写hash。
所以这里采用了traits手法,根据_KeyType的不同,选择不同类型的 Hash容器类型,并且在函数中也会根据不同的情况,分派到不同的执行函数char –> char_tag
string –> string_tag。(当然以后也可以考虑给long long (&)[256]类型的数组这种简单的hash,加上与STL hash相同的接口,从而是代码更加同一,避免分配函数,不过目前感觉那样太费时间了。
下面是根据不同的_KeyType,给出不同的类型设置。
//基于字符情况的encoder<unsigned char>.caculate_frequencey(),以后实现的基于单词情况的则会调用encoder<std::string>.do_caculate_frequencey(string_tag)
00034 typedef typename TypeTraits<_KeyType>::FrequencyHashMap FrequencyHashMap; 00035 typedef typename TypeTraits<_KeyType>::EncodeHashMap EncodeHashMap; 00036 typedef typename TypeTraits<_KeyType>::type_catergory type_catergory; 00037 public: 00047 void caculate_frequency() { 00048 do_caculate_frequency(type_catergory()); 00049 } 00050
00080 void do_caculate_frequency(char_tag) { 00081 Buffer reader(infile_); 00082 unsigned char key; 00083 while(reader.read_byte(key)) 00084 frequency_map_[key] += 1; 00085 //std::cout << "Finished caculating frequency\n"; 00086 }
//--------------------------------------------------------------------------- 00021 struct normal_tag {}; 00022 struct char_tag: public normal_tag {}; 00023 struct string_tag: public normal_tag {}; 00024 00025 struct encode_hufftree {}; 00026 struct decode_hufftree {}; 00027 //---------------------------------------------------------------------------- 00028 00029 //---TypeTraits,from here we can find the HashMap type 00030 template <typename _KeyType> 00031 class TypeTraits { 00032 public: 00033 typedef normal_tag type_catergory; 00034 typedef std::tr1::unordered_map<_KeyType, size_t> HashMap; 00035 }; 00036 //---special TypeTraits for unsigned char 00037 template<> 00038 class TypeTraits<unsigned char> { 00039 public: 00040 typedef char_tag type_catergory; 00041 typedef long long count[256]; 00042 typedef count FrequencyHashMap; 00043 typedef std::vector<std::string> EncodeHashMap; 00044 }; 00045 //---special TypeTraits for std::string 00046 template<> 00047 class TypeTraits<std::string> { 00048 public: 00049 typedef string_tag type_catergory; 00050 typedef std::tr1::unordered_map<std::string, size_t> FrequencyHashMap; 00051 typedef std::tr1::unordered_map<std::string, std::string> EncodeHashMap;
HuffEncoder,利用复合设计模式,采用HuffTree来帮助实现编码。
为了Huffman压缩过程和解压缩过程,我利用模版类的特化设计了两个不同的HuffTree分别用于压缩过程,而解压缩过程。它们继承一个HuffTreeBase,提供共有的数据root_和操作如delete_tree().
这里感觉最不爽的还是GCC采用的不认模版父类的名称必须显示指明或者用this->,据说VC8就不需要显示指明或者加this,那多好啊,感觉真是没必要,麻烦的要命。尤其是刚开始你觉得只要一个
类,后来你要写一个类似但是不同的类,觉得可以提出一个基类的时候,你发现你把共同的代码提出去了,剩下的代码原来可以用的现在却要加很多this,麻烦死了啊还不如不复用,直接复制快呢。