文件压缩的原理:

  文件压缩总体可以分为有损压缩和无损压缩两类,有损压缩是指对mp3等格式的文件,忽略一些无关紧要的信息,只保留一些关键的信息,但并不因此影响用户对于这些mp3格式文件的体验度,无损压缩是基于比特位的压缩,即都是通过某种特殊的编码方式将数据信息中存在的重复度、冗余度有效地降低,从而达到数据压缩的目的。比如,“中国”可以代替中华人民共和国。压缩的文件必须可以解压,这样才算达到了文件压缩的目的。

  haffman压缩:

1.定义一个字符数组vector<_char_info>(_char_info为一个结构体,存放字符的信息),大小为256,遍历整个文档,统计文档中字符出现的次数(以字符的ascll码为下标,每当字符出现一次,就将存储在该位置的数字加一);

2.根据统计得到的次数建立haffman树,haffman树采用孩子双亲表示法

  priority_queue实际是一个堆,默认情况下按照按照小于的方式建堆,即默认情况下为大堆,我们建立haffman需要先找到vector<_char_info>中的子符出现次数(权值)最小的,因此需要建立小堆,这样堆顶元素即为vector<_char_info>中的最小值。建立小堆,需要自定义priority_queue,将其设置成大于的比较方式,因此需要对()运算符进行重载,设置成大于的比较方式

3.haffman树建立好之后,根据 haffman获取文档中出现字符的haffman编码

  记录根节点的权值,存储的文件中所有字符的个数filesize

  获取方式:从haffman树的叶子节点开始,如果当前节点的父节点不为空,则一直往上遍历。得到的编码我们需要将其reverse一下,获得正确的编码,每reverse一下,需要将filzesize--,将获取的编码存储在_char_info结构体中的_char_Code字段,当filesize为0时,获取了所有字符的编码,进行下一步

  往压缩文件中写源文件的信息,我们将自己压缩的文件后缀命名为.hzp,将源文件的后缀,字符信息存储到压缩的文件中,注意,解压时必须要能区分文本信息与我们写入的辅助信息,因此需要将每个字符的信息以如下的方式存入.hzp文件中

  

   将辅助信息写入压缩文件之后,开始以将源文档中字符编码按照比特位的方式一个字节一个字节写入压缩文件,当源文件的文件在此到达文件末尾的时候写入完毕,此时还要注意是否将所有的比特位都写入压缩文件,如果没有,则需要特殊处理

   以上的步骤做完之后,需要解压文件

 4.解压文件:

  打开后缀为.hzp的文件,由于我们往.hzp文件中写信息的时候是以上图中的方式,因此,我们需要先读取解压后文件的后缀,存入一个字符串中str_suffix中,读取一行后接着读取需读取的辅助信息的行数,读到行数之后,将字符形式的行数转化为size_t类型,可以调用库函数atoi。将读取的信息存入_char_info结构体中(字符以及字符出现的次数),读完辅助信息之后根据辅助信息重建haffman树。

  haffman重建之后,根据压缩文件中的字符遍历haffman树,如果读到的字节中的比特位为1,则访问haffman树的右子树,为0则访问haffman树的左子树,当走到叶子节点的时候需要将该编码对应的字符写入到解压后的文件中(每次写一个字符一个字符),这时需要将访问haffman树的指针复位,重新指向根节点,让filesize--,当filesize为0时说明解压完毕。

 5.一下为编程代码:

  (1)头文件:

 #pragma once 

 #include<iostream>
