檔案壓縮專案原始碼
阿新 • • 發佈:2018-12-09
Heap.h
/堆類 #pragma once #include<iostream> using namespace std; #include<vector> #include<assert.h> //仿函式(函式物件)--建小堆 template<typename T> struct Less { bool operator()(const T& left, const T& right) { return left<right; } }; //仿函式(函式物件)--建大堆 template<typename T> struct Great { bool operator()(const T& left, const T& right) { return left>right; } }; template<typename T, typename Compare> class Heap { public: Heap()//預設建構函式(全預設建構函式) {} Heap(const T* arr, size_t sz)//建構函式 { assert(arr); //1.將陣列中的元素放入堆中 for (int i = 0; i<sz; ++i) { _heap.push_back(arr[i]); } //2.從堆中(sz-2)/2的位置(即最後一個非葉子節點位置)開始利用向下調整建堆 for (int i = (sz - 2) / 2; i >= 0; ++i) { AdjustDown(i); } } void Push(const T& x)//插入--插入到最後一個位置,然後沿著該節點到根結點的路徑向上調整 { _heap.push_back(x); AdjustUp(_heap.size() - 1); } void Pop()//刪除(堆頂元素)---將堆頂元素和堆底元素交換;刪除堆底元素;然後從堆頂向下調整 { if (_heap.empty()) { cout << "heap empty" << endl; } else { //先交換堆底和堆頂元素 std::swap(_heap[0], _heap[_heap.size() - 1]); _heap.pop_back(); //然後從堆頂開始向下調整 AdjustDown(0); } } const T& Top()//取最值(堆頂元素) { if (!_heap.empty()) { return _heap[0]; } else { cout << "heap empty!" << endl; } } size_t Size()//返回堆中元素個數 { return _heap.size(); } bool Empty()//判斷堆是否為空 { return _heap.empty(); } private: //向下調整 void AdjustDown(size_t index) { size_t leftchild = index * 2 + 1;//左孩子 Compare com;//大小堆 while (leftchild<_heap.size()) { //1.尋找左右孩子的最值下標 if (leftchild + 1<_heap.size() && com(_heap[leftchild + 1], _heap[leftchild])) { leftchild++;//記錄左右孩子的最值下標 } //2.比較並決定是否交換 //2.1 交換,繼續向下調整 if (com(_heap[leftchild], _heap[index])) { std::swap(_heap[leftchild], _heap[index]); //繼續向下調整 index = leftchild; leftchild = index * 2 + 1; } //2.2不交換,那麼不用繼續向下調整 else break; } } //向上調整 void AdjustUp(int index) { int father = (index - 2) / 2;//父節點 Compare com; while (father >= 0) { //交換,繼續向上調整 if (com(_heap[index], _heap[father])) { std::swap(_heap[index], _heap[father]); index = father; father = (index - 2) / 2;//繼續向上調整 } //不交換,不用向上調整 else break; } } private: vector<T> _heap; };
HuffmanTree.h
//哈夫曼樹類 #pragma once #include<iostream> using namespace std; #include<assert.h> #include "Heap.h"//藉助堆構建哈夫曼樹 template<typename T> struct HuffmanTreeNode { HuffmanTreeNode<T>* _leftchild;//左孩子 HuffmanTreeNode<T>* _rightchild;//右孩子 T _weight;//權值 HuffmanTreeNode(const T& weight = 0) :_leftchild(NULL) , _rightchild(NULL) , _weight(weight) {} }; template<typename T> class HuffmanTree { typedef HuffmanTreeNode<T> Node; public: HuffmanTree()//預設建構函式(全預設建構函式) :_root(NULL) {} //利用貪心演算法構造哈夫曼樹 HuffmanTree(const T* arr, size_t sz, const T& invaild)//構造 { _root = CreatHuffmanTree(arr, sz, invaild); } ~HuffmanTree()//析構 { _Destroy(_root); } Node* GetRoot()//獲取哈夫曼樹的根結點 { return _root; } private: //構建哈夫曼樹 Node* CreatHuffmanTree(const T* arr, size_t sz, const T& invaild) { struct LessNode { bool operator()(const Node* left, const Node* right) { return left->_weight < right->_weight; } }; Heap<Node*, LessNode> MinHeap; //1.依據權值建結點的MinHeap for (size_t i = 0; i<sz; i++) { if (arr[i] != invaild) { Node* node = new Node(arr[i]); MinHeap.Push(node); } } //2.構建huffmanTree while (MinHeap.Size()>1) { //2.從堆中依次去除兩個最小的結點建樹 Node* left = MinHeap.Top(); MinHeap.Pop(); Node* right = MinHeap.Top(); MinHeap.Pop(); Node* father = new Node(left->_weight + right->_weight); father->_leftchild = left; father->_rightchild = right; //3.將兩個最小值的和入堆 MinHeap.Push(father); } _root = MinHeap.Top(); MinHeap.Pop(); return _root; } void _Destroy(Node* root) { if (root == NULL) { return; } else { _Destroy(root->_leftchild); _Destroy(root->_rightchild); delete root; root = NULL; } } private: Node* _root; };
FileCompression.h
/檔案壓縮類 #pragma once #define _CRT_SECURE_NO_WARNINGS #include "HuffmanTree.h" #include<string> #include<stdlib.h> #include <stdio.h> typedef long long LongType; //字元資訊類---充當哈夫曼樹的結點權值 struct CharInformation { unsigned char _c;//出現的字元 //ASCLL表中一共有0-255十進位制編號的256個字元 //unsigned char 是單位元組的全8位表示字元型別,可以表示這256個字元 string _code;//該字元的哈夫曼編碼 LongType _count;//該字元在檔案中出現的次數 CharInformation()//建構函式 :_c(0) , _count(0) //_code不用初始化,會呼叫string類的預設建構函式初始化 {} //運算子過載,讓該自定義類也能像內建型別一樣,進行運算 //建哈夫曼樹時,和非法值比較使用 bool operator!=(const CharInformation& ch)const { return _count != ch._count; } //建小堆使用 bool operator<(const CharInformation& ch)const { return _count<ch._count; } //構造父節點時使用 CharInformation operator+(const CharInformation& ch) { CharInformation cf; cf._count = _count + ch._count; return cf; } }; //檔案壓縮與解壓縮類 class FileCompression { public: //建構函式 FileCompression() { //初始化字元表陣列的每個字元資訊的字元成員 for (int i = 0; i<256; ++i) { _ArrCh[i]._c = i; } } //壓縮函式 //傳入一個待壓縮的原始檔名,返回一個壓縮之後的檔名,方便解壓縮函式傳參 void Compression(string PrimaryFileName) { //1.開啟原始檔 FILE* fout = fopen(PrimaryFileName.c_str(), "rb");//以只讀的二進位制方式開啟檔案,否則讀漢字有問題 if (fout == NULL) { perror("fopen"); exit(1); } //2.統計256個字元出現的次數,並賦值給相應的陣列的字元 int ch = getc(fout); while (!feof(fout)) { _ArrCh[ch]._count++; ch = getc(fout); } //3.根據字元的出現次數構建哈夫曼樹,從而得到每個字元對應的哈夫曼編碼 //3.1根據字元資訊構建哈夫曼樹 CharInformation invaild;//count=0相當於一個非法值,不用構建到huffman樹中 HuffmanTree<CharInformation> huffmantree(_ArrCh, 256, invaild); //3.2根據哈夫曼樹得到對應的哈夫曼編碼,並賦值正確的字元資訊中 string code;//用於遞迴記錄字元的哈夫曼編碼 GetHuffmanCode(huffmantree.GetRoot(), code); //至此已經根據已經將原始檔的全部資訊(字元,字元的出現次數,字元對應的哈夫曼編碼) //全部統計出來,放入成員變數字元表陣列中,接下來就要運算元組,壓縮檔案 //4.壓縮檔案---即將字元的全部哈夫曼編碼以位元組為單位放入壓縮檔案中 //4.1建立壓縮檔案 string CompressFileName = PrimaryFileName; size_t index = CompressFileName.find('.', 0); CompressFileName = CompressFileName.substr(0, index); CompressFileName += ".compress"; FILE* Input = fopen(CompressFileName.c_str(), "wb");//以只寫防止開啟二進位制檔案,檔案不存在則建立新檔案 if (Input == NULL) { perror("fopen"); exit(1); } //4.2將所有字元的哈夫曼編碼以一位元組為單位放入壓縮檔案中 fseek(fout, 0, SEEK_SET);//更正文原件流的讀寫位置到檔案起始處 ch = getc(fout); char c = 0;//以一位元組為單位放入壓縮檔案 int size = 0;//記錄一個位元組八位是否寫滿 while (!feof(fout)) { //處理一個字元的所有哈夫曼編碼 for (size_t i = 0; i<_ArrCh[ch]._code.size(); i++) { c <<= 1; if (_ArrCh[ch]._code[i] == '1') { c |= 1;//先把1放到最低位 } //如果為'0'不用管; size++; //當一個位元組八位裝滿,則寫入壓縮檔案 if (size == 8) { //寫入壓縮檔案 if (fputc(c, Input) == EOF) { cout << "壓縮時哈夫曼編碼寫入失敗" << endl; } //重置 size = 0; c = 0; } } //一個字元處理完,讀取下一個字元 ch = getc(fout); } //處理最後一個不滿八位的字元,後面全加0 if (size>0) { c <<= (8 - size); fputc(c, Input); } fclose(fout);//關閉原始檔 fclose(Input);//關閉壓縮檔案 //至此壓縮原始檔完畢 //5.寫配置檔案---由於解壓時,收件人沒有原始檔案,而解壓壓縮檔案,要根據哈夫曼樹 //恢復字元,形成文中資訊;所以,在壓縮函式中要寫配置檔案,記錄 //哈夫曼樹的相關資訊,從而在解壓縮檔案時,可以根據此資訊,重建 //哈夫曼樹 //配置檔案格式: //a:5 //b:3 //5.1定義配置檔名 string ConfigurationFile = PrimaryFileName; ConfigurationFile = ConfigurationFile.substr(0, index); ConfigurationFile += ". config"; FILE* FinConfi = fopen(ConfigurationFile.c_str(), "wb"); //5.2給配置檔案寫資訊 string line;//記錄一個字元的全部資訊的字串 for (int i = 0; i<256; i++) { if (_ArrCh[i]._count != 0) { line += _ArrCh[i]._c;//字元 line += ',';//分隔符 char StringCount[25] = { 0 }; _itoa(_ArrCh[i]._count, StringCount, 10);//將次數轉化為字串 line += StringCount; line += '\n'; if (fputs(line.c_str(), FinConfi) == EOF) { cout << "配置檔案寫入失敗" << endl; } line.clear();//清除字串資訊,方便下一次統計 } } fclose(FinConfi);//關閉配置檔案 } //解壓縮函式 //傳入一個壓縮檔名 void UnCompression(string CompressFileName) { //1.通過配置資訊構建哈夫曼樹 //1.1還原字元表陣列 string ConfigurationFile = CompressFileName; size_t index = ConfigurationFile.find('.', 0); ConfigurationFile = ConfigurationFile.substr(0, index); ConfigurationFile += ". config"; FILE* FinConfi = fopen(ConfigurationFile.c_str(), "rb"); if (FinConfi == NULL) { perror("fopen"); exit(1); } string line;//記錄每行配置檔案的資訊 while (ReadLine(FinConfi, line)) { //該行為空 if (line.empty()) { line += '\n'; continue; } else { unsigned char ch = line[0]; //使用string::substr(pos)函式提取字元出現的次數 _ArrCh[ch]._count = atoi(line.substr(2).c_str()); line.clear(); } } //1.2構建哈夫曼樹 CharInformation invaild; HuffmanTree<CharInformation> ht(_ArrCh, 256, invaild); //1.3解壓縮檔案--通過從根結點遍歷哈夫曼樹得到原始檔案 string UnCompressFileName = CompressFileName; UnCompressFileName = UnCompressFileName.substr(0, index); UnCompressFileName += " .unCompress"; FILE* Func = fopen(UnCompressFileName.c_str(), "wb"); if (Func == NULL) { perror("fopen"); exit(1); } //1.4讀取壓縮檔案 FILE* Fcom = fopen(CompressFileName.c_str(), "rb"); if (Fcom == NULL) { perror("fopen"); exit(1); } HuffmanTreeNode<CharInformation>* root = ht.GetRoot(); LongType TotalCites = root->_weight._count;//原始檔案所有字元的個數 HuffmanTreeNode<CharInformation>* cur = root;//遍歷二叉樹 int ch = fgetc(Fcom); int pos = 7; while (TotalCites>0) { if (ch&(1 << pos))//右1 { cur = cur->_rightchild; } else//左0 { cur = cur->_leftchild; } if (cur->_leftchild == NULL&&cur->_rightchild == NULL) { fputc(cur->_weight._c, Func);//將對應的字元寫入解壓縮檔案中 cur = root;//回到根結點繼續尋找下一個字元 --TotalCites;//總還原字元減少1個 if (TotalCites == 0) { break; } } pos--; //一個壓縮字元每位處理完畢 if (pos<0) { ch = fgetc(Fcom);//從壓縮檔案中讀下一個壓縮字元 pos = 7; } } fclose(FinConfi); fclose(Fcom); fclose(Func); } protected: //獲取對應字元的哈夫曼編碼 void GetHuffmanCode(HuffmanTreeNode<CharInformation>* root, string code) { if (root == NULL) { return; } //遍歷到葉子節點,即找到了對應字元的哈夫曼編碼 if (root->_leftchild == NULL&&root->_rightchild == NULL) { _ArrCh[root->_weight._c]._code = code; } //左0,右1 GetHuffmanCode(root->_leftchild, code + '0'); GetHuffmanCode(root->_rightchild, code + '1'); } //讀取配置檔案的一行 bool ReadLine(FILE* FinConfi, string& line) { int ch = fgetc(FinConfi); if (feof(FinConfi))//如果讀到檔案末尾,返回假 { return false; } while (!feof(FinConfi) && ch != '\n') { line += ch; ch = fgetc(FinConfi); } //沒有讀到檔案末尾 return true; } protected: CharInformation _ArrCh[256];//檔案操作的物件,字元資訊表 };
test.c
#include "FileCompression.h"
#include<windows.h>
//#include<iostream>
//#include<stdlib.h>
//using namespace std;
void test()
{
FileCompression fc;
cout << "開始壓縮" << endl;
cout << "壓縮用時";
int start = GetTickCount();
//第一組:文字檔案
fc.Compression("123.txt");
//第二組:圖片檔案
//fc.Compression("專案.png");
//第三組:視訊檔案
//fc.Compression("Video.WMV");
//第四組:音訊檔案
//fc.Compression("不曾告別(三毛姐姐如晤) - 齊豫,潘越雲.mp3");
//第五組:大檔案字串文章---壓縮效果最好
//fc.Compression("yingyu.txt");
int end = GetTickCount();
cout << "compress time:" << (end - start) << endl;
cout << "開始解壓" << endl;
cout << "解壓用時:";
start = GetTickCount();
//////第一組:文字檔案
fc.UnCompression("123.compress");
////第二組:圖片檔案
//fc.UnCompression("專案.compress");
////第三組:視訊檔案
//fc.UnCompression("Video.compress");
//第四組:音訊檔案
//fc.UnCompression("不曾告別(三毛姐姐如晤) - 齊豫,潘越雲.compress");
//第五組:大檔案字串文章---壓縮效果最好
/*fc.UnCompression("yingyu.compress");*/
end = GetTickCount();
cout << "uncompress time:" << (end - start) << endl;
};
int main()
{
test();
system("pause");
return 0;
}