循环缓冲区(循环队列)的优化技巧
循环缓冲区(循环队列)的优化技巧
循环缓冲区作为一种FIFO的数据结构,应用非常广泛。在小红书上偶然间看到评论区里有分享一种针对开辟空间和位运算,提高存取效率的方法,非常新颖。在此记录一下。
传统的循环缓冲区
传统的循环缓冲区通常开辟一块连续的数据空间。使用两个指针,head和tail指针,分别代表即将写入的索引和即将读取的索引。
- 当
head == tail
时,代表缓冲区空 - 当
(head + 1) % N == tail
时,代表缓冲区满,这浪费了一个元素的存储空间,当这个条件符合的时候,实际上head指向的空间还是可以存入一个元素,但存入后会导致head == tail
,与判空的方式冲突了,虽然也有办法避免这个问题,但大部分应用一般也不介意浪费到这个元素的空间。
2次幂大小的空间的循环缓冲区
此类循环缓冲区有以下三个不同
- 缓冲区空间大小必须是2次幂大小,即2,4,8,16等,因为要用到位运算特性
- 同样使用两个指针write和read指针,但分别代表的是累计写入的次数和累计读取的次数,所以当
write == head
时,代表缓冲区空(即读取次数和写入次数相同);当write - head == N
时,代表缓冲区满(即没有更多写入的位置),因此避免了浪费空间的问题。这里需要注意的是,一定要让指针的数据类型为无符号型,不会出现UB的溢出回环等问题 - 写入的读取的索引位置为
read & (N - 1)
或write & (N - 1)
,相当于取模运算,但是位运算更快
ChatGPT给出的一种实现方法
#define SIZE 8 // 必须是 2 的幂
int buffer[SIZE];
unsigned int read = 0, write = 0;
bool enqueue(int value) {
if (write - read == SIZE) return false; // 满
buffer[write & (SIZE - 1)] = value;
write++;
return true;
}
bool dequeue(int *value) {
if (write == read) return false; // 空
*value = buffer[read & (SIZE - 1)];
read++;
return true;
}