#include<vector>
#include<string>
#include<assert.h>
#include"HaffmanTree.hpp"
using namespace std; typedef unsigned char UCH; struct Char_info{ //构造函数
Char_info(size_t char_count = )
:_char_count(char_count)
{} UCH _ch; //记录当前字符
long long _char_count; //记录当前字符在文件中的位置
string _str_code; //记录当前字符的haffman编码 Char_info operator+(const Char_info& temp)const{
return Char_info(temp._char_count + _char_count);
} bool operator>(const Char_info& temp)const{
return _char_count > temp._char_count;
} bool operator<(const Char_info& temp)const{
return _char_count < temp._char_count;
} bool operator!=(const Char_info& temp)const{
return _char_count != temp._char_count;
} bool operator==(const Char_info& temp)const{
return _char_count == temp._char_count;
} bool operator!()const{
return !_char_count;
} }; class FileCompressHaffMan{
public:
//构造函数
FileCompressHaffMan(); void CompressFile(const string& strPath); //文件压缩 void GetHaffmanCode(HaffmanTreeNode<Char_info>* pRoot); //获取haffman编码 void WriteHead(FILE* DestFile, const string& strPath); //写后缀,编码等信息 void UNCompressFile(const string& strPath); //解压文件 void GetLine(FILE* fIn,string& str);//读取一行数据 vector<Char_info> _char_info; };

 (2)构造函数:

 FileCompressHaffMan::FileCompressHaffMan(){
_char_info.resize();
for (size_t i = ; i < _char_info.size(); ++i){
_char_info[i]._ch = i;
}
}

   (3)压缩文件的函数:

 void FileCompressHaffMan::CompressFile(const string& strPath){

     //打开文件,记录当前字符的出现字数
if (strPath.empty()){
cout << "文件目录错误" << endl;
return;
} FILE* Source_File = fopen(strPath.c_str(), "rb"); //以二进制的形式打开文件 if (nullptr == Source_File){
cout << "打开文件失败" << endl;
return;
} //定义读取缓冲区,接收一次读取的数据 UCH* ReadBuff = new UCH[]; //定义记录的数组,用来统计文档中每个字符出现的次数 while (true){
size_t ReadSize = fread(ReadBuff, , , Source_File);
if ( == ReadSize){
//读取文件完成
break;
} //统计文档中字符出现的次数
for (size_t i = ; i < ReadSize; ++i){
_char_info[ReadBuff[i]]._char_count++;
}
} //将char_info的数据进行排序,取权值最小的,建立haffman树
HaffmanTree<Char_info> ht;
ht.CreateHaffmanTree(_char_info,_char_info[]); //获取haffman编码
GetHaffmanCode(ht.GetRoot());
//fseek(Source_File, 0, SEEK_SET); //将文件指针偏移到文件的开始位置 FILE* DestFile = fopen("finshFileCompress.hzp", "wb"); //打开压缩文件
assert(DestFile); WriteHead(DestFile, strPath); //往目标文件中写入后缀,字符编码等信息 //写压缩文件
//由于当前打开额源文件的文件指针已经到达文件末尾,所以需要将文件指针重置到开头
fseek(Source_File, , SEEK_SET);
char ch = ;
int bitcount = ;
while (true){
size_t Read_Size = fread(ReadBuff, , , Source_File);
if ( == Read_Size){
break;
//读取文件完毕,结束
} //以比特位的方式将字符编码写入到文件中
for (size_t i = ; i < Read_Size; ++i){
string& char_Code = _char_info[ReadBuff[i]]._str_code;
for (size_t j = ; j < char_Code.size(); ++j){
ch <<= ;
if ('' == char_Code[j]){
ch |= ;
}
bitcount++;
if ( == bitcount){
fputc(ch, DestFile);
//一个字节一个字节写入文件
bitcount = ;
}
}
}
}
//写完之后还有比特位剩余,则将剩余的比特位写入文件
if (bitcount > && bitcount < ){
ch <<= ( - bitcount); //等于号啊啊啊啊啊
fputc(ch, DestFile);
} delete[] ReadBuff;
fclose(DestFile); //关闭压缩文件
fclose(Source_File); //关闭源文件 }
 void FileCompressHaffMan::GetHaffmanCode(HaffmanTreeNode<Char_info>* pRoot)    //获取haffman编码
{
if (nullptr == pRoot){
return;//没写这个判断条件就会造成死循环
}
GetHaffmanCode(pRoot->_pLeft);
GetHaffmanCode(pRoot->_pRight); if ((pRoot->_pLeft == nullptr)&&(pRoot->_pRight == nullptr)){
HaffmanTreeNode<Char_info>* pCur = pRoot;
HaffmanTreeNode<Char_info>* pParent = pCur->_pParent; string& strCode = _char_info[pCur->_weight._ch]._str_code;
while (nullptr!=pParent){
if (pCur == pParent->_pLeft){
strCode += '';
}
else
{
strCode += '';
}
//pParent要往下走,不然会形成死循环
pCur = pParent;
pParent = pCur->_pParent;
}
reverse(strCode.begin(),strCode.end()); //翻转字符串,将haffman编码写入
}
} void FileCompressHaffMan::WriteHead(FILE* DestFile, const string& strPath){
string str_suffix = strPath.substr(strPath.rfind('.')); //截取文件的后缀写入目标文件中
str_suffix += '\n'; string str_charinfo;
char szCount[];
size_t linecount = ; for (size_t i = ; i < ; ++i){
if (_char_info[i]._char_count){
str_charinfo += _char_info[i]._ch;
str_charinfo += '/';
_itoa(_char_info[i]._char_count, szCount, );
str_charinfo += szCount;
str_charinfo += '\n';
linecount++;
}
}
_itoa(linecount, szCount, );
str_suffix += szCount;
str_suffix += '\n'; str_suffix += str_charinfo;
fwrite(str_suffix.c_str(), , str_suffix.size(), DestFile); }

   (4)创建haffman树(采用模板的方式)

 #pragma once
