刻意练习-哈夫曼编码简单压缩一个文件

一、文件是如何被压缩的?

  • 现在假设有这样一个文件,文件内容如下:

    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来存储。整体占用的空间就会下降。
  • 对数据流操作,就是不断的能够记录当前数据的位置,下一次读取或者写入的时候能够找到这个位置。 就仿佛没有间断,也就形成了流。

六、参考

  • 哈夫曼编码
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值