- 整体思路:
- 读原文件,获得权值列表,生成哈夫曼树,遍历树生成哈夫曼编码。
- 写入压缩文件,后缀.huf,先写入文件头(包括文件类型,权值列表,文件长度,以便于从压缩文件中再次生成哈夫曼编码来解压,也可以用其他的方式,比如直接将哈夫曼编码以先序遍历的方式写入压缩文件的开始,代码中有体现)。
- 将原文件编码后写入压缩文件。
- 读取压缩文件,生成哈夫曼树,每次读取到0则向左子树移动,读取到1向右子树移动,直到遇到叶子结点,将叶子结点存储的编码写入解压文件。
- 值得注意的的是,权值数组的运作。图片文件按二进制读取,一个字节一个字节读,那么每个字节有8位二进制,则用unsigned char读取,最多只会有0~255种可能的值,即权值数组的大小设成256。
- 数据结构并没有使用真正意义上的树,即包括指向结点的指针,而使用一个结点数组来保存树,结点中存储的左右孩子和双亲都是数组下标。~
然而这样非常难用,在需要遍历树的地方代码异常恶心~,同时哈夫曼树的一个性质是总结点数 = 叶子结点数 * 2 - 1,这样得到了开辟的存储树的数组大小。 - 其中用哈夫曼编码将原文件编码时,不能直接将编码(0101…)按1个char(即一个字节的大小)存1位编码来写入压缩文件,否则文件反而会变更大,而是用1个char的8位(即1byte中的8bits)来存8位的编码。在代码中有BitIO.h专门定义了做这些位运算的结构和函数,也有HuffmanTree.h中定义了Str2Byte函数,也有#define定义的GET_BYTE等函数。这三处功能是基本相同的,实现各有不同,这里我使用的是较简单的Str2Byte。
代码的注释比较详细
BitIO.h
#ifndef HUFFMANCOMPRESSCPRO1_BITIO_H
#define HUFFMANCOMPRESSCPRO1_BITIO_H
#include <iostream>
#define BITBUFFSIZE 1024
#define SHIFT 3
struct BIT{
char b[BITBUFFSIZE];//位数组 bit数组
int p; //指示数组填到哪一位的下一位
};
//向位数组栈顶推入一位
bool pushBit(BIT *buffer, const bool istrue);
//从文件加载多位
int fPushBit(BIT* buffer, FILE* fp);
//修改position位置的一位
int changeBit(BIT* buffer, const int istrue, const int position);
//读取一位
int readBit(BIT* buffer, const int position);
//栈顶弹出一位
int popBit(BIT* buffer);
#endif //HUFFMANCOMPRESSCPRO1_BITIO_H
BitIO.cpp
#include "BitIO.h"
bool pushBit(BIT *buffer, const bool istrue){
if(buffer->p >= BITBUFFSIZE * 8)
return EOF;
else if(istrue)
buffer->b[buffer->p >> SHIFT] |= 128u >> buffer -> p % 8; //p所指位置填1
else
buffer->b[buffer->p >> SHIFT] &= ~(128u >> buffer -> p % 8); //p所指位置填0
buffer->p++;
return istrue;
}
int fPushBit(BIT* buffer, FILE* fp){
memset(buffer, 0, sizeof(BIT));
if(buffer -> p = fread(buffer->b, sizeof(char), BITBUFFSIZE, fp) && feof(fp)){
buffer->p = (buffer->p - 2) * 8 + buffer -> b[buffer -> p - 1] + 1;
} else
buffer -> p *= 8;
return buffer -> p;
}
int changeBit(BIT* buffer, const int istrue, const int position){
if(position >= buffer -> p)
return EOF;
else if (istrue)
buffer -> b[position >> SHIFT] |= 128u >> position % 8;
else
buffer -> b[position >> SHIFT] &= ~(128u >> position % 8);
return istrue;
}
int readBit(BIT* buffer, const int position){
if(position >= buffer -> p)
return EOF;
else
return buffer->b[position >> SHIFT] & (128u >> position % 8);
}
int popBit(BIT* buffer){
if(buffer -> p >= BITBUFFSIZE || buffer -> p < 0)
return EOF;
buffer->p--;
return buffer->b[(buffer->p + 1) >> SHIFT] & (128u >> (buffer->p + 1) % 8);
}
HuffmanTree.h
#ifndef HUFFMANCOMPRESSCPRO1_HUFFMANTREE_H
#define HUFFMANCOMPRESSCPRO1_HUFFMANTREE_H
#include <string>
#include <iostream>
#include "BitIO.h"
#define SIZE 256
#define NODES 2*SIZE - 1
//各种各样的编码方法
//取出index位,若取出的index位为0,则GET_BYTE值为假,否则为真
#define GET_BYTE(vByte, index) (((vByte) & (1 << ((index) ^ 7))) != 0)
//把index位设置为‘1’
#define SET_BYTE(vbyte, index) ((vbyte) |= (1 << ((index) ^ 7)))
//把index位设置为‘0’
#define CLR_BYTE(vbyte, index) ((vbyte) &= (~(1 << ((index) ^ 7))))
//n个叶子结点的哈夫曼树共有2n-1个结点
//一个字节8位 读取文件的char可以从0-255 用weight[]数组记录每个char出现的频率
//HuffmanTree 保存在HTNode[2n-1]的数组中
struct Node{
bool isLeaf();
int ch = 0;
int weight = 0;
int lChild = 0, rChild = 0, parent = 0;
};
typedef Node HTNode, *HuffTree;
struct HEAD{
char type