循环队列、以及环形缓冲区

循环队列

普通队列的入队操作将队尾指针后移+1,出队操作将队头指针后移+1,操作几次之后会发现队头指针和队尾指针都跑到缓冲区的尾部去了,这就导致了前面的内存空间全被浪费,如果要重新恢复使用,则需要进行元素和指针的移动

显然这种队列使用方式太不方便了,所以就诞生了环形队列「不用搬移元素和指针,一直可以重复利用这段内存空间」。

golang 的有缓冲的 channel 通道,内部就包含了一个环形队列

如何判断环形队列为空,为满,有两种方式:

一. 是附加一个标志位 isFull

二. 限制 tail 赶上 head,即队尾结点与队首结点之间至少留有一个元素的空间。

代码示例:

ppackage queue

import (
	"errors"
	"fmt"
)

type Queue struct {
	data []interface{}
	head int // 队首
	tail int // 队尾
	size int // 队列长度
}

// NewQueue 新建指定长度的队列
func NewQueue(size int) *Queue {
	return &Queue{make([]interface{}, size+1), 0, 0, 0}
}

// CapaCity 获取容量
// 限制 tail 追上 head,即队尾结点与队首结点之间至少留有一个元素的空间。
func (q *Queue) CapaCity() int {
	return len(q.data) - 1
}

func (q *Queue) IsEmpty() bool {
	return q.head == q.tail
}

func (q *Queue) Len() int {
	return q.size
}

// Enqueue 入队
func (q *Queue) Enqueue(x interface{}) {
	q.grow(q.CapaCity() * 2)
	q.data[q.tail] = x
	q.tail = (q.tail + 1) % len(q.data)
	q.size++
}

// grow 队列扩容
func (q *Queue) grow(new_capacity int) {
	// 队列已满,需要扩容
	if q.head == (q.tail+1)%len(q.data) {
		new_data := make([]interface{}, new_capacity+1)
		for i := 0; i < q.Len(); i++ {
			new_data[i] = q.data[(i+q.head)%len(q.data)]
		}
		q.data = new_data
		q.head, q.tail = 0, q.size
		fmt.Println("queue resized to ", q.size)
	}
}

// Dequeue 出队
func (q *Queue) Dequeue() (interface{}, error) {
	if q.IsEmpty() {
		return nil, errors.New("Cannot dequeue from an empty queue.")
	}
	x := q.data[q.head]
	q.data[q.head] = nil
	q.head = (q.head + 1) % q.CapaCity()
	q.size--
	if q.size == q.CapaCity()/4 && q.CapaCity()/2 != 0 {
		q.grow(q.CapaCity() / 2)
	}
	return x, nil
}

// Gethead, 获取队首元素
func (q *Queue) Gethead() (interface{}, error) {
	if q.IsEmpty() {
		return nil, errors.New("Queue is empty.")
	}
	return q.data[q.head], nil
}

func (q *Queue) String() string {
	return fmt.Sprintf("cap: %d size: %d data: %v head: %d tail: %d", q.CapaCity(), q.Len(), q.data, q.head, q.tail)
}

环形缓冲器

环形缓冲器(ringr buffer),也称作圆形队列(circular queue),循环缓冲区(cyclic buffer),圆形缓冲区(circula buffer),是一种用于表示一个固定尺寸、头尾相连的缓冲区的数据结构,适合缓存数据流。 和循环队列类似的实现,区别在于存储字节类型数据

对于 I/O 较重的应用程序,可以使用环形缓冲替换 bytes.Buffer,因为开启了固定的一块连续内存,可节省减少内存消耗

看下图是个非常有意思的东西:

开源的线程安全的环形缓冲实现在: https://github.com/smallnest/ringbuffer

看上去已经很完善了,这里就不重复实现了