文件压缩与解压缩(哈夫曼编码压缩方式)

         本文采用哈夫曼编码的方式进行文件(文本文件)压缩和解压缩,首先介绍项目的整体思路:哈夫曼编码压缩文件实际就是统计出文件中各个字符出现的频率,然后为每个字符生成对应的编码,然后将每个字符用哈夫曼编码的形式按字节保存在压缩文件中。而文件的解压缩实际上就是将压缩文件翻译过来保存到解压缩文件中,需要使用压缩过程中生成的配置文件配合完成。下面将具体介绍文件的压缩和解压缩步骤:

       文件的压缩的核心是产生哈夫曼编码,而哈夫曼编码的过程中需要找到一系列数据中的最小权重和次小权重,我们自然联想到用堆这种结构时间复发度小并且很方便找到最小值和次小值,我将堆的源代码放在Heap.h文件中(见下文)。现在我们进行文件压缩。

       1。统计文件中所有字符的出现次数。由于Ascall码字符一共255个,只有前128个字符可以显示,定义字符变量时一定要定义成无符号型变量unsigned char ch如下,这是ch读不到文件的结束标志,所以我们可以用函数feof来代替文件的结束标志EOF,最重要的是文件的打开方式一定要是二进制的形式打开否则读不到汉字字符,将出现乱码。关于存储方式我们采用哈希表的方式将每个字符映射为哈希表的下标,这样可以很方便的将每个字符和出现的次数对应起来。需要说明的是我们这个哈希表存的绝不是单纯的次数而是结点FileInfo,这个节点称为权重结点中保存出现次数和字符以及将来我们产生的哈夫曼编码,方便我门进行索引。

bool Compress(const char *filename)//该函数起到统计的作用
{
FILE *fout = fopen(filename, "rb");//以二进制形式打开文件
assert(fout);
unsigned char ch = fgetc(fout);
while (!feof(fout))
{
_Infos[ch]._count++;//统计各种字符在文件中的个数
ch = fgetc(fout);
COUNT++;//统计文件中总的字符个数
}
fclose(fout);
return true;
}

     2。现在我们创建一个最小堆,将统计到的结点压入堆中

     3。从堆中取数据在HuffMan.h头文件中建立哈夫曼树

     4。通过哈夫曼树产生哈夫曼编码存入节点中

     5。遍历待压缩文件将对应的哈夫曼编码按字节保存到压缩文件中

     6.将每个字符出现的个数保存到配置文件中。由步骤5产生的压缩文件,当我们遍历到文件的最后一个字符时,如果编码凑不成8      一个字节我们给剩下的位置补0,为了解决最后一个字符的解析问题,我们将待压缩文件中的总的字符个数统计出来存到配置文       件的第一行,以后每行一“X,n”的形式存放字符和对应的出现次数。这样我们的文件压缩过程就完成了。


     文件的解压缩思路简单,但是要尤其注意细节读配置文件就要花些心思,体现在换行符的统计上,下面进行文件的解压缩(源文件见Uncompress.h):

      1。读配置文件

      2。通过配置文件重构哈夫曼树

      3。开始文件的解压缩,按字符读入编码通过编码在哈夫曼树种寻找对应的字符,并将字符存入到解压缩文件中去,通过配置文件中读入的COUNT来控制最后一个字符正确编码的起止。这样文件的解压缩完成。

总结:

项目的特点和用到的技术有,仿函数,堆,哈夫曼编码技术,string类,哈希表

项目注意事项,文件名字转换方法艺术,文件的二进制读入写入,读配置文件时换行符的处理,以及统计的字符数如何以10进制字符的形式存到文件中去。其他问题详见源代码重点注释的地方。

Heap.h

#include <vector>


template<class T>
struct Less
{
	bool operator() (const T& l, const T& r)
	{
		return l < r; // operator<
	}
};

template<class T>
struct Greater
{
	bool operator() (const T& l, const T& r)
	{
		return l > r; // operator>
	}
};


template<class T, class Compare=Less<T>>//哈夫曼结点的仿函数
class Heap
{
public:
	Heap()
	{}
	Heap(const T* a, size_t size)
	{
		for (size_t i = 0; i < size; ++i)
		{
			_arrays.push_back(a[i]);//将所有数据插入堆
		}

		// 建堆
		for (int i = (_arrays.size() - 2) / 2; i >= 0; --i)
		{
			AdjustDown(i);//对这个范围的每个节点都向下调整,建堆的过程实际就是向下调整堆的过程
		}
	}

	void Push(const T& x)
	{
		_arrays.push_back(x);
		AdjustUp(_arrays.size() - 1);//插入节点的过程实际就是向上调整堆的过程
	}

