RingBuffer源代码分析(最详细)

2024-02-05 10:08

本文主要是介绍RingBuffer源代码分析(最详细),希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

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;
}

 

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

 

三种情况:

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


这篇关于RingBuffer源代码分析(最详细)的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/680573

相关文章

怎样通过分析GC日志来定位Java进程的内存问题

《怎样通过分析GC日志来定位Java进程的内存问题》:本文主要介绍怎样通过分析GC日志来定位Java进程的内存问题,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录一、GC 日志基础配置1. 启用详细 GC 日志2. 不同收集器的日志格式二、关键指标与分析维度1.

SpringBoot整合liteflow的详细过程

《SpringBoot整合liteflow的详细过程》:本文主要介绍SpringBoot整合liteflow的详细过程,本文给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋...  liteflow 是什么? 能做什么?总之一句话:能帮你规范写代码逻辑 ,编排并解耦业务逻辑,代码

MySQL中的表连接原理分析

《MySQL中的表连接原理分析》:本文主要介绍MySQL中的表连接原理分析,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录1、背景2、环境3、表连接原理【1】驱动表和被驱动表【2】内连接【3】外连接【4编程】嵌套循环连接【5】join buffer4、总结1、背景

浏览器插件cursor实现自动注册、续杯的详细过程

《浏览器插件cursor实现自动注册、续杯的详细过程》Cursor简易注册助手脚本通过自动化邮箱填写和验证码获取流程,大大简化了Cursor的注册过程,它不仅提高了注册效率,还通过友好的用户界面和详细... 目录前言功能概述使用方法安装脚本使用流程邮箱输入页面验证码页面实战演示技术实现核心功能实现1. 随机

python中Hash使用场景分析

《python中Hash使用场景分析》Python的hash()函数用于获取对象哈希值,常用于字典和集合,不可变类型可哈希,可变类型不可,常见算法包括除法、乘法、平方取中和随机数哈希,各有优缺点,需根... 目录python中的 Hash除法哈希算法乘法哈希算法平方取中法随机数哈希算法小结在Python中,

Java Stream的distinct去重原理分析

《JavaStream的distinct去重原理分析》Javastream中的distinct方法用于去除流中的重复元素,它返回一个包含过滤后唯一元素的新流,该方法会根据元素的hashcode和eq... 目录一、distinct 的基础用法与核心特性二、distinct 的底层实现原理1. 顺序流中的去重

HTML img标签和超链接标签详细介绍

《HTMLimg标签和超链接标签详细介绍》:本文主要介绍了HTML中img标签的使用,包括src属性(指定图片路径)、相对/绝对路径区别、alt替代文本、title提示、宽高控制及边框设置等,详细内容请阅读本文,希望能对你有所帮助... 目录img 标签src 属性alt 属性title 属性width/h

CSS3打造的现代交互式登录界面详细实现过程

《CSS3打造的现代交互式登录界面详细实现过程》本文介绍CSS3和jQuery在登录界面设计中的应用,涵盖动画、选择器、自定义字体及盒模型技术,提升界面美观与交互性,同时优化性能和可访问性,感兴趣的朋... 目录1. css3用户登录界面设计概述1.1 用户界面设计的重要性1.2 CSS3的新特性与优势1.

关于MyISAM和InnoDB对比分析

《关于MyISAM和InnoDB对比分析》:本文主要介绍关于MyISAM和InnoDB对比分析,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录开篇:从交通规则看存储引擎选择理解存储引擎的基本概念技术原理对比1. 事务支持:ACID的守护者2. 锁机制:并发控制的艺

CSS中的Static、Relative、Absolute、Fixed、Sticky的应用与详细对比

《CSS中的Static、Relative、Absolute、Fixed、Sticky的应用与详细对比》CSS中的position属性用于控制元素的定位方式,不同的定位方式会影响元素在页面中的布... css 中的 position 属性用于控制元素的定位方式,不同的定位方式会影响元素在页面中的布局和层叠关