#include<vector>
#include<string>
#include<queue>
using namespace std; //以模板的形式写haffman树 //haffman树的结点信息
template<class T>
struct HaffmanTreeNode{ typedef HaffmanTreeNode<T> HFNode; T _weight; //haffman树节点的权值,存放文档中字符出现的次数
HFNode* _pLeft; //haffman树的左孩子
HFNode* _pRight; //haffman树的右孩子
HFNode* _pParent; //haffman树的结点的双亲 //构造函数,初始化haffman树节点的权值以及指针
HaffmanTreeNode(const T& weight)
:_weight(weight)
, _pLeft(nullptr)
, _pRight(nullptr)
, _pParent(nullptr)
{}
}; //自定义priority_queue的排序方式,建立小堆(默认比较方式是>,即默认情况下建立大堆)
template<class T>
struct compare{
typedef HaffmanTreeNode<T>* pHFNode;
bool operator()(const pHFNode& pLeft,const pHFNode& pRight){
return pLeft->_weight > pRight->_weight;
}
}; //haffman树
template<class T>
class HaffmanTree{
public:
typedef HaffmanTreeNode<T> HFNode;
typedef HFNode* pHFNode; HaffmanTree()
:_pRoot(nullptr)
{}
~HaffmanTree(){
_delete(_pRoot);
} //v用来存放Char_info结构体
void CreateHaffmanTree(const vector<T>& v,const T& invalid){
if (v.empty()){
return;
//v中为空,则说明信息不存在
} //通过priority_queue建立小堆,对v中的数据进行排序
priority_queue<pHFNode, vector<pHFNode>, compare<T>> q;
//入队
for (size_t i = ; i < v.size(); ++i){
if (v[i] != invalid){
q.push(new HFNode(v[i]));
} } //如果q中的个数大于1.则说明haffman树环没有创建成功
while (q.size()>){
pHFNode pLeft = q.top();
q.pop();
pHFNode pRight = q.top();
q.pop(); pHFNode pParent = new HFNode(pLeft->_weight + pRight->_weight);
pParent->_pLeft = pLeft;
pParent->_pRight = pRight;
pLeft->_pParent = pParent;
pRight->_pParent = pParent;
//将新的子树插入优先级队列
q.push(pParent);
}
_pRoot = q.top(); //根节点赋值给_pRoot
} //获取haffman树的根节点
pHFNode GetRoot(){
return _pRoot;
} //销毁haffman树
void _delete(HaffmanTreeNode<T>* pRoot){
if (pRoot == nullptr){
return;
}
_delete(pRoot->_pLeft);
_delete(pRoot->_pRight); delete pRoot;
} private:
pHFNode _pRoot; //haffman树的根节点
};

    (6)解压文件

 void FileCompressHaffMan::GetLine(FILE* fIn, string& str)//读取一行数据
{
//feof用来检测文件指针是否到达文件末尾
while (!feof(fIn)){
UCH ch = fgetc(fIn);
if (ch == '\n'){
return;
}
str += ch;
}
} void FileCompressHaffMan::UNCompressFile(const string& strPath){
string& str_suffix = strPath.substr(strPath.rfind('.'));
if (".hzp" != str_suffix){
cout << "文件格式不匹配" << endl;
return;
}
FILE* fIn = fopen(strPath.c_str(), "rb");
if (nullptr == fIn){
return;
//打开文件失败
} string strfix = ""; //获取文件的后缀
GetLine(fIn, strfix); string strChar = "";
GetLine(fIn, strChar);
size_t linecount = atoi(strChar.c_str());
for (size_t i = ; i < linecount; ++i){
strChar = "";
GetLine(fIn, strChar);
if (strChar.empty()){
strChar += '\n';
GetLine(fIn, strChar);
}
_char_info[(UCH)strChar[]]._char_count = atoi(strChar.c_str() + );
//加2的原因是要把字符以及分隔符跳过去
} //创建haffman树
HaffmanTree<Char_info> ht;
ht.CreateHaffmanTree(_char_info, _char_info[]); string UNCompress = "finshFileCompress";
UNCompress += strfix; //解压后文件的后缀 FILE* fOut = fopen(UNCompress.c_str(), "wb");
assert(fOut); char* pReadBuff = new char[]; HaffmanTreeNode<Char_info>* pCur = ht.GetRoot(); char pos = ;
long long filesize = pCur->_weight._char_count; while (true){
size_t ReadSize = fread(pReadBuff, , , fIn);
if (ReadSize == ){
break;//文件读取完毕
} for (size_t i = ; i < ReadSize; ++i){
pos = ;
char ch = pReadBuff[i];
for (size_t j = ; j < ; ++j){
//测试代码
//cout << pReadBuff[j] << endl;
//cout << (1 << pos) << endl;
//cout << (pReadBuff[j] & (1 << pos)) << endl;
if (ch&(<<pos)){
pCur = pCur->_pRight;
}
else{
pCur = pCur->_pLeft;
} if (nullptr == pCur->_pLeft&&nullptr == pCur->_pRight){
fputc(pCur->_weight._ch, fOut);
pCur = ht.GetRoot();
filesize--;
if ( == filesize){
break;//解压缩完成
}
}
pos--;
}
}
}
delete[] pReadBuff;
fclose(fIn);
fclose(fOut);
}