	void Pop()
	{
		assert(_arrays.size() > 0);
		swap(_arrays[0], _arrays[_arrays.size() - 1]);
		_arrays.pop_back();

		AdjustDown(0);
	}

	T& Top()
	{
		assert(_arrays.size() > 0);
		return _arrays[0];
	}

	bool Empty()
	{
		return _arrays.empty();
	}

	size_t Size()
	{
		return _arrays.size();
	}

	void AdjustDown(int root)
	{
		int child = root * 2 + 1;

		Compare com;
		while (child < _arrays.size())
		{
			// 比较出左右孩子中小的那个
			if (child + 1<_arrays.size() &&
				com(_arrays[child + 1], _arrays[child]))
			{
				++child;
			}

			if (com(_arrays[child], _arrays[root]))
			{
				swap(_arrays[child], _arrays[root]);
				root = child;
				child = 2 * root + 1;
			}
			else
			{
				break;
			}
		}
	}

	void AdjustUp(int child)
	{
		int parent = (child - 1) / 2;

		while (child > 0)
		{
			if (Compare()(_arrays[child], _arrays[parent]))
			{
				swap(_arrays[parent], _arrays[child]);
				child = parent;
				parent = (child - 1) / 2;
			}
			else
			{
				break;
			}
		}
	}

	void Print()
	{
		for (size_t i = 0; i < _arrays.size(); ++i)
		{
			cout << _arrays[i] << " ";
		}
		cout << endl;
	}

public:
	vector<T> _arrays;
};

//测试堆 
//void Test1()
//{
//	int a[10] = { 10, 11, 13, 12, 16, 18, 15, 17, 14, 19 };
//	Heap<int, Greater<int> > hp1(a, 10);
//	hp1.Push(1);
//	hp1.Print();
//
//	Heap<int> hp2(a, 10);
//	hp2.Push(1);
//	hp2.Print();
//
//
//	Less<int> less;
//	cout<<less(1, 2)<<endl;
//
//	Greater<int> greater;
//	cout<<greater(1, 2)<<endl;
//}
HuffMan.h

#pragma once

#include "Heap.h"

template<class T>
struct HuffManNode
{
	HuffManNode<T> *_left;
	HuffManNode<T> *_right;
	HuffManNode<T> *_parent;
	T _weight;
	HuffManNode(const T&x)
		: _left(NULL)
		, _right(NULL)
		, _parent(NULL)
		, _weight(x)
	{}
};

template<class T>
class HuffMan
{
	typedef HuffManNode<T> Node;

	template<class T>
	struct NodeCompare
	{
		bool operator() ( const Node*l, const Node*r)//模板不能分离编译
		//因此用到NodeCompare的地方都要放到一个文件
		{
			return l->_weight < r->_weight;
		}
	};

protected:
	Node* _root;

public:
	HuffMan()
		:_root(NULL)
	{}

	~HuffMan()
	{}

public:
	Node* GetRootNode()
	{
		return _root;
	}

	Node* CreatTree(T*a, size_t size,const T& invalid)
	{
		//取数转换成哈夫曼结点,放到最小堆中
		assert(a);
		Heap<Node*, NodeCompare<T>> minHeap;
		for (size_t i = 0; i < size; ++i)
		{
			if (a[i] != invalid)
			{
				Node*node = new Node(a[i]);
			    minHeap.Push(node);
			}
			
		}
		/*for (int i = 0; i<10; i++)
		{
			Node *temp = minHeap._arrays[i];//用于测试的代码
			cout << temp->_weight << " ";
		}*/
		//从最小堆中取最小的和次小的结点,建立哈夫曼树
		while (minHeap.Size()>1)
		{
			Node* left = minHeap.Top();//取最小的
			minHeap.Pop();
			Node* right = minHeap.Top();//取次小的
			minHeap.Pop();
			Node *parent = new Node(left->_weight + right->_weight);
			parent->_left = left;
			parent->_right = right;
			left->_parent = parent;
			right->_parent = parent;//链接节点间的关系

			minHeap.Push(parent);//将最小的和次小的之和放到堆中重新调整
		}
		_root = minHeap.Top();//堆中最后剩下的结点就是哈夫曼的根结点
		return _root;
	}
	
	HuffManNode<T>* GetRoot()
	{
		return _root;
	}
	void PrintHuff()
	{
		Node *root = _root;
		_Print(root);
	}
protected:
	void _Print(Node *root)
	{
		if (root == NULL)
		{
			return;
		}
		else
		{
			cout << root->_weight;
		}
		_Print(root->_left);
		_Print(root->_right);
	}

};

