读功能:将需要保存的编码(v)保存到结构体中(压缩的最后一步)
写功能:根据保存到结构体的压缩流,读取 所需要的位大小 的编码(解压的第一步。)
(流是个抽象的概念,是对输入输出设备的抽象:https://blog.csdn.net/hansnowqiang/article/details/50130437)
先按压缩格式,获得32位每个值对应的压缩编码。
每次以32位读取要压缩的文件,并将其转化成其对应的压缩编码并保存到变量:u32 v 中。
通过位流读写器的put_bits()函数 ,将生成的编码保存到其中的结构体中。
之后可以写入文件,得到压缩文件。
如果想将这一段压缩流解压,或者读取压缩文件获得压缩流
通过位流读写器的 get_bits()函数 ,从结构体中获得想要的位数大小的编码
之后将其与编码格式匹配,再转化,再写入文件,完成解压。
通过统计和计算,得到压缩编码(例如):
针对占4位的值:
符号 值 (十进制) 值(二进制) 压缩编码 位数
A 0 0000 0 1
B 2 0010 10 2
C 6 0110 110 3
D 7 1110 1110 3
(红字是位流读写器的功能)要完成的操作:将内容为"ABACAADB"的文件(不是字符串,而是根据每4位二进制值不同随便命名得符号,比写二进制方便)读取并转化为压缩流:0 10 0 110 0 0 111 10 (空格只是为了便于查看),
其中每转化一次(比如将A(0000)转化成0后,),都调用put_bits(bit_t* s, u32 v, s32 n)来保存0到结构体中,且这一次v=0,n=1。
以下代码完全来自老师。
其中u32为unsigned 32;
typedef struct bit_t
{
u32 val; // 当前读写器的值
s32 bit; // 当前读写器的位数
s32 size; // 已读写的字节数
s32 pos; // 读写位置
u8* ptr;
u32 strm[128*1024]; // 可最好动态分配, 但要用realloc
} bit_t;
注意这个结构体不是链表。
1. u32 val; // 当前读写器的值
s32 bit; // 当前读写器的位数
把每次写操作抽象成添加到一个32位读写容器中,这个容器的bit只要在写完后>16位,就将前16位写入数组u32 strm[128*1024];
如何写入呢?见代码2.d
注意位流读写器32位,从左向右写入。(这是为了下面操作方便而做的规定)
2.u32 strm[128*1024];为声明空间的数组。而写入或者读取则通过ptr指针来完成。详情见下文:代码2.d
注意编码的写入是通过操做指针ptr写入的
1:为什么每次以32位读取呢?
对于读取,每次处理的位数越大,速度越快。(一次读取并处理32位,要比读4次读8位快)
而现在的计算机很多32位的,64位的也能兼容32位的。
2:u8* ptr;代表?
因为内存的基本单位为1字节即8位,所以用u8来声明。
通过让指针指向strm数组 存储数据的结束位置,方便以后来完成对数据的写入,所以ptr-strm就是整个流的位数,
1.进行读取时,初始化数据结构操作
a.s->ptr = (u8*)s->strm; ptr指针指向数组的开始位置,因为为空数组,也是存储元素的结束位置。
void begin_put_bits(bit_t* s)
{
//memset(s, 0, sizeof(*s));
s->val = 0;
s->bit = 0;
s->pos = 0;
s->size = 0;
s->ptr = (u8*)s->strm;
}
2.将已经读取的编码V存储到结构体 *s中
a:为什么形参中结构体是指针:(bit_t* s, )
https://blog.csdn.net/lin37985/article/details/38582027
b:为什么v需要左右移动?(v = v << (32 - n) >> s->bit;)
v为32位大小的编码,但是有效位数为n位。而其他位不一定是0(老师这么讲的,可能和浮点数表示或者转化编码时的操作有关,以防万一吧),所以先左移(32-n)为到最左边,让其他位为0。然后需要将其加入当前读写容器中。当前读写容器的位数为s->bit,要将编码加在后面,所以需要右移s->bit,编码位置调整好。然后s->val与v进行或运算,完成添加。
c:为什么当前读写容器的位数大于16位时需要右移16位,然后再将其保存到数组strm中?(if (new_bit < 16))
首先:要存储的内容必须是存储基本单位8位 的整数倍大小。
其次:编码v的位数为0到16位;(原因见代码3.a),而 s->val的位数为32位,如果s->bit>16位时,不进行左移,那么再加上编码v的位数,会超过32位,赋值给s->val时会出现溢位。
d:*((u16*)s->ptr) = s->val >> 16;的讲解:
一层一层的分析:*p = x表示将x的值赋给指针p指向的内存单元,即将 s->val >> 16的内容赋值给(u16*)s->ptr所指向的内存单元。而(u16*)s->ptr则是将s->ptr(指向8位类型的指针)强制类型转化为(指向16位类型的指针),即s->ptr所指向的空间向下扩大为16位。(注意不是指针变量自己大小的变化)。
上面说了当前位流读写器写入是从左到右,而在计算机中,左边代表高位,右边表示低位。
因此 s->val >> 16表示将左边的16位放到右边的低位中,因为 s->val为32位,而(u16*)s->ptr指向的位16位内存空间,只会写入低16位。
二级指针又叫双指针。C语言中不存在引用,所以当你试图改变一个指针的值的时候必须使用二级指针。
C++中可以使用引用类型来实现。
写入之后的操作很明白了:
ptr+2,即地址+2,移到新的地址空间,指向一个编码的空间。
再进行s->val <<= 16;左移16位的操作。继续进行位流读写器从左到右写入的动作。
(注意移位操作不会改变原操作数)
关于指针则可以简单理解为:一个变量的地址就称为该变量的指针。指针就是地址
代码为:
static inline void put_bit(bit_t* s, u32 v, s32 n) // v: 编码, n: 编码的位宽
{
s32 new_bit;
v = v << (32 - n) >> s->bit;
s->val |= v;
new_bit = s->bit + n;
if (new_bit < 16)
{
s->bit = new_bit;
return;
}
*((u16*)s->ptr) = s->val >> 16;
s->ptr += 2;
s->val <<= 16;
s->bit = new_bit - 16;
}
3.结合上面的每次大于16位左移,这里要求在调用上面函数时32位v的编码要<=16位。
static inline void put_bits(bit_t* s, u32 v, s32 n)
{
if (n <=16)
{
put_bit(s, v, n);
return;
}
put_bit(s, v >> 16, n - 16);
put_bit(s, v, 16);
}
void end_put_bits(bit_t* s, u8** buf, s32* size)
{
*((u32*)s->ptr) = s->val >> 16; // 后面两个字节为0
s->ptr += (s->bit + 7) >> 3;
*buf = (u8*)s->strm;
*size = (u32)s->ptr - (u32)s->strm;
}
而对于读操作,则和写操作类似,不再叙述
代码如下:
void begin_get_bits(bit_t* s, u8* buf, s32 size)
{
s->val = 0;
s->bit = 0;
s->pos = 0;
s->size = size;
s->ptr = buf;
}
static inline u32 get_bit(bit_t* s, s32 n)
{
u32 ret;
if (s->bit <= 16)
{
s->val = (s->val << 16) | *((u16*)s->ptr);
s->ptr += 2;
s->bit += 16;
}
ret = s->val << (32 - s->bit) >> (32 - n);
s->bit -= n;
return ret;
}
static inline u32 get_bits(bit_t* s, s32 n)
{
if (n <= 16)
return get_bit(s, n);
return (get_bit(s, n - 16) << 16) | get_bit(s, 16);
}
之前已经写过huffman压缩的算法。可我直接把编码转化为了char字符,效率十分低下。
c语言也太灵活了。怕了。