RingBuffer源代码分析

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了RingBuffer源代码分析相关的知识,希望对你有一定的参考价值。

Mark 事实证明转载的重要性

转载:http://www.cnblogs.com/prayer521/p/5868283.html

 

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;

}

   

获得缓冲区可读区域,返回可读区域大小:

   

三种情况:

1headtail都指向同一个地方时,可读区域大小为0【这种情况只会在缓冲区还未使用时出现,

开始使用之后,不会出现head/tail重合的现象,即tail永远不会等于head,否则head指向的数据还未读走就被覆盖了!】

2head < tail  ,说明tail没有写到缓冲区末尾,从缓冲区开头重新开始。可读的区域自然为(tail - head)

3head > 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个数据。返回读的个数

三种情况:

1head < tail  ,此时要从count "可读区域大小"中选一个较小的值,作为读操作的次数。避免了count 大于"可读区域"的错误。

2head > tail   count 的个数 小于"head到缓冲区末尾的数据个数"图中蓝色。直接复制内存,再修改head 指针即可。

3head > 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个。返回写的个数。

这里进来直接判断,要写入的内容大小 要小于可写区域大小,防止造成数据覆盖。写入合法。

下面写入分了三种情况:

12 需要计算tail_avail_sz,这个值为tail 到缓冲区末尾的数据区域大小。

1head < tail  count < tail_avail_sz  。直接复制内容。假如tail 到了缓冲区末尾,让tail 回到缓冲区首地址。

2head < tail  count > tail_avail_sz  。先写入 tail_avail_sz 个数据,tail 回到缓冲区首地址,再写入剩余的部分。

3head > 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判断,其实是必须要加上的,不知道为什么,我下载的代码,这些部分都被注释掉了。

以上是关于RingBuffer源代码分析的主要内容,如果未能解决你的问题,请参考以下文章

Android 逆向整体加固脱壳 ( DEX 优化流程分析 | DexPrepare.cpp 中 dvmOptimizeDexFile() 方法分析 | /bin/dexopt 源码分析 )(代码片段

lmax RingBuffer 和 log4j 占用大量内存

Android 事件分发事件分发源码分析 ( Activity 中各层级的事件传递 | Activity -> PhoneWindow -> DecorView -> ViewGroup )(代码片段

Android 插件化VirtualApp 源码分析 ( 目前的 API 现状 | 安装应用源码分析 | 安装按钮执行的操作 | 返回到 HomeActivity 执行的操作 )(代码片段

优化 C# 代码片段、ObservableCollection 和 AddRange

Disruptor并发框架 核心概念场景分析