//void TestHuff()
//{
//	int a[] = { 1, 0, 2, 3, 4, 5, 6, 7, 8, 9 };
//	HuffMan<int> t;
//	t.CreatTree(a, sizeof(a) / sizeof(int), -1);
//
//}
filecompress.h

# include<iostream>
# include<cassert>
# include<string>
# include<algorithm>
# include"HuffMan.h"
using namespace std;
typedef unsigned long long LongType;
struct FileInfo
{
  unsigned	char _ch;
  LongType  _count;
  string  _code;
  FileInfo(unsigned char ch=0)
	  :_ch(ch)
	  , _count(0)
  {}
 FileInfo operator+(FileInfo filein)
  {
	 FileInfo temp;
	 temp._count=_count + filein._count;
	 return temp;
  }
 bool operator<( const FileInfo filein)const               
 {
	 return _count < filein._count;
 }
 bool operator!=(const FileInfo  Invalid)const
 {
	 return _count != Invalid._count;
 }
};
class FileCompress
{
protected:
	FileInfo _Infos[256];
	LongType COUNT = 0;
public:
	FileCompress()
	{
		for (int i = 0; i < 256;i++)
		{
			_Infos[i]._ch = i;
		}
	}
	bool Compress(const char *filename)//该函数起到统计的作用
	{
		FILE *fout = fopen(filename, "rb");//以二进制形式打开文件
		assert(fout);
		unsigned char ch = fgetc(fout);
		while (!feof(fout))
		{
			_Infos[ch]._count++;//统计各种字符在文件中的个数
			ch = fgetc(fout);
			COUNT++;//统计文件中总的字符个数
		}
		fclose(fout);
		return true;
	}
	void GenerateHuffManCode()
	{
		HuffMan<FileInfo> t;
		FileInfo invalid;
		t.CreatTree(_Infos, 256, invalid);
		HuffManNode<FileInfo>*root = t.GetRoot();
		_GenrateHuffManCode(root);
	}
	void _GenrateHuffManCode(HuffManNode<FileInfo>* root)
	{
		if (root == NULL)
		{
			return;
		}
		_GenrateHuffManCode(root->_left);
		_GenrateHuffManCode(root->_right);

		if ((root->_left == NULL) && (root->_right == NULL))
		{
			HuffManNode<FileInfo>*cur = root;
			HuffManNode<FileInfo>*parent = cur->_parent;
			string &code = _Infos[cur->_weight._ch]._code;
			while (parent)//从叶子节点走到根结点
			{
				if (parent->_left == cur)
			
					code += '0';		
				else		
					code += '1';	
				cur = parent;
				parent = cur->_parent;
			}
			reverse(code.begin(), code.end());
		}		
	}

	//下面进行文件压缩
	void CompressFile(const char *filename)
	{
		Compress(filename);
		string compressFile = filename;
		compressFile += ".huffman";
		FILE *FinCompress = fopen(compressFile.c_str(), "wb");
		assert(FinCompress);//对压缩文件的命名处理
		
		GenerateHuffManCode();//产生编码
		FILE *fout = fopen(filename, "rb");
		assert(fout);

		//进行文件压缩
		 unsigned char inch = 0;
		int index = 0;
		char ch = fgetc(fout);
		while (ch!=EOF)
		{
			string&code = _Infos[(unsigned char)ch]._code;
			for (int i = 0; i < code.size(); ++i)
			{
				++index;
				inch <<= 1;
				if (code[i] == '1')
				{
					inch |= 1;
				}
				if (index == 8)
				{
					fputc(inch, FinCompress);
					index = 0;
					inch = 0;
				}		
			}
			ch = fgetc(fout);
		}
		if (index != 0)
		{
			inch <<= (8 - index);
			fputc(inch,FinCompress);
		}
		fclose(fout);
		FileInfo invalid;
		CreateConfig(filename,invalid);
	}
	void CreateConfig( const char* filename,FileInfo invalid)
	{
		string ConfigFile = filename;
		ConfigFile += ".config";
		FILE *FileConfig = fopen(ConfigFile.c_str(), "wb");
		assert(FileConfig);

		char ch[256];
		string tempcount;
		int i = 0;
		tempcount=	_itoa(COUNT, ch, 10);
		while (i < tempcount.size())
		{
			fputc(tempcount[i],FileConfig);
			i++;
		}//将总的字符数写入配置文件
		fputc('\n', FileConfig);
		for (size_t i = 0; i < 256; i++)
		{
			if (_Infos[i] != invalid)
			{
				string chInfo;
				chInfo.clear();

				if (_Infos[i]._count>0)
				{
					chInfo += _Infos[i]._ch;
					chInfo += ',';
					char ch[256]; //转换成的字符可能足够长
					_itoa(_Infos[i]._count,ch, 10);
					chInfo += ch;
					for (int j = 0; j < chInfo.size(); j++)
					{
						fputc(chInfo[j], FileConfig);
					}
								
						fputc('\n', FileConfig);					
				}
			}
		}
		fclose(FileConfig);
	}

};
void TestFileCompress()
{
	FileCompress FC;
	FC.CompressFile("fin.txt");
	cout << "压缩成功" << endl;
}
Uncompress.h



