位流读写器

功能介绍:

读功能:将需要保存的编码(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语言也太灵活了。怕了。

 

 

你可能感兴趣的:(课堂知识)