Go的数据结构与实现【Ring Buffer】

介绍

在本文中,我们将用Go实现环形缓冲区(Ring Buffer)

Ring Buffer

环形缓冲区(或循环缓冲区)是一种有界循环数据结构,用于在两个或多个线程之间缓冲数据。当我们继续写入环形缓冲区时,它会在到达末尾时回绕。

原理

环形缓冲区是使用在边界处环绕的固定大小数组实现的。
除了数组之外,它还保留了三个关键变量位置:

  • 缓冲区中用于插入元素的下一个可用插槽,
  • 缓冲区中的下一个未读元素,
  • 数组的结尾,即缓冲区环绕到数组开头的点
    -

环形缓冲区如何处理这些要求的机制因实现而异。我们需要知道的第一件事是容量,缓冲区的固定最大大小。接下来,我们将使用两个单调递增的序列:

  • 写入序列:从-1开始,在我们插入元素时递增1
  • 读取序列:从0开始,随着我们消耗一个元素递增1我们可以使用取模操作将序列映射到数组中的索引:

arrayIndex = sequence % capacity

取模操作在边界周围将序列连接起来,序列中每个值都对应环形缓冲区中的一个槽:
在这里插入图片描述
让我们看看我们如何插入一个元素:

buffer[++writeSequence % capacity] = element

我们在插入元素之前预先增加了序列。为了消费一个元素,我们做一个后增量:

element = buffer[readSequence++ % capacity]

在这种情况下,我们对序列执行后增量。使用一个元素并不会将其从缓冲区中删除,它只是保留在数组中直到被覆盖。

缓冲区空与溢出

当我们循环数组时,我们将开始覆盖缓冲区中的数据。如果缓冲区已满,我们可以选择覆盖最旧的数据,无论读取序列是否已使用它或阻止覆盖尚未读取的数据。
如果中间值或旧值(例如,商品价格)可以被覆盖,我们可以覆盖数据而无需等待数据被使用。另一方面,如果必须消耗序列中所有值(例如电子商务交易),我们应该等待(阻塞/忙碌等待),直到缓冲区有可用的插槽。
如果缓冲区的大小等于其容量,则缓冲区已满,其中其大小等于未读元素的数量:

size = (writeSequence - readSequence) + 1
isFull = (size == capacity)

在这里插入图片描述
如果写序列落后于读序列,则缓冲区为空:

isEmpty = writeSequence < readSequence

在这里插入图片描述
如果缓冲区为空,则缓冲区返回空值。

优点与缺点

环形缓冲区是一种高效的FIFO缓冲区。它使用可以预先分配的固定大小的数组,并允许高效的内存访问模式。所有缓冲区操作都是常数时间O(1),包括消耗一个元素,因为它不需要移动元素。
另一方面,确定环形缓冲区的正确大小至关重要。例如,如果缓冲区过小并且读取速度很慢,则写入操作可能会阻塞很长时间。我们可以使用动态调整大小,但它需要移动数据,我们会错过上面讨论的大部分优势。

Go的实现

在了解环形缓冲区的原理之后,我们来用Go实现这个数据结构。

结构体定义

type T string

type RingBuffer struct {
   sync.RWMutex
   data     []T
   capacity int
   read     int
   write    int
}

我们定义一个环形缓冲区结构体,其中包含值数组、数组大小,读指针和写指针。同时,它也是并发安全的。

初始化

首先,让我们定义一个使用预定义容量初始化缓冲区的构造函数:

const DefaultCapacity = 8

func NewRingBuffer(cap int) *RingBuffer {
   ring := &RingBuffer{}
   if cap < 1 {
      cap = DefaultCapacity
   }

   ring.data = make([]T, cap)
   ring.capacity = cap
   ring.read = 0
   ring.write = -1

   return ring
}

接下来,我们将实现Offer操作,在下一个可用槽处将元素插入缓冲区,并在成功时返回 true。如果缓冲区找不到空槽,则返回false,也就是说,我们不能覆盖未读取的值。

func (r *RingBuffer) Offer(t T) bool {
   if !r.IsFull() {
      next := r.write + 1
      r.data[next%r.capacity] = t
      r.write++
      return true
   }

   return false
}

因此,我们正在递增写入序列并计算数组中下一个可用插槽的索引。然后,我们将数据写入缓冲区并存储更新的写入序列。

最后,我们将实现获取并删除下一个未读元素的轮询操作。轮询操作不会删除元素,但会增加读取序列。

func (r *RingBuffer) Poll() *T {
   if !r.IsEmpty() {
      next := r.data[r.read%r.capacity]
      r.read++
      return &next
   }

   return nil
}

在这里,我们通过计算数组中的索引来读取当前读取序列的数据。然后,如果缓冲区不为空,我们将递增序列并返回值。

  • 18
    点赞
  • 28
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
Ring Buffer(环形缓冲区)是一种循环使用的缓冲区,常用于实现数据的异步传输。下面是一个简单的C语言实现环形缓冲区的代码示例。 ```c #include <stdio.h> #include <stdlib.h> #define BUFFER_SIZE 10 typedef struct { int *buffer; int head; int tail; int size; } RingBuffer; RingBuffer *create_ring_buffer(int size) { RingBuffer *rb = (RingBuffer *)malloc(sizeof(RingBuffer)); rb->buffer = (int *)calloc(size, sizeof(int)); rb->head = 0; rb->tail = 0; rb->size = size; return rb; } void destroy_ring_buffer(RingBuffer *rb) { free(rb->buffer); free(rb); } int is_empty(RingBuffer *rb) { return rb->head == rb->tail; } int is_full(RingBuffer *rb) { return (rb->tail + 1) % rb->size == rb->head; } void push(RingBuffer *rb, int value) { if (is_full(rb)) { printf("Ring buffer is full!\n"); return; } rb->buffer[rb->tail] = value; rb->tail = (rb->tail + 1) % rb->size; } int pop(RingBuffer *rb) { if (is_empty(rb)) { printf("Ring buffer is empty!\n"); return -1; } int value = rb->buffer[rb->head]; rb->head = (rb->head + 1) % rb->size; return value; } int main() { RingBuffer *rb = create_ring_buffer(BUFFER_SIZE); push(rb, 1); push(rb, 2); push(rb, 3); printf("Pop: %d\n", pop(rb)); printf("Pop: %d\n", pop(rb)); push(rb, 4); push(rb, 5); push(rb, 6); while (!is_empty(rb)) { printf("Pop: %d\n", pop(rb)); } destroy_ring_buffer(rb); return 0; } ``` 在这个示例中,我们定义了一个RingBuffer结构体,包含了一个整型数组作为缓冲区、头指针、尾指针和缓冲区大小。我们通过create_ring_buffer函数创建缓冲区,并通过destroy_ring_buffer函数销毁缓冲区。 我们还定义了is_empty和is_full函数用于判断缓冲区是否为空或已满,以及push和pop函数用于向缓冲区中添加或取出数据。 在main函数中,我们先向缓冲区中添加了三个整数,然后取出了前两个。接着,我们又向缓冲区中添加了三个整数,并通过while循环将缓冲区中的所有数据取出并打印。最后,我们销毁了缓冲区。 这个示例只是一个简单的实现,实际的环形缓冲区可能会更加复杂。但是,这个示例可以帮助你了解如何使用C语言实现环形缓冲区。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

mldxxxxll5

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值