一、文件是如何被压缩的?
-
现在假设有这样一个文件,文件内容如下:
damainnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnaaaaaaaaaaaaaaaaaaaaaaaaaayikunnnnnnnnnnnnnnnaaaaaaaaaa
-
现在这个文件总共是100个字节
-
现在文件里面的内容是一个字符占用一个字节(bytes),8个bit。如果我有办法能够用更少的bit来表示这个文件中的内容,就能够实现文件的压缩。
-
所以,我先放弃ascii 编码,使用另外一个表来编码这个文件。 这个表怎么来的呢?看这里。
haffuman_map haffuman_table[]= { {'n',0b1,1}, {'a',0b01,2}, {'d',0b00011,5}, {'i',0b0010,4}, {'y',0b0000,4}, {'u',0b00010,5}, {'m',0b00110,5}, {'k',0b00111,5}, };
-
这里可以看到,n只需要一个bit就能表示。文件中的n出现了55次,之前就是需要55个字节,现在只需要55/8 不到7个字节就表示成功了。文件占的空间少了,也就是被压缩了。
-
理论上,只要我的这个表非常的丰富,包含了所有的字符,并且高频出现的采用更少的字节存储,那么,就能压缩所有的字符文件。核心其实是这个表。
二、抽象
-
压缩和解压
//把from 文件 压缩成 to 文件。 int compress_to_file(char *from_file,char *to_file); //把 from 的压缩文件,解压缩成to 文件。 int uncompress_to_file(char *from_file,char *to_file);
-
对bit的操作。因为不再是对一个个字节的操作,而是涉及到一个个的读取bit,一个个的写入bit。所以抽象了一个流的结构体操作bit。
typedef struct bit_stream { /*当前读取或者写入的字节指针*/ char *p; /*当前这个字节写入第几个bit了*/ int locate; /*最后总共写了多少个bit*/ int bit_len; /*用来存放流数据的buffer.*/ char *buff; /*用来存放流数据的buffer大小*/ int buff_size_t; }bit_stream;
-
文件和buffer的数据交互。需要从文件中读取数据到buffer,把buffer写入到文件中。
/*把文件读取到buffer中*/ int read_file_to_buffer(char *file_name,char *buff,int buff_len); /*把buffer 写入到文件中*/ int write_buff_to_file(char *file_name,char *buff,int buff_len);
三、实现
-
加密实现
-
解密实现
四、整理
-
完整代码
#include<stdio.h> #include<string.h> typedef struct haffuman_map { char src_chr; char dst_chr; int bit_size; }haffuman_map; typedef struct bit_stream { int buff_size_t; char *p; int locate; int bit_len; char *buff; }bit_stream; #define RENYJ_TRACE( format, args... ) do {fprintf(stderr, "RENYJ_TRACE >>> %s->%s()->line.%d : " format "\n", __FILE__, __FUNCTION__, __LINE__, ##args);}while(0) haffuman_map haffuman_table[]= { {'n',0b1,1}, {'a',0b01,2}, {'d',0b00011,5}, {'i',0b0010,4}, {'y',0b0000,4}, {'u',0b00010,5}, {'m',0b00110,5}, {'k',0b00111,5}, }; char bit_table[] = {0x80,0x40,0x20,0x10,0x08,0x04,0x02,0x01}; int bit_stream_construct(bit_stream *p_stream,char *buffer,int buffer_size) { if(p_stream == NULL) { return 0; } p_stream->buff = buffer; p_stream->buff_size_t = buffer_size; p_stream->p = buffer; p_stream->locate = 0; p_stream->bit_len = 0; return 0; } int read_bit(bit_stream *p_stream) { if(p_stream == NULL) { return -1; } if((p_stream->p - p_stream->buff )*8 + p_stream->locate >= p_stream->bit_len) { return -1; } int bit_value = ((*(p_stream->p) & bit_table[p_stream->locate])); p_stream->locate++; if(p_stream->locate%8 == 0) { p_stream->locate = 0; p_stream->p++; } return (bit_value && 1); } int write_bit(bit_stream *p_stream,int bit) { if(p_stream == NULL) { return -1; } if(bit) { *(p_stream->p) |= bit_table[p_stream->locate]; } p_stream->bit_len++; p_stream->locate++; if(p_stream->locate%8 == 0) { p_stream->locate = 0; p_stream->p++; } return 0; } haffuman_map *encode_char(char ch) { int i = 0; for(i = 0; i < sizeof(haffuman_table)/sizeof(haffuman_table[0]); i++) { if(haffuman_table[i].src_chr == ch) { return &haffuman_table[i]; } } return 0; } int decode_char(char ch,char *ori_chr) { int i = 0; for(i = 0; i < sizeof(haffuman_table)/sizeof(haffuman_table[0]); i++) { if(haffuman_table[i].dst_chr == ch) { *ori_chr = haffuman_table[i].src_chr; return 0; } } return -1; } char locate_valid_start_bit(haffuman_map *cur_en_code) { if(cur_en_code == NULL) { return -1; } int i = 0; for(i = 0;i < 7;i++) { if(i == (8-cur_en_code->bit_size)) { break; } } return i; } int write_en_code_to_stream(bit_stream *p_stream,haffuman_map *cur_en_code) { char valid_start_bit = 0; int i = 0; /*找到有效的bit起始位置*/ valid_start_bit = locate_valid_start_bit(cur_en_code); /*依次写入*/ for(i = valid_start_bit ; i < 8; i++) { write_bit(p_stream,cur_en_code->dst_chr & bit_table[i]); } return 0; } int write_en_code_stream_to_file(bit_stream *p_stream,char *file_name) { /*需要把数据流的信息写入到文件中去*/ FILE *fp = fopen(file_name,"wb"); if(fp == NULL) { return -1; } fwrite(&(p_stream->bit_len),4,1,fp); fwrite(p_stream->buff,1,p_stream->bit_len/8+1,fp); fclose(fp); return 0; } int read_en_code_stream_from_file(char *file_name,bit_stream *p_stream) { /*先读取数据流的信息*/ FILE *fp = fopen(file_name,"rb"); if(fp == NULL) { return -1; } fread(&(p_stream->bit_len),4,1,fp); fread(p_stream->buff,1,p_stream->bit_len/8+1,fp); fclose(fp); return 0; } int encode_buffer_to_stream(char *src_string,int src_len,bit_stream *p_stream) { if(p_stream == NULL) { return -1; } char i = 0; char en_code = 0; haffuman_map *cur_encode_map = NULL; for(i = 0; i < src_len; i++) { cur_encode_map = encode_char(src_string[i]); /*将加密后的数据写入新buffer 组成的流中*/ write_en_code_to_stream(p_stream,cur_encode_map); } return 0; } int decode_stream_to_buff(char *buffer,int buff_len,bit_stream *p_stream) { if(buffer == NULL || p_stream == NULL) { return -1; } /*初始化流的位置,从头开始读取*/ p_stream->locate = 0; p_stream->p = p_stream->buff; char en_code = 0; char ori_chr = 0; int bit_value = 0; int ret = -1; int ori_index = 0; /*依次读取所有的bit*/ while((bit_value = read_bit(p_stream)) != -1) { en_code = (en_code << 1)+bit_value; /*如果成功解码,从下一个bit开始,组装新的字符*/ ret = decode_char(en_code,&ori_chr); if(ret != -1) { en_code = 0; buffer[ori_index] = ori_chr; ori_index++; } } return 0; } int read_file_to_buffer(char *file_name,char *buff,int buff_len) { if(file_name == NULL) { return -1; } FILE *fp = fopen(file_name,"rb"); if(fp == NULL) { return -1; } int act_len = fread(buff,1,buff_len,fp); fclose(fp); return act_len; } int write_buff_to_file(char *file_name,char *buff,int buff_len) { if(file_name == NULL) { return -1; } /*需要把数据流的信息写入到文件中去*/ FILE *fp = fopen(file_name,"wb"); if(fp == NULL) { return -1; } int act_len = fwrite(buff,1,buff_len,fp); fclose(fp); return act_len; } int compress_to_file(char *from_file,char *to_file) { if(from_file == NULL || to_file == NULL) { return -1; } /*压缩*/ bit_stream write_stream; bit_stream *p_stream = &write_stream; memset(p_stream,0x00,sizeof(bit_stream)); /*存放原始数据*/ char buffer[1024] = {0}; /*用来存放流中的buffer*/ char stream_buff[1024] = {0}; /*构建一个流*/ bit_stream_construct(p_stream,stream_buff,sizeof(stream_buff)-1); /*从原始数据中读取,放到buffer中*/ int act_len = read_file_to_buffer(from_file,buffer,sizeof(buffer)-1); RENYJ_TRACE("act_len=%d,buffer=%s",act_len,buffer); /*将 buffer 加密到 stream 中*/ encode_buffer_to_stream(buffer,act_len,p_stream); /*将加密后的stream 中数据写入文件*/ write_en_code_stream_to_file(p_stream,to_file); return 0; } int uncompress_to_file(char *from_file,char *to_file) { if(to_file == NULL) { return -1; } /*解压缩*/ bit_stream read_stream; bit_stream *p_stream = &read_stream; memset(p_stream,0x00,sizeof(bit_stream)); /*用来存放加密buffer的*/ char stream_buff[1024] = {0}; /*用来存放原始buffer的*/ char buffer[1024] = {0}; /*构建一个流*/ bit_stream_construct(p_stream,stream_buff,sizeof(stream_buff)-1); /*加密文件读取到 stream 中*/ read_en_code_stream_from_file(from_file,&read_stream); /*加压到 buffer 中*/ decode_stream_to_buff(buffer,sizeof(buffer)-1,p_stream); /*写入指定的文件中*/ write_buff_to_file(to_file,buffer,strlen(buffer)); return 0; } int main(int argc, const char *argv[]) { char *src_string = "damainnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnaaaaaaaaaaaaaaaaaaaaaaaaaayikunnnnnnnnnnnnnnnaaaaaaaaaa"; int str_len = strlen(src_string); char *from_file = "./hello"; write_buff_to_file(from_file,src_string,str_len); compress_to_file(from_file,"./compress_hello"); uncompress_to_file("compress_hello","uncompress_hello"); return 0; }
-
从理论上讲,如果我的表足够的丰富和准确,我就能压缩和解压所有的文件。
-
运行结果
可见,压缩文件之前有100个字节,压缩之后,有25个字节,解压缩之后,100个字节。
五、方法记忆
- 压缩的本质是换一种编解码的方法。 将高频出现的内容采用更少的bit来存储,低频出现的内容采用较多的bit来存储。整体占用的空间就会下降。
- 对数据流操作,就是不断的能够记录当前数据的位置,下一次读取或者写入的时候能够找到这个位置。 就仿佛没有间断,也就形成了流。
六、参考
- 哈夫曼编码