前面用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 fileencoder_.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 {};0002400025 struct encode_hufftree {};00026 struct decode_hufftree {};00027 //----------------------------------------------------------------------------0002800029 //---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 char00037 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::string00046 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,麻煩死了啊還不如不複用,直接複製快呢。