看到一篇寫的非常詳細的帖子,為防止樓主刪帖后找不到,果斷轉載過來
RingBuffer源代碼分析 出處: http://bbs.ickey.cn/community/forum.php?mod=viewthread&tid=43202
(出處: ICKEY BBS)
大家都知道,環形緩沖區是比較常用的數據結構,正好機智雲“微信寵物屋源代碼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判斷,其實是必須要加上的,不知道為什么,我下載的代碼,這些部分都被注釋掉了。
