C++实现的Huffman压缩解压缩程序及相应程序框架的设计

时间:2024-02-21 07:57:39

前面用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不带缓冲,同时使得代码清晰。

image

 

还没有尝试算法的优化,比如当前解码的时候一个unsigend int 转换成相应的bit,采用最笨的动态计算。

可以考虑查表法优化。

不过当前的速度可python实现的比已经快多了。

在我的1G内存虚拟机上跑,

压缩,然后解压缩一个24M的文本,总共用时大概7-8秒。用GCC编译器优化选项-O2能够达到3-4秒完成。

 

程序的框架上,首先考虑定义两个类

Compressor,Decompressor

image

image

 

提供压缩,解压缩的流程框架。对于用户而言

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所拥有的变量。

image

     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来帮助实现编码。
image 
 
为了Huffman压缩过程和解压缩过程,我利用模版类的特化设计了两个不同的HuffTree分别用于压缩过程,而解压缩过程。它们继承一个HuffTreeBase,提供共有的数据root_和操作如delete_tree().
这里感觉最不爽的还是GCC采用的不认模版父类的名称必须显示指明或者用this->,据说VC8就不需要显示指明或者加this,那多好啊,感觉真是没必要,麻烦的要命。尤其是刚开始你觉得只要一个
类,后来你要写一个类似但是不同的类,觉得可以提出一个基类的时候,你发现你把共同的代码提出去了,剩下的代码原来可以用的现在却要加很多this,麻烦死了啊还不如不复用,直接复制快呢。
image 
 
image