# include<iostream>
using namespace std;
# include"HuffMan.h"
# include"filecompress.h"

class Uncompress
{
private:
	FileInfo _UNinfos[256];
	LongType Count;
public:
	Uncompress()//哈希表的初始化
	{
		for (int i = 0; i < 256; i++)
		{
			_UNinfos[i]._ch = i;
		}
		Count = 0;
	}
	bool _Uncompress(const char *Ufilename)//读配置文件
	{
		string Configfile = Ufilename;
		Configfile += ".config";
		FILE *fpConfig = fopen(Configfile.c_str(), "rb");
		assert(fpConfig);

		string line;
	    unsigned char ch = fgetc(fpConfig);
		while (ch != '\n')
		{   
			line += ch;
			ch =fgetc(fpConfig);		
		}//读取第一个字符
		Count = atoi(line.substr(0).c_str());//(总的字符个数)
		ch = fgetc(fpConfig);//读入下一行字符
		line.clear();
		int j = 0;
		while (!feof(fpConfig))
		{
			
			j++;
			while (ch != '\n')
			{
				line += ch;
				ch = fgetc(fpConfig);

			}
			if (line.empty())
			{
				line += '\n';
				ch = fgetc(fpConfig);
				while (ch != '\n')
				{
					line += ch;
					ch = fgetc(fpConfig);
				}
			}
			ch = fgetc(fpConfig);
			unsigned char tempch = line[0];//将第一个字符转换成无符号数和下标对应起来
			                               //尤其要注意这里稍微不注意就全乱码了  
			_UNinfos[tempch]._count = atoi(line.substr(2).c_str());//截取字符串并转换成整型数据
			line.clear();
		}
		return true;
	}
	void GenrateHuffManCode(HuffManNode<FileInfo>* root)//重构哈夫曼树
	{
		if (root == NULL)
		{
			return;
		}
		GenrateHuffManCode(root->_left);
		GenrateHuffManCode(root->_right);

		if ((root->_left == NULL) && (root->_right == NULL))
		{
			HuffManNode<FileInfo>*cur = root;
			HuffManNode<FileInfo>*parent = cur->_parent;
			string &code = _UNinfos[cur->_weight._ch]._code;
			while (parent)//从叶子节点走到根结点
			{
				if (parent->_left == cur)

					code += '0';
				else
					code += '1';
				cur = parent;
				parent = cur->_parent;
			}
			reverse(code.begin(), code.end());
		}
	}

	bool UncomPress(const char *UNfilename)//文件的解压缩过程
	{
		_Uncompress(UNfilename);
		HuffMan<FileInfo> Re_huffTree;
		FileInfo invalid;
		HuffManNode<FileInfo>*root = Re_huffTree.CreatTree(_UNinfos, 256, invalid);//重构哈夫曼树
		GenrateHuffManCode(root);

		//打开文件
		string UnComPressFile = UNfilename;
		UnComPressFile += ".Unhuffman";
		FILE *UCPfile = fopen(UnComPressFile.c_str(), "wb");
		string ComPressFile = UNfilename;
		ComPressFile += ".huffman";
		FILE *CPfile = fopen(ComPressFile.c_str(), "rb");

		//解压缩字符写入文件


		HuffManNode<FileInfo>*tempRoot = root;//获得其根结点
		while (!feof(CPfile))
		{
			unsigned char ch = fgetc(CPfile);
			int bitCount = 7;
			for (int i = bitCount; i >= 0; i--)
			{
				if (ch&(1 << i))
				{
					tempRoot = tempRoot->_right;
				}
				else
				{
					tempRoot = tempRoot->_left;
				}
				if (!tempRoot->_left&&!tempRoot->_right)//做到这里
				{
					fputc(tempRoot->_weight._ch, UCPfile);
					Count--;
					tempRoot = root;
				}
				if (Count <= 0)
				{
					break;
				}
			}
			if (Count <= 0)
			{
				break;
			}
		}
		return true;
	}

};
void TestUNCP()
{
	Uncompress Uncp;
	Uncp.UncomPress("fin.txt");
}




  • 11
    点赞
  • 66
    收藏
    觉得还不错? 一键收藏
  • 3
    评论
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值