注意事项:

  1.在写代码时注意语法错误,ch|1没“=”的这种错误编译器是无法检测出来的

  2.创建haffman树的时候需要将那些权值不为0的字符信息存入haffman树,所以需要键入判断条件

  3.在读写文件时需要以二进制的方式打开文件,否则如果以文本的方式打开文件,当文档中遇到-1时,会认为文档结束,因此会造成文档不能完全压缩,只压缩一部分。如果以二进制的方式打开文件,则会避免这种问题

  4.解压文件的时候需要处理换行,如果读到一行,读到的字符串为空串,说明读到了换行,此时需要我们判断,如果字符串为空,则需要将换行加入字符串写入文件,并且再读一次,否则当文档中遇到换行的时候会解析出错

  5.重建haffman树之后,读完叶子节点之后需要将pCur重置,归位

          

  6.由于中文所占字节数比普通的英文字符多,因此不能用char,char类型存不下中文字符,因此需要用unsigned char存放

            

  

文件压缩小项目haffman压缩的更多相关文章

  1. 解析Job,bpmn文件的小项目总结

    1.在使用String类中split(String regex)切割字符串abcd.job遇得到job字符串时,直接使用split("."),导致数组超出界限错误 原因:得到的数组 ...

  2. .net对js和css、img剥离项目进行压缩优化、cdn加速

    由于网站首页以及经常用的页面初始化慢,想后面想了对image.js和css进行迁移优化. 1.把他放到独立的域名上面,这个就要对image,js和css从原项目上面脱离,以及把原来很多页面引用的地址修 ...

  3. Python之文件与目录操作及压缩模块(os、shutil、zipfile、tarfile)

    Python中可以用于对文件和目录进行操作的内置模块包括: 模块/函数名称 功能描述 open()函数 文件读取或写入 os.path模块 文件路径操作 os模块 文件和目录简单操作 zipfile模 ...

  4. sencha touch 入门系列 扩展篇之sencha touch 项目打包压缩

    经常有新手同学抱怨说sencha touch的项目加载速度为什么这么慢,经常要10秒左右的时间甚至更多, 大家都知道,sencha touch开发的项目中引用了大量的js文件,当我们打开项目时,st的 ...

  5. [Swift通天遁地]七、数据与安全-(10)文件的加密压缩和解压加密压缩

    ★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★➤微信公众号:山青咏芝(shanqingyongzhi)➤博客园地址:山青咏芝(https://www.cnblogs. ...

  6. 使用c#将多个文件放入文件夹中,并压缩下载

    ZipClass.cs  这个是一个压缩文件的类,可直接复制使用,使用到的命名空间是 using System.IO;using ICSharpCode.SharpZipLib;using ICSha ...

  7. 跨平台的zip文件压缩处理,支持压缩解压文件夹

    根据minizip改写的模块,需要zlib支持 输出的接口: #define RG_ZIP_FILE_REPLACE 0 #define RG_ZIP_FILE_APPEND 1 //压缩文件夹目录, ...

  8. zip4j实现文件压缩与解压缩 & common-compress压缩与解压缩

    有时候需要批量下载文件,所以需要在后台将多个文件压缩之后进行下载. zip4j可以进行目录压缩与文件压缩,同时可以加密压缩. common-compress只压缩文件,没有找到压缩目录的API. 1. ...

  9. Grunt: 拼接代码,js丑化(压缩),css压缩,html压缩,观察文件,拷贝文件,删除文件,压缩文件

    准备工作 grunt 基于nodeJs所以 nodeJs需要的基础配置都需要安装 1.Grunt 安装 npm install -g grunt-cli 这是全局安装 2.在当前文件下npm init ...

随机推荐

  1. 证明抛物线焦点发出的光线经y=ax^2反射后平行于y轴

  2. 普通用户修改.bash_profile 权限问题

    例如oracle用户想要修改它下面的.bash_profile文件: 在命令行运行: [root@localhost ~]# ls -lh /home/oracle/.bash_profile

  3. PHP Libevent扩展安装及应用

    http://www.phpddt.com/php/libevent.html PHP Libevent扩展安装及应用

  4. IntelliJ IDEA 2017版 spring-boot2.0.4的集成JSP

    一.必须依赖四个包,其中三个是springboot自带包,可以不写版本号,有一个不在springboot中,需要设置版本号 <!--引入Spring Boot内嵌的Tomcat对Jsp的解析包- ...

  5. FreeMarker 处理不存在的变量

    FreeMarker不能容忍引用不存在的变量,除非明确地告诉它当变量不存在时如何处理.这里介绍两种典型的处理方法. 一个不存在的变量和一个是null的变量,对于FreeMarker来说是一样的. 处理 ...

  6. #微码分享#C++变参字符串格式化函数format_string

    在C和C++中,变参格式化函数虽然非类型安全,但却十分便利,因为得到广泛使用.对于常见的size_t类型要用“%zu”,ssize_t用”%zd“,int64_t用“% ”PRId64,uint64_ ...

  7. crontab使用环境变量

    两种方式: 1)直接在crontab中定义变量,如: A=123 * * * * * echo $A > /tmp/a.txt 注意在定义变量时不能使用$引用其它变量,如下面的做法错误: A=1 ...

  8. C#控件之:进度条(ProgressBar)

    一.重绘进度条 public class CustomProgressBar:ProgressBar { public CustomProgressBar() { this.SetStyle(Cont ...

  9. centos6.4 安装wireless驱动

    安装完centos6.4之后,目测只有有线的驱动,没有无线驱动. 一.检测网卡 [root@centos ~]# lspci | grep Net :.11b/g LP-PHY (rev ) :) 第 ...

  10. android sqlite 模糊查询

    正确的做法Cursor cursor = sd.rawQuery("select * from contect where QT_CUSTOM like ?", new Strin ...