1.什么是文件压缩?
通过某一个机制让文件变小 并且能够通过某种方式对其进行还原。
2.分类:
无损压缩:解压缩之后的文件与原文件完全相同
有损压缩:解压缩之后的文件与原文件不相同,不能还原和原文件一模一样的模式 ,比如:图片有高清变标清,变不回去。
3.为什么有文件压缩?
1.压缩数据的存储容量,减少存储空间。
2.可以提高数据的传输效率,提高通讯的效率
3.文件压缩也是对文件安全的一种保护方法,增强了数据在传输过程中的安全性。
4.如何实现?
文件中内容在磁盘中以字节流的方式进行存储 ,一个字节有8个比特位,我们可以给每一个字节寻找更短的编码就可以。
我们来举个例子:
文件:ABBBCCCCCDDDDDDD 一共16个字节。
用等长编码:
00 A
01 B
10 C
11 D
每一个字符 都有一个编码
ABBBCCCCCDDDDDDD相当于
00010101 10101010 10111111 11111111
15 AA BF FF
解压缩 :压缩文件+字符编码
还有不等长编码:
100 A
101 B
11 C
0 D
ABBBCCCCCDDDDDDD相当于
10010110 11011111 11111100 00000000
不管等长或者不等长编码,都是将16个字节变成4个字节。
所以现在的问题就是确定一个字符的编码。
5.哈夫曼树
哈夫曼树:
WPL(带权路径长度):从二叉树的根节点到二叉树中所有叶节点的路径长度与相应的权值的乘积为该二叉树的带权路径长度
将带权路径长度最小的树称为哈夫曼树。
哈夫曼树的左子树都是 0 右子树 为 1
找不等长编码:通过哈夫曼树找每个字符的编码
6.创建一个哈夫曼树
1.由给定的n个权值 构造n颗只有根节点的二叉树森林 每颗二叉树Ti只有一个带权值wi的根节点,左右孩子均为空
2.重复一下步骤,直到f中只剩下一棵树为止:
(1)在二叉树森林中选取两个根节点权值最小的二叉树,作为左右子树构造一颗新的二叉树,
新二叉树的权值为其左右子树根节点的权值之和
(2)在二叉树森林中删除这两个树
(3)把新的二叉树加入到二叉树森林中
例如:
根据左0右1:
遍历每条叶子节点的路径得到字符的哈夫曼编码:
A:100 B:101 C:11 D:0
template<class w>
struct htnode
{
htnode(const w&weight)
: _pleft(nullptr)
, _pright(nullptr)
, _parent(nullptr)
, _weight(weight)
{
}
htnode<w>* _pleft;
htnode<w>* _pright;
htnode<w>* _parent;
w _weight;//权值
};
template<class w>
struct compare//比较规则
{
bool operator()(htnode<w>* pleft, htnode<w>* pright)
{
if (pleft->_weight > pright->_weight)//默认是大堆 用的是less(小于) 所以小堆用大于 >
return true;
return false;
}
};
template<class w>
class huffmantree
{
typedef htnode<w> node;
typedef node* pnode;
public:
huffmantree()
:_proot(nullptr)
{
}
~huffmantree()
{
distroy(_proot);
}
void creathuffmantree(const std::vector<w>&v,const w&invalid)
{
if (v.empty())
return;
//创建二叉树森林。 小堆
std::priority_queue<pnode, vector<pnode>, compare<w>> hp;//1.元素类型2.默认放到vector3.比较规则 默认是大堆
for (size_t i = 0; i < v.size(); ++i)
{
if (v[i] != invalid)
{
node* tmp = new node(v[i]);
hp.push(tmp);
}
}
while (hp.size()>1)
{
//从堆中 获取权值最小的两个二叉树
pnode left = hp.top();
hp.pop();
pnode right = hp.top();
hp.pop();
//两个最小的权值之和创建新的节点
pnode parent = new node(left->_weight + right->_weight);
parent->_pleft = left;
left->_parent = parent;
parent->_pright = right;
right->_parent = parent;
hp.push(parent);
}
_proot = hp.top();
}
pnode getroot()
{
return _proot;
}
private:
void distroy(pnode &proot)//二叉树销毁
{
if (proot)
{
distroy(proot->_pleft);
distroy(proot->_pright);
delete proot;
proot = nullptr;
}
}
private:
pnode _proot;
};
7.基于哈夫曼树进行文件压缩
基于哈夫曼树进行文件压缩需要四步:
1.读取原文件,统计原文件中每个字符出现的次数
2.以每个字符出现的次数作为权值,创建哈夫曼树(小堆 可以用优先级队列代替)
由于权值和字符必须一一对应,创建一个结构体:字符 字符出现的次数 字符的编码
3.通过哈夫曼树中每个字符对应的编码
4.用每个字符的新编码重新对原文件进行改写
8.增加头部信息
再压缩完毕后,为了完成解压缩,而且使得解压缩要和原文件保持一致,还需要包括以下信息:源文件的后缀,字符出现的总行数,以及每个字符及其出现的次数,用来在解压缩是创建哈夫曼树。
9.解压缩
解压缩分成四步:
1.从压缩文件中获取原文件的后缀
2.从压缩文件中获取字符次数的总行数
3.从压缩文件中获取每个字符出现的次数
4.重建哈夫曼树
代码及其解释:
hssx.cpp
#include"filec.hpp"
#include"huffman.hpp"
#include<iostream>
#include<cstdlib>
using namespace std;
filec::filec()//对文件信息进行初始化
{
_fileinfo.resize(256);//charinfo
for (size_t i = 0; i < 256; ++i)
{
_fileinfo[i]._ch = i;
_fileinfo[i]._count = 0;
}
}
void filec::yasuofile(const std::string & strfile)
{
//0.读取原文件,统计原文件中每个字符出现的次数
FILE* fin = fopen(strfile.c_str(), "r");
if (fin == nullptr)
{
cout << "打开文件失败" << endl;
return;
}
//统计字符出现的次数:
//unsigned long long count[256] = { 0 };
USCH* pbuff = new USCH[1024];
while (1)
{
size_t ret=fread(pbuff, 1, 1024, fin);//从 文件指针fin中 读取1024个字节放入到ppuff,每次一个字节
if (ret == 0)
break;
for (size_t i = 0; i < ret; i++)
{
_fileinfo[pbuff[i]]._count++;//由于不知道原文件到底有多大 必须加循环
}
}
//1.以每个字符出现的次数作为权值,创建哈夫曼树(小堆 可以用优先级队列代替)
//权值和字符必须一一对应 创建一个结构体:字符 字符出现的次数 字符的编码
huffmantree<charinfo> ht;
ht.creathuffmantree(_fileinfo,charinfo(0));
//2.通过哈夫曼树中每个字符对应的编码
gethuffmancode(ht.getroot());
//3.用每个字符的新编码重新对原文件进行改写
char ch = 0;
int count = 0;
FILE* fout = fopen("2.txt", "w");
assert(fout);
writehead(fout,strfile);
//fin 已经读多了
fseek(fin, 0,SEEK_SET);
while (1)
{
size_t ret = fread(pbuff, 1, 1024, fin);
if (ret == 0)
break;
//用编码改写原文件
for (size_t i = 0; i < ret; i++)
{
string&strcode = _fileinfo[pbuff[i]]._strccode;
for (size_t j = 0; j < strcode.size(); j++)
{
ch <<= 1;
//将每个字符的编码放在ch中,
if (strcode[j] == '1')
ch |= 1;
//0不管
count++;
if (count == 8)
{
fputc(ch, fout);
count = 0;
ch = 0;
}
}
}
}
//最后一次的ch的8个比特位没有填充满,放的时候从后往前放慢8个比特位,读的时候从前往后读四个(假说),
//所以要对没有放满的ch进行移位。
if (count > 0 && count < 8)
{
ch <<= (8 - count);
fputc(ch, fout);
}
delete[]pbuff;
fclose(fin);
fclose(fout);
}
void filec::gethuffmancode(htnode<charinfo>* proot)
{
if (proot == nullptr)
return;
gethuffmancode(proot->_pleft);
gethuffmancode(proot->_pright);
if (nullptr==proot->_pleft&&nullptr==proot->_pright)
{
htnode<charinfo>* pcur = proot;
htnode<charinfo>* pparent = pcur->_parent;
string &strcode = _fileinfo[proot->_weight._ch]._strccode;//ch是有符号的 所以范围是-128-127
while (pparent!=nullptr)
{
if (pcur == pparent->_pleft)
strcode+='0';
else
strcode+='1';
pcur = pparent;
pparent = pcur->_parent;
}
//reverse(逆置)
reverse(strcode.begin(), strcode.end());
}
}
void filec::writehead(FILE* pf, const std::string strfilename)
{
//原文件后缀
string houzhui = strfilename.substr(strfilename.rfind('.'));
//有效编码的行数
//有效字符以及出现的次数
string strcharcount;
size_t sumline = 0;
char sss[256] = { 0 };
memset(sss, '\0', 256);
char szcount[32] = { 0 };
for (size_t i = 0; i < 256; i++)
{
if (_fileinfo[i]._count != 0)
{
strcharcount += _fileinfo[i]._ch;
strcharcount += ',';
_itoa(_fileinfo[i]._count,sss,256);
strcharcount += sss;
//老师所用的转化为 itoa
strcharcount += "\n";
sumline++;
}
}
string strheadinfo;
strheadinfo += houzhui;
strheadinfo += "\n";
strheadinfo += to_string(sumline);
strheadinfo += "\n";
strheadinfo += strcharcount;
fwrite(strheadinfo.c_str(), 1, strheadinfo.size(), pf);
}
void filec::getlines(FILE*pf ,string & strc)
{
while (!feof(pf))//判断文件指针是不是在文件的末尾 不在的话就继续 在就跳出循环
{
char ch = fgetc(pf);
if (ch == '\n')
return;
strc += ch;
}
}
void filec::jieyasuofile(const std::string & strfile)
{
string houzhui = strfile.substr(strfile.rfind('.')+1);
if (houzhui != "txt")
{
cout << "压缩文件的格式不对" << endl;
return;
}
FILE*fin = fopen(strfile.c_str(), "r");
if (fin == nullptr)
{
cout << "压缩文件打开失败" << endl;
return;
}
//获取后缀
houzhui = "";
getlines(fin, houzhui);
//获取字符编码信息
string strc;
getlines(fin, strc);
size_t sumline = atoi(strc.c_str());//先获取多少行
size_t charcount = 0;
for (size_t i = 0; i < sumline; ++i)
{
strc = "";
getlines(fin, strc);
charcount =atoi(strc.c_str() + 2);
_fileinfo[(USCH)strc[0]]._count = charcount;
}
//还原哈夫曼树
huffmantree<charinfo> hufft;
hufft.creathuffmantree(_fileinfo, charinfo(0));
//解压缩
string jyasuoname("3");
jyasuoname += houzhui;
FILE* fout = fopen(jyasuoname.c_str(), "w");
USCH * readbuf = new USCH[1024];
int pos = 7;
htnode<charinfo>* pcur=hufft.getroot();
long long filesize = pcur->_weight._count;
while (1)
{
size_t ret = fread(readbuf, 1, 1024, fin);
if (ret == 0)
break;
for (size_t i = 0; i < ret; i++)
{
//解压缩当前数字的压缩数据
for (size_t j = 0; j < 8; j++)
{
if (readbuf[i] & (1 << pos))//成立 最高为就是1
pcur = pcur->_pright;
else
pcur = pcur->_pleft;
if (pcur->_pright == nullptr&&pcur->_pleft == nullptr)
{
fputc(pcur->_weight._ch, fout);
pcur = hufft.getroot();
filesize--;
if (filesize == 0)
break;
}
pos--;
if (pos < 0)
{
pos = 7;
break;
}
}
}
}
delete[]readbuf;
fclose (fout);
fclose(fin);
}
filc.hpp
#pragma once
#include"huffman.hpp"
#include<iostream>
#include<string>
#include<vector>
#include<assert.h>
using namespace std;
typedef unsigned char USCH;
struct charinfo
{
charinfo(unsigned long long count=0)
:_count(count)
{
}
USCH _ch;//字符
unsigned long long _count;//字符出现的次数
std::string _strccode;//字符的编码
charinfo operator+(const charinfo& info)
{
return charinfo(_count + info._count);
}
bool operator >(const charinfo& info)
{
return _count > info._count;
}
bool operator !=(const charinfo& info)const
{
return _count != info._count;
}
};
class filec
{
public:
filec();
void yasuofile(const std::string & strfile);
void jieyasuofile(const std::string & strfile);
private:
void gethuffmancode(htnode<charinfo>* proot);//利用哈夫曼树得到每一个字符的编码
void writehead(FILE* pf,const std::string strfilename);//写一下头部的信息
void getlines(FILE*pf, std::string & strc);
private:
std::vector<charinfo> _fileinfo;
};
huffman.hpp
#pragma once
#include<vector>
#include<queue>
template<class w>
struct htnode
{
htnode(const w&weight)
: _pleft(nullptr)
, _pright(nullptr)
, _parent(nullptr)
, _weight(weight)
{
}
htnode<w>* _pleft;
htnode<w>* _pright;
htnode<w>* _parent;
w _weight;//权值
};
template<class w>
struct compare//比较规则 仿函数
{
bool operator()(htnode<w>* pleft, htnode<w>* pright)
{
if (pleft->_weight > pright->_weight)//默认是大堆 用的是less(小于) 所以小堆用大于 >=
return true;
return false;
}
};
template<class w>
class huffmantree
{
typedef htnode<w> node;
typedef node* pnode;
public:
huffmantree()
:_proot(nullptr)
{
}
~huffmantree()
{
distroy(_proot);
}
void creathuffmantree(const std::vector<w>&v,const w&invalid)
{
if (v.empty())
return;
//创建二叉树森林。 小堆
std::priority_queue<pnode, vector<pnode>, compare<w>> hp;//1.元素类型2.默认放到vector3.比较规则 默认是大堆
for (size_t i = 0; i < v.size(); ++i)
{
if (v[i] != invalid)
{
node* tmp = new node(v[i]);
hp.push(tmp);
}
}
while (hp.size()>1)
{
//从堆中 获取权值最小的两个二叉树
pnode left = hp.top();
hp.pop();
pnode right = hp.top();
hp.pop();
//两个最小的权值之和创建新的节点
pnode parent = new node(left->_weight + right->_weight);
parent->_pleft = left;
left->_parent = parent;
parent->_pright = right;
right->_parent = parent;
hp.push(parent);
}
_proot = hp.top();
}
pnode getroot()
{
return _proot;
}
private:
void distroy(pnode &proot)//二叉树销毁
{
if (proot)
{
distroy(proot->_pleft);
distroy(proot->_pright);
delete proot;
proot = nullptr;
}
}
private:
pnode _proot;
};
wenjianyasuo.cpp
#include"filec.hpp"
#include"huffman.hpp"
int main()
{
filec fc;
fc.yasuofile("1.txt");
fc.jieyasuofile("2.txt");
return 0;
}