RingBuffer源代码分析
- 1、下载文档前请自行甄别文档内容的完整性,平台不提供额外的编辑、内容补充、找答案等附加服务。
- 2、"仅部分预览"的文档,不可在线预览部分如存在完整性等问题,可反馈申请退款(可完整预览的文档不适用该条件!)。
- 3、如文档侵犯您的权益,请联系客服反馈,我们会尽快为您处理(人工客服工作时间:9:00-18:30)。
RingBuffer源代码分析
看到⼀篇写的⾮常详细的帖⼦,为防⽌楼主删帖后找不到,果断转载过来
⼤家都知道,环形缓冲区是⽐较常⽤的数据结构,正好机智云“微信宠物屋源代码v2.3”中也⽤到了。
下⾯给⼤家分析⼀下。
⾸先是数据结构:
“RingBuffer.h”
注意是head指向了读区域,tail指向了写区域!
注意是head指向了读区域,tail指向了写区域!
注意是head指向了读区域,tail指向了写区域!
typedef struct {
size_t rb_capacity; //缓冲区容量
char *rb_head; //⽤于读出的指针
char *rb_tail; //⽤于写⼊的指针
char rb_buff[256]; //缓冲区实体
}RingBuffer;
下⾯分析他的⼏个函数:
“RingBuffer.c”
//⽤来⽐较最⼩值的宏
#define min(a, b) (a)<(b)?(a)
b)
//新建RingBuffer,给成员赋值
//MAX_RINGBUFFER_LEN 这个宏,被定义为"P0数据最⼤长度"的2倍
//head/tail 两个指针,都指向缓冲区实体(数组rb_buff)的⾸地址
void rb_new(RingBuffer* rb)
{
rb->rb_capacity = MAX_RINGBUFFER_LEN; //capacity;
rb->rb_head = rb->rb_buff;
rb->rb_tail = rb->rb_buff;
};
获得缓冲区总容量Capacity:
size_t rb_capacity(RingBuffer *rb)
{
return rb->rb_capacity;
}
获得缓冲区可读区域,返回可读区域⼤⼩:
三种情况:
1、head与tail都指向同⼀个地⽅时,可读区域⼤⼩为0【这种情况只会在缓冲区还未使⽤时出现,
开始使⽤之后,不会出现head/tail重合的现象,即tail永远不会等于head,否则head指向的数据还未读⾛就被覆盖了!】
2、head < tail ,说明tail没有写到缓冲区末尾,从缓冲区开头重新开始。
可读的区域⾃然为(tail - head)
3、head > tail ,说明tail已经从缓冲区末尾写完,并从开头处重新准备写了。
插⼊图⽚给⼤家看看:
rb_buff是数组名,因此可以作为缓冲实体⾸地址的指针。
size_t rb_can_read(RingBuffer *rb)
{
if (rb->rb_head == rb->rb_tail) return 0;
if (rb->rb_head < rb->rb_tail) return rb->rb_tail - rb->rb_head;
return rb_capacity(rb) - (rb->rb_head - rb->rb_tail);
}
获得可写区域⼤⼩,就可以⽤总容量减去可读区域⼤⼩来计算了:
size_t rb_can_write(RingBuffer *rb)
{
return rb_capacity(rb) - rb_can_read(rb);
}
读数据,从head指向的地址开始,读到data指向的地址处,读count个数据。
返回读的个数
三种情况:
1、head < tail ,此时要从count 和"可读区域⼤⼩"中选⼀个较⼩的值,作为读操作的次数。
避免了count ⼤于“可读区域”的错误。
2、head > tail 且 count 的个数⼩于“从head到缓冲区末尾的数据个数”图中蓝⾊。
直接复制内存,再修改head 指针即可。
3、head > tail 且 count 的个数⼤于“从head到缓冲区末尾的数据个数”。
此时,先把从head到缓冲区末尾的值蓝⾊复制到data处,再把剩余的绿⾊复制过去。
注意两个值:copy_sz 和*(data + copy_sz)如图
这种情况下,问题来了,要是绿⾊的区域超过了tail 怎么办?:)
所以,应该加了⼀个判断,这个在写操作中做了,但这⾥没做。
即要读的个数count 要⼩于可读区域的⼤⼩。
不然会出现head > tail 但head 指向的数据以及head 后边的数据⼜不是有效数据,这个问题。
代码:
size_t rb_read(RingBuffer *rb, void *data, size_t count)
{
if (rb->rb_head < rb->rb_tail)
{
int copy_sz = min(count, rb_can_read(rb));
memcpy(data, rb->rb_head, copy_sz);
rb->rb_head += copy_sz;
return copy_sz;
}
else
{
if (count < rb_capacity(rb)-(rb->rb_head - rb->rb_buff))
{
int copy_sz = count;
memcpy(data, rb->rb_head, copy_sz);
rb->rb_head += copy_sz;
return copy_sz;
}
else
{
int copy_sz = rb_capacity(rb) - (rb->rb_head - rb->rb_buff);
memcpy(data, rb->rb_head, copy_sz);
rb->rb_head = rb->rb_buff;
copy_sz += rb_read(rb, (char*)data+copy_sz, count-copy_sz);
return copy_sz;
}
}
}
写数据,把数据从data指向的地址,写到tail 指向的地址,写count个。
返回写的个数。
这⾥进来直接判断,要写⼊的内容⼤⼩要⼩于可写区域⼤⼩,防⽌造成数据覆盖。
写⼊合法。
下⾯写⼊分了三种情况:
1、2 需要计算tail_avail_sz,这个值为tail 到缓冲区末尾的数据区域⼤⼩。
1、head < tail ,count < tail_avail_sz 。
直接复制内容。
假如tail 到了缓冲区末尾,让tail 回到缓冲区⾸地址。
2、head < tail ,count > tail_avail_sz 。
先写⼊ tail_avail_sz 个数据,tail 回到缓冲区⾸地址,再写⼊剩余的部分。
3、head > tail ,这种情况最简单,由于已经做了写⼊合法判断,所以直接复制内容,修改tail 即可。
代码:
size_t rb_write(RingBuffer *rb, const void *data, size_t count)
{
if (count >= rb_can_write(rb))
return -1;
if (rb->rb_head <= rb->rb_tail)
{
int tail_avail_sz = rb_capacity(rb) - (rb->rb_tail - rb->rb_buff);
if (count <= tail_avail_sz)
{
memcpy(rb->rb_tail, data, count);
rb->rb_tail += count;
if (rb->rb_tail == rb->rb_buff+rb_capacity(rb))
rb->rb_tail = rb->rb_buff;
return count;
}
else
{
memcpy(rb->rb_tail, data, tail_avail_sz);
rb->rb_tail = rb->rb_buff;
return tail_avail_sz + rb_write(rb, (char*)data+tail_avail_sz, count-tail_avail_sz);
}
}
else
{
memcpy(rb->rb_tail, data, count);
rb->rb_tail += count;
return count;
}
}
对于源程序中的,指针不为NULL判断,其实是必须要加上的,不知道为什么,我下载的代码,这些部分都被注释掉了。