1. 程式人生 > >檔案壓縮專案原始碼

檔案壓縮專案原始碼

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;
}