最近在学习Android 下的Bluedroid时, 看到在Bluedroid中实现了ringbuffer这一数据结构, 比较简洁, 所以独立出来进行分享.
Bluedorid官方源码路径
本文分享的ringbuffer例子源码路径
什么是ringbuffer?
上述图片是来自Amazon AVS C++ SDK中的图片. Shared Memory Ring Buffer.
如上图所示, ringbuffer 实质上就是一个连续内存/首位相连的一个环形缓冲区. 这里数据结构在音频数据的存储上比较常用到.
本文所分享的ringbuffer比较简单,同时仅支持一个写一个读. 对于event的存储我们通常会使用链表这一数据结构,而对于stream数据流我们通常会采用ringbuffer这一个数据结构.
具体实现数据数据
头文件的定义
#pragma once
#include <stdint.h>
#include <stddef.h>
typedef struct ringbuffer_t ringbuffer_t;
// NOTE:
// None of the functions below are thread safe when it comes to accessing the
// *rb pointer. It is *NOT* possible to insert and pop/delete at the same time.
// Callers must protect the *rb pointer separately.
// Create a ringbuffer with the specified size
// Returns NULL if memory allocation failed. Resulting pointer must be freed
// using |ringbuffer_free|.
ringbuffer_t* ringbuffer_init(const size_t size);
// Frees the ringbuffer structure and buffer
// Save to call with NULL.
void ringbuffer_free(ringbuffer_t* rb);
// Returns remaining buffer size
size_t ringbuffer_available(const ringbuffer_t* rb);
// Returns size of data in buffer
size_t ringbuffer_size(const ringbuffer_t* rb);
// Attempts to insert up to |length| bytes of data at |p| into the buffer
// Return actual number of bytes added. Can be less than |length| if buffer
// is full.
size_t ringbuffer_insert(ringbuffer_t* rb, const uint8_t* p, size_t length);
// Peek |length| number of bytes from the ringbuffer, starting at |offset|,
// into the buffer |p|. Return the actual number of bytes peeked. Can be less
// than |length| if there is less than |length| data available. |offset| must
// be non-negative.
size_t ringbuffer_peek(const ringbuffer_t* rb, off_t offset, uint8_t* p,
size_t length);
// Does the same as |ringbuffer_peek|, but also advances the ring buffer head
size_t ringbuffer_pop(ringbuffer_t* rb, uint8_t* p, size_t length);
// Deletes |length| bytes from the ringbuffer starting from the head
// Return actual number of bytes deleted.
size_t ringbuffer_delete(ringbuffer_t* rb, size_t length);
具体函数和数据结构的的实现
- ringbuffer 结构体的定义
struct ringbuffer_t {
size_t total; //总的数据大小
size_t available; //剩余可写入的buffer大小
uint8_t* base;
uint8_t* head; //可以理解为读指针
uint8_t* tail; //可以理解为写指针
};
在struct ringbuffer_t 中的uint8_t* base是用于解决头尾相连部分的特殊位置处理(类似链表中的哨兵),这在接下来的内容中会说明.
- ringbuffer初始化
ringbuffer_t* ringbuffer_init(const size_t size) {
ringbuffer_t* p =
static_cast<ringbuffer_t*>(malloc(sizeof(ringbuffer_t)));
p->base = static_cast<uint8_t*>(malloc(size));
p->head = p->tail = p->base;
p->total = p->available = size;
return p;
}
对于ringbuffer_init, 主要是进行内存的分配,和相关变量的初始化.
初始时p->head = p->tail = p->base;
- 往ringbuffer中写入数据
size_t ringbuffer_insert(ringbuffer_t* rb, const uint8_t* p, size_t length) {
assert(rb);
assert(p);
if (length > ringbuffer_available(rb)) length = ringbuffer_available(rb);
for (size_t i = 0; i != length; ++i) {
*rb->tail++ = *p++;
if (rb->tail >= (rb->base + rb->total)) rb->tail = rb->base;
}
rb->available -= length;
return length;
}
关键代码说明:
在往rb->tail 中写入数据前,需要先判断当前需要写入的数据和ringbuffer还可以写入的数据大小, 不能覆盖未被读走的数据.
另外一点,
当rb->tail >= (rb->base + rb->total)需要进行临界点的处理, 也就是当写指针>= buffer内存地址的尾部时 需要将rb->tail 赋值为rb->base, 这点可以理解为:虽然我们说ringbuffer时首尾相连的, 但分配的内存的角度并不是首尾连续的,而是需要在代码逻辑上将写指针(rb->tail)移动到尾部时进行了特殊处理,将其赋值为起始的内存地址.
- 删除ringbuffer中指定长度的数据
assert(rb);
if (length > ringbuffer_size(rb)) length = ringbuffer_size(rb);
rb->head += length;
if (rb->head >= (rb->base + rb->total)) rb->head -= rb->total;
rb->available += length;
return length;
}
关键代码说明:
还是在临界点的处理上, 这里由于是删除数据,不需要进行数据的拷贝,直接将rb->head 移动length, 然后进行临界点的处理,
if (rb->head >= (rb->base + rb->total)) rb->head -= rb->total;
看到这里可能有些同学会对 rb->head -= rb->total;处理会有疑问,怎么不是rb->head = rb->base?? 这是因为我们在前面是直接将 rb->head += length; 也就是rb->head 的值是有可能大于在初始化是分配的最大有效地址值,而对于ringbuffer首尾是相连的,也就是当读指针移动到“分配的最大有效地址值”需要将读指针直接移动rb->head -= rb->total;的位置进行数据的删除.
- 取出偏移指定offset的指定长度数据
size_t ringbuffer_peek(const ringbuffer_t* rb, off_t offset, uint8_t* p,
size_t length) {
assert(rb);
assert(p);
assert(offset >= 0);
assert((size_t)offset <= ringbuffer_size(rb));
uint8_t* b = ((rb->head - rb->base + offset) % rb->total) + rb->base; //得到需要开始读取数据的起始地址
const size_t bytes_to_copy = (offset + length > ringbuffer_size(rb))
? ringbuffer_size(rb) - offset
: length; //计算需要copy的数据
for (size_t copied = 0; copied < bytes_to_copy; ++copied) {
*p++ = *b++;
if (b >= (rb->base + rb->total)) b = rb->base; //临界点处理
}
return bytes_to_copy;
}
关键代码说明,
在 uint8_t* b = ((rb->head - rb->base + offset) % rb->total) + rb->base;中,rb->head - rb->base的操作得到的是读指针距离rb->base的距离,进而将(rb->head - rb->base + offset) % rb->total 得到的是读到的数据距离rb->base的字节数.
需要注意的是: ringbuffer_peek操作并不会改变ringbuffer中的相关数据, 只是提取了数据