GZIP压缩文件实现
做此项目需要掌握的知识
C:文件操作相关,例如:文件指针、打开文件、写入文件等
C++:模板、类、vector、string、C++11仿函数
数据结构:Huffman树、priority_queue(优先级队列)、堆
项目进行环境
vs2019 + Windows10
为什么要进行文件压缩?
目的是让文件变小,在传输和存储时都可以减少占用。
原理
此项目是基于LZ77和Huffman编码的文件压缩,LZ77算法(下面详细讲)是将重复的字段替换成<长度、距离>对,从而达到减少字符所占的空间,LZ77算法的最大问题时编码和字符不好区分,而且小于等于2字节的字符不值得用<长度、距离>对替换,此时利用Huffman树得到的编码,可以解决LZ77的缺陷,项目中的实现方式是:统计所有字符的出现次数,将出现次数越多的放在树靠上的位置,编码也就越短,反之放在树靠下的位置,编码越长。这样的编码在压缩时大多数情况会比等长编码更优,占用空间会更少。
压缩的时候首要根据每个字符出现的次数构造N个节点的二叉树森林,根据Huffman树的特点(下面详细讲)将森林构建成Huffman树,此时也就能得到Huffman编码,我们要存储这个编码,方便解压,同时还要保存源文件的格式、编码所占用的行数、编码后的数据。
LZ77原型(**<长度、距离>**对)
将重复出现的内容 替换成更短的<长度,距离>对
对重复出现的文字压缩
mnoabczxyuvwabc123456abczxydefgh
用**<长度、距离>**对的方式来替换
长度:重复文件所占的字节
距离:后文中重复出现词语首字节 与 前文中的重复词语的 字节差
压缩:monabczxyuvw==(3,9)123456(6,18)==defgh (真实存储时没有括号和逗号,这里是方便观看)
解压:用长度距离 去替换回来
缺点:
-
无法区分字符是替换后的编码还是原有的字符
-
字符<= 2字节 时 不值得去替换,因为替换完还是占用2字节
编码替换
根据LZ77的缺陷,我们想到了编码替换
等长编码
一个字节是8比特位
给每个字节找一个小于8比特位的编码,来替换文件中的字节。
文件:DDAB DDBC DBCD CDCC (16字节)
编码:A:00 B:01 C:10 D:11
压缩:11110001 11110110 11011011 10111010 (4字节)
解压:DDAB DDBC DBCD CDCC (查表解压)
优化:让出现次数少的编码长一点,让出现次数多的编码少一些
动态不等长编码
文件:DDAB DDBC DBCD CDCC DDD (19字节)
编码:A:100 B:101 C:11 D:0
解压:00100101 00101110 10111011 01111000 (4字节)
编码:此时编码从何而来? —— Huffman树
Huffman树
特点
带权路径长度:二叉树的根节点到二叉树所有叶子节点的路径长度与相应权值乘积之和,
而huffman树的带权路径是最小的。
发现:权值大的靠近根,带权路径就越小。
如何创建Huffman树
先创建N个数的森林 例如:10 5 3 1
先在森林中获取两个权值最小的两个树 3、1 新节点的权值是这两个权值之和
将新的二叉树放回二叉树森林中 10 5 4
4 、5 = 9
9 、10 = 19
此时森林创建完成
Huffman编码如何获得
编码:从根节点到叶子经过的边。
假设:左边为0,右边为1,那么遍历边到叶子,也就得到了这个字符的编码
权值:字符对应节点内的数字是该节点的权值,也是该字符在文件中出现的次数
代码实现:压缩
文件
Common.h
:放头文件的 头文件
FileComperssHuffman.h
:定义Huffman压缩文件的 头文件
HuffmanTree.hpp
:定义和实现Huffman树
FileComperssHuffman.cpp
:实现Huffman压缩
UaenaZioTest.cpp
:main函数执行文件
1.实现Huffman树
a.定义树的每一个节点
首先要定义树的每个节点,节点中要记录(左孩子、右孩子、双亲、权值(出现的次数))
HuffmanTree.hpp
#pragma once
#include <iostream>
#include <vector>
#include <queue>
template<class W>
struct HuffmanTreeNode
{
HuffmanTreeNode<W>* _left;
HuffmanTreeNode<W>* _right;
HuffmanTreeNode<W>* _parent;
W _weight;
HuffmanTreeNode(const W& weight = W())
:_left(nullptr)
,_right(nullptr)
,_parent(nullptr)
,_weight(weight)
{}
};
构造函数 weight
给匿名对象