CompressIt
结构
压缩软件的核心在于压缩算法。基于Huffman编码的压缩算法思路:
- 以二进制方式读取源文件,按照每8bits作为一个字符;
- 统计每个字符的出现频率即为叶子结点的权值,按照Huffman算法得到每个叶子的编码;
- 对源文件的每个字符,将新的编码组合为二进制流,按照每8bits一个单位写入压缩文件。
举例来看:
假设我们有待压缩源文件hello,h的ASCII码为01101000,同理可得整个文件的二进制形式0110100001100101011011000110110001101111,共5B,40bits。
根据Huffman算法:得到h的编码为00,同理可得整个文件的Huffman编码为0001111110,末尾不够8bits,采用补0的方法可得0001111110000000,按照每8bits一个单位,写入压缩文件的是31和255对应的字符,共2B,16bits。
解压缩流程是压缩的逆过程:
- 以二进制方式读取压缩文件;
- 每次取1bit,从Huffman树的根结点出发,找到某个叶子即为源字符。
效果
做一个简单的比较:
| 压缩软件 | 测试文件 | 压缩率 | 测试文件 | 压缩率 |
|---|---|---|---|---|
| CompressIt | txt(840B) | 70.4% | png(282KB) | 101% |
| WinRaR | txt(840B) | 14.4% | png(282KB) | 100% |
压缩率和压缩时间和专业软件没法比。之所以出现压缩文件大于源文件,是因为压缩文件中还存储了Huffman树等信息,为解压所需。
对于不同内容的文件,得到的压缩文件大小也不尽相同,这主要与Huffman编码的性质有关。
理论分析
Huffman编码依赖于信源的统计特征,其背后的原理在于为出现频率高的字符分配尽可能短的码长,这样就可以降低平均码长:
\]
使得\(L\)最短的编码就是最优编码,可以证明Huffman编码是一种最优编码。
同时Huffman编码还是前缀码,简化了解码过程。
假设一种理想情况:源文件长\(len\)很大,共有\(m\)种不同字符,每个字符用8bits表示,并且每种字符出现频率\(\frac{len}{m}\)相同,忽略掉存储Huffman树等信息所需的空间。
这棵完全二叉树共有结点\(n=2*m-1\)个,那么树深度为\(h=1+\lfloor log_2n \rfloor\),每个字符的压缩长度为\(h-1=\lfloor log_2n \rfloor\),故压缩后的串长度为\(\frac{(h-1)*len}{8}\),可得压缩率\(\frac{h-1}{8}\),即:
\]
源文件中不同字符种类\(m\)越小,即源文件分布越集中,压缩效果越好。
如果和定长编码比较,可以得到压缩率:
\]
\(m\)取值256时,Huffman树是一棵满二叉树,压缩率为100%,并不比8位固定长度编码更高效。
收获
EOF和feof()
EOF是一个定义在cstdio头文件中的宏,一般为-1:
#define EOF (-1)
但是如果按照二进制读取文件,对于文件中的-1又该如何处理?
阮一峰的博客说:
在Linux系统之中,EOF根本不是一个字符,而是当系统读取到文件结尾,所返回的一个信号值(也就是-1)。至于系统怎么知道文件的结尾,资料上说是通过比较文件的长度。
我们通常会写出下面程序来读取文件:
int ch;
while ((ch = fgetc(fp)) != EOF) {
// your code here
}
但是fgetc()在到达文件结尾和发生读取错误的情况下都会返回EOF,所以上述代码不严谨,采用feof()函数来判断文件结尾:
int ch;
while (!feof(fp)) {
ch = fgetc(fp);
// your code here
}
但是采用feof()也有一个问题:读取最后一个字符后,feof()仍然返回0,进入循环,fgetc()再向后读取一个字符,feof()才返回1,这样程序会多循环一次。
所以比较安全的写法是:
int ch = fgetc(fp);
while (ch != EOF) {
// your code here
ch = fgetc(fp);
}
if (feof(fp))
puts("End-of-File reached.");
else
puts("Something went wrong.");
- 虚析构函数
基类的析构函数一般写成虚函数,做个测试:
class base {
public:
base() {};
virtual ~base() {
cout << "destructor in base" << endl;
};
virtual void f() {
cout << "f in base" << endl;
}
};
class derive :public base {
public:
derive() {};
~derive() {
cout << "destructor in derive" << endl;
};
void f() {
cout << "f in derive" << endl;
}
};
base* p = new derive;
p->f();
delete p;
输出:
f in derive
destructor in derive
destructor in base
如果基类的析构函数不是虚函数,输出:
f in derive
destructor in base
结果并没有调用派生类的析构函数,造成内存泄漏。
所以基类的虚析构函数的作用是:当一个基类指针删除一个派生类对象,确保调用派生类的析构函数。
- 二进制文件
在压缩过程中,对于不同格式源文件的读取都是采用二进制方式rb。
实际上二进制文件和文本文件并没有本质区别,你所看到的内容取决于打开文件的软件对二进制流的解释方式,文件扩展名帮助计算机知道应该用哪种解释方式,通常的文本文件的解释方式有ASCII码和Unicode码。
CompressIt的更多相关文章
- 使用zlib来压缩文件-用delphi描述
今天用到压缩文件的问题,找了一些网上的资料,后来发现了delphi自身所带的zlib单元,根据例子稍微改变了一些,使它能够符合所有的格式. 使用时,需要Zlib.pas和 Zlibconst.pas两 ...
- delphi 压缩
DELPHI 通过ZLib来压缩文件夹 unit Unit1; interface uses ZLib, Windows, Messages, SysUtils, Variants, Classes, ...
随机推荐
- (29)ASP.NET Core3.1 Swagger(OpenAPI)
1.什么是Swagger/OpenAPI? Swagger是一个与语言无关的规范,用于描述REST API.因为Swagger项目已捐赠给OpenAPI计划,所以也叫OpenAPI.它允许计算机和人员 ...
- Vue-cli2.0 第3节 解读Vue-cli模板
Vue-cli2.0 第3节 解读Vue-cli模板 目录 Vue-cli2.0 第3节 解读Vue-cli模板 第3节 解读Vue-cli模板 1. npm run build命令 2. main. ...
- 刨根问底系列(1)——虚假唤醒(spurious wakeups)的原因以及在pthread_cond_wait、pthread_cond_singal中使用while的必要性
刨根问底之虚假唤醒 1. 概要 将会以下方式展开介绍: 什么是虚假唤醒 什么原因会导致虚假唤醒(两种原因) 为什么系统内核不从根本上解决虚假唤醒这个"bug"(两个原因) 开发者如 ...
- developerWorks 中文社区
https://www.ibm.com/developerworks/community/groups/service/html/communityview?communityUuid=3302cc3 ...
- 007-函数-C语言笔记
007-函数-C语言笔记 学习目标 1.[了解]函数的分类 2.[掌握]函数的声明定义和调用 3.[掌握]函数的形参和实参 4.[掌握]带返回值的函数 5.[掌握]全局变量和局部变量 6.[了解]注释 ...
- AJ学IOS 之微博项目实战(13)发送微博调用相机里面的图片以及调用相机
AJ分享,必须精品 一:效果 二:代码 相机部分就简单多了,几行代码调用而已,但是如果你要是想实现更多丰富的功能,需要自己写.利用AssetsLibrary.framework,利用这个框架可以获得手 ...
- C# 基础知识系列- 10 反射和泛型(二)
0. 前言 这篇文章延续<C# 基础知识系列- 5 反射和泛型>,继续介绍C#在反射所开发的功能和做的努力.上一篇文章大概介绍了一下泛型和反射的一些基本内容,主要是通过获取对象的类型,然后 ...
- Daily Scrum 1/18/2016
Yandong & Zhaoyang: Prepare bug bash slides for Beta release; Dong & Fuchen:Prepare demo for ...
- 小L的直线
小学时期的小L发现自己很有艺术细胞,于是买了一块画板,但是他的绘画水平使得他只能连接两点画出一条线段.有一天他决定在一张有n个点的图上作画,即他可以把这n个点任意连接.大家认为平行线是非常不美观的,于 ...
- 01、WireShark——ARP 协议包分析
1. 什么是ARP ARP(Address Resolution Protocol)协议,即地址解析协议.该协议的功能就是将 IP 地 址解析成 MAC 地址. ARP(Address Resolu ...