RingBuffer源代碼分析


看到一篇寫的非常詳細的帖子,為防止樓主刪帖后找不到,果斷轉載過來

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判斷,其實是必須要加上的,不知道為什么,我下載的代碼,這些部分都被注釋掉了。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM