Go 语言标准库 —— container/ring 包(环形链表)
🔹 概述
container/ring 包实现了环形链表(循环链表)的数据结构。
主要功能:
- 环形链表操作
- O(1) 时间复杂度的遍历
- 支持在任意位置插入/删除元素
- 可以存储任意类型的元素
重要说明:
- ring 是一个通用的环形链表实现
- 不是并发安全的
- 元素类型为 interface{}(可以存储任意类型)
- 环形结构(尾节点指向头节点)
- 可以从任意节点开始遍历整个环
环形链表的特性:
- 循环结构(没有真正的头尾)
- 可以从任意节点访问所有节点
- 支持 O(1) 时间复杂度的遍历
- 适合固定大小的缓冲区
- 适合轮转调度场景
应用场景:
- 循环缓冲区
- 轮转调度(Round Robin)
- 时间片轮转
- 固定大小的缓存
- 音频/视频缓冲
🔹 核心类型
Ring 环形链表类型
ring.Ring struct
-
说明:
- 环形链表结构
- 每个节点都指向下一个节点
- 最后一个节点指向第一个节点形成环
- 零值表示长度为 0 的环
-
字段:
type Ring struct { Value interface{} // 当前节点的值 next *Ring // 下一个节点(内部字段) prev *Ring // 上一个节点(内部字段) } -
注意:
- Value 是公开字段,可以直接访问和修改
- next 和 prev 是内部字段,不应直接访问
- 零值 ring 可以直接使用,表示空环
🔹 核心函数
创建新环
ring.New(n int) *Ring
-
说明:
- 创建包含 n 个节点的环
- 每个节点的 Value 初始化为 nil
- 如果 n <= 0,返回 nil
-
参数:
n int- 节点数量
-
返回值:
*Ring- 新创建的环
-
时间复杂度:O(n)
-
示例:
// 创建包含 5 个节点的环 r := ring.New(5) // 设置每个节点的值 for i := 0; i < 5; i++ { r.Value = i r = r.Next() }
🔹 核心方法
获取下一个节点
r.Next() *Ring
-
说明:
- 返回当前节点的下一个节点
- 如果环只有一个节点,返回自身
-
返回值:
*Ring- 下一个节点
-
时间复杂度:O(1)
-
示例:
r := ring.New(3) // 遍历环 start := r for i := 0; i < 3; i++ { fmt.Println(r.Value) r = r.Next() }
获取上一个节点
r.Prev() *Ring
-
说明:
- 返回当前节点的前一个节点
- 如果环只有一个节点,返回自身
-
返回值:
*Ring- 前一个节点
-
时间复杂度:O(1)
-
示例:
r := ring.New(3) // 反向遍历 start := r for i := 0; i < 3; i++ { r = r.Prev() fmt.Println(r.Value) }
移动节点
r.Move(n int) *Ring
-
说明:
- 从当前节点移动 n 个位置
- n > 0 向前移动(Next 方向)
- n < 0 向后移动(Prev 方向)
- n = 0 返回当前节点
-
参数:
n int- 移动的步数
-
返回值:
*Ring- 移动后的节点
-
时间复杂度:O(|n|)
-
示例:
r := ring.New(10) // 向前移动 3 步 r = r.Move(3) // 向后移动 2 步 r = r.Move(-2) // 回到起点 r = r.Move(0)
删除节点
r.Unlink(n int) *Ring
-
说明:
- 从当前节点之后删除 n 个节点
- 返回被删除的节点组成的新环
- 原环会缩短
-
参数:
n int- 要删除的节点数量
-
返回值:
*Ring- 被删除的节点组成的环
-
时间复杂度:O(n)
-
示例:
r := ring.New(5) // 删除 2 个节点 removed := r.Unlink(2) fmt.Println("原环大小:", r.Len()) // 3 fmt.Println("删除的环大小:", removed.Len()) // 2
连接两个环
r.Link(s *Ring) *Ring
-
说明:
- 将环 s 连接到当前节点之后
- s 的第一个节点成为 r 的下一个节点
- 返回 s 的最后一个节点
- 两个环合并为一个环
-
参数:
s *Ring- 要连接的环
-
返回值:
*Ring- s 的最后一个节点
-
时间复杂度:O(1)
-
示例:
r1 := ring.New(3) r2 := ring.New(2) // 连接两个环 r1.Link(r2) fmt.Println("合并后大小:", r1.Len()) // 5
获取环的长度
r.Len() int
-
说明:
- 计算环中节点的数量
- 需要遍历整个环
-
返回值:
int- 节点数量
-
时间复杂度:O(n)
-
示例:
r := ring.New(5) fmt.Println("环的大小:", r.Len()) // 5
执行函数
r.Do(f func(interface{}))
-
说明:
- 对环中每个节点执行函数 f
- 从当前节点开始遍历
-
参数:
f func(interface{})- 要执行的函数
-
时间复杂度:O(n)
-
示例:
r := ring.New(3) r.Value = 1 r.Next().Value = 2 r.Next().Next().Value = 3 // 对每个节点执行函数 r.Do(func(v interface{}) { fmt.Println(v) }) // 输出:1 2 3
🔹 使用场景
1. 循环缓冲区
package main
import (
"container/ring"
"fmt"
)
// CircularBuffer 实现循环缓冲区
type CircularBuffer struct {
buffer *ring.Ring
size int
}
func NewCircularBuffer(size int) *CircularBuffer {
return &CircularBuffer{
buffer: ring.New(size),
size: size,
}
}
func (cb *CircularBuffer) Write(value interface{}) {
cb.buffer.Value = value
cb.buffer = cb.buffer.Next()
}
func (cb *CircularBuffer) Read() interface{} {
value := cb.buffer.Value
cb.buffer.Value = nil
cb.buffer = cb.buffer.Next()
return value
}
func (cb *CircularBuffer) GetAll() []interface{} {
values := make([]interface{}, 0, cb.size)
cb.buffer.Do(func(v interface{}) {
if v != nil {
values = append(values, v)
}
})
return values
}
func main() {
// 创建大小为 5 的循环缓冲区
buf := NewCircularBuffer(5)
// 写入数据
for i := 1; i <= 7; i++ {
buf.Write(i)
fmt.Printf("写入:%d\n", i)
}
// 读取所有数据
fmt.Println("缓冲区内容:", buf.GetAll())
// 输出:[3 4 5 6 7](最早的 2 个数据被覆盖)
}
2. 轮转调度(Round Robin)
package main
import (
"container/ring"
"fmt"
)
// Task 任务
type Task struct {
ID int
Name string
}
// RoundRobin 实现轮转调度
type RoundRobin struct {
tasks *ring.Ring
}
func NewRoundRobin(numSlots int) *RoundRobin {
return &RoundRobin{
tasks: ring.New(numSlots),
}
}
func (rr *RoundRobin) AddTask(task Task) {
rr.tasks.Value = task
rr.tasks = rr.tasks.Next()
}
func (rr *RoundRobin) GetNextTask() *Task {
task := rr.tasks.Value
if task == nil {
return nil
}
t := task.(Task)
rr.tasks.Value = nil // 清除
rr.tasks = rr.tasks.Next()
return &t
}
func (rr *RoundRobin) HasTasks() bool {
has := false
rr.tasks.Do(func(v interface{}) {
if v != nil {
has = true
}
})
return has
}
func main() {
// 创建轮转调度器(5 个槽位)
rr := NewRoundRobin(5)
// 添加任务
rr.AddTask(Task{ID: 1, Name: "任务 1"})
rr.AddTask(Task{ID: 2, Name: "任务 2"})
rr.AddTask(Task{ID: 3, Name: "任务 3"})
// 轮转执行任务
for rr.HasTasks() {
task := rr.GetNextTask()
if task != nil {
fmt.Printf("执行:%s\n", task.Name)
}
}
}
3. 时间片轮转
package main
import (
"container/ring"
"fmt"
"time"
)
// Process 进程
type Process struct {
ID int
Name string
Remaining int // 剩余时间片
}
// TimeSliceScheduler 时间片轮转调度器
type TimeSliceScheduler struct {
processes *ring.Ring
quantum int // 时间片大小
}
func NewScheduler(numSlots, quantum int) *TimeSliceScheduler {
return &TimeSliceScheduler{
processes: ring.New(numSlots),
quantum: quantum,
}
}
func (s *TimeSliceScheduler) AddProcess(p Process) {
s.processes.Value = p
s.processes = s.processes.Next()
}
func (s *TimeSliceScheduler) Run() {
start := s.processes
for {
// 检查是否还有进程需要执行
done := true
s.processes.Do(func(v interface{}) {
if v != nil && v.(Process).Remaining > 0 {
done = false
}
})
if done {
break
}
// 执行当前进程
if p, ok := s.processes.Value.(Process); ok && p.Remaining > 0 {
execTime := min(s.quantum, p.Remaining)
p.Remaining -= execTime
s.processes.Value = p
fmt.Printf("进程 %s 执行 %d 时间片,剩余 %d\n",
p.Name, execTime, p.Remaining)
time.Sleep(100 * time.Millisecond) // 模拟执行
}
s.processes = s.processes.Next()
// 防止死循环
if s.processes == start {
start = s.processes
}
}
}
func min(a, b int) int {
if a < b {
return a
}
return b
}
func main() {
// 创建调度器(5 个槽位,时间片 2)
scheduler := NewScheduler(5, 2)
// 添加进程
scheduler.AddProcess(Process{ID: 1, Name: "P1", Remaining: 5})
scheduler.AddProcess(Process{ID: 2, Name: "P2", Remaining: 3})
scheduler.AddProcess(Process{ID: 3, Name: "P3", Remaining: 4})
// 运行调度
scheduler.Run()
}
4. 固定大小缓存
package main
import (
"container/ring"
"fmt"
)
// Cache 实现固定大小的缓存
type Cache struct {
cache *ring.Ring
lookup map[interface{}]*ring.Ring
}
type CacheEntry struct {
Key interface{}
Value interface{}
}
func NewCache(size int) *Cache {
return &Cache{
cache: ring.New(size),
lookup: make(map[interface{}]*ring.Ring),
}
}
func (c *Cache) Put(key, value interface{}) {
// 如果键已存在,更新值
if r, ok := c.lookup[key]; ok {
r.Value = CacheEntry{Key: key, Value: value}
return
}
// 否则,覆盖最旧的条目
entry := CacheEntry{Key: key, Value: value}
// 删除旧的查找记录
oldEntry := c.cache.Value.(CacheEntry)
if oldEntry.Key != nil {
delete(c.lookup, oldEntry.Key)
}
// 写入新条目
c.cache.Value = entry
c.lookup[key] = c.cache
c.cache = c.cache.Next()
}
func (c *Cache) Get(key interface{}) (interface{}, bool) {
if r, ok := c.lookup[key]; ok {
return r.Value.(CacheEntry).Value, true
}
return nil, false
}
func (c *Cache) GetAll() []CacheEntry {
entries := make([]CacheEntry, 0, c.cache.Len())
c.cache.Do(func(v interface{}) {
if entry, ok := v.(CacheEntry); ok && entry.Key != nil {
entries = append(entries, entry)
}
})
return entries
}
func main() {
// 创建大小为 3 的缓存
cache := NewCache(3)
// 添加数据
cache.Put("key1", "value1")
cache.Put("key2", "value2")
cache.Put("key3", "value3")
fmt.Println("缓存内容:", cache.GetAll())
// 添加新数据(淘汰最旧的)
cache.Put("key4", "value4")
fmt.Println("淘汰后:", cache.GetAll())
// 查询
if val, ok := cache.Get("key2"); ok {
fmt.Println("key2 =", val)
}
}
5. 音频/视频缓冲
package main
import (
"container/ring"
"fmt"
)
// AudioBuffer 实现音频缓冲区
type AudioBuffer struct {
buffer *ring.Ring
sampleRate int
}
type AudioSample struct {
Left float32 // 左声道
Right float32 // 右声道
}
func NewAudioBuffer(size, sampleRate int) *AudioBuffer {
return &AudioBuffer{
buffer: ring.New(size),
sampleRate: sampleRate,
}
}
func (ab *AudioBuffer) Write(sample AudioSample) {
ab.buffer.Value = sample
ab.buffer = ab.buffer.Next()
}
func (ab *AudioBuffer) Read() AudioSample {
sample := ab.buffer.Value.(AudioSample)
ab.buffer.Value = AudioSample{} // 清零
ab.buffer = ab.buffer.Next()
return sample
}
func (ab *AudioBuffer) GetLatency() float64 {
// 延迟 = 缓冲区大小 / 采样率
return float64(ab.buffer.Len()) / float64(ab.sampleRate) * 1000 // 毫秒
}
func main() {
// 创建音频缓冲区(1024 个采样,44.1kHz)
buf := NewAudioBuffer(1024, 44100)
fmt.Printf("缓冲区延迟:%.2f ms\n", buf.GetLatency())
// 写入音频数据
for i := 0; i < 1024; i++ {
buf.Write(AudioSample{
Left: float32(i) / 1024.0,
Right: float32(1024-i) / 1024.0,
})
}
// 读取音频数据
sample := buf.Read()
fmt.Printf("读取采样:L=%.3f, R=%.3f\n", sample.Left, sample.Right)
}
🔹 注意事项和最佳实践
1. 零值 Ring
- ✅ 零值 ring 可以直接使用
- ⚠️ 零值 ring 表示空环(长度为 0)
var r ring.Ring
fmt.Println(r.Len()) // 0
2. 类型断言
- ⚠️ ring 存储的是 interface{} 类型
- ✅ 取出元素时必须进行类型断言
- ⚠️ 类型断言可能 panic,建议使用 comma-ok 形式
// 不安全
value := r.Value.(int)
// 安全
if value, ok := r.Value.(int); ok {
fmt.Println(value)
}
3. 并发安全
- ⚠️ ring 不是并发安全的
- ✅ 多线程环境需要加锁
type SafeRing struct {
mu sync.RWMutex
ring *ring.Ring
}
func (sr *SafeRing) Write(v interface{}) {
sr.mu.Lock()
defer sr.mu.Unlock()
sr.ring.Value = v
sr.ring = sr.ring.Next()
}
4. 内存管理
- ✅ 及时清理不需要的数据
- ✅ 读取后可以清零
value := r.Value
r.Value = nil // 清零
r = r.Next()
5. 性能考虑
- ✅ 遍历:O(n)
- ✅ 移动:O(|n|)
- ✅ 连接:O(1)
- ⚠️ 长度计算:O(n)
🔹 Ring vs List 对比
数据结构对比
| 特性 | Ring | List |
|---|---|---|
| 结构 | 环形 | 双向链表 |
| 头尾 | 无(循环) | 有(Front/Back) |
| 遍历 | 循环遍历 | 单向/双向遍历 |
| 长度计算 | O(n) | O(1) |
| 固定大小 | ✅ 适合 | ❌ 不适合 |
| 轮转调度 | ✅ 适合 | ❌ 不适合 |
选择指南
使用 Ring:
- ✅ 需要循环遍历
- ✅ 固定大小的缓冲区
- ✅ 轮转调度场景
- ✅ 时间片轮转
使用 List:
- ✅ 需要头尾操作
- ✅ 需要频繁插入删除
- ✅ 实现 LRU 缓存
- ✅ 动态大小的队列
🔥 总结
核心类型
| 类型 | 说明 |
|---|---|
| Ring | 环形链表节点 |
核心函数
| 函数 | 说明 | 时间复杂度 |
|---|---|---|
| ring.New(n) | 创建 n 个节点的环 | O(n) |
核心方法
| 方法 | 说明 | 时间复杂度 |
|---|---|---|
| Next() | 返回下一个节点 | O(1) |
| Prev() | 返回上一个节点 | O(1) |
| Move(n) | 移动 n 个位置 | O(|n|) |
| Unlink(n) | 删除 n 个节点 | O(n) |
| Link(s) | 连接环 s | O(1) |
| Len() | 计算长度 | O(n) |
| Do(f) | 执行函数 f | O(n) |
主要特点
- 环形结构 👉 没有真正的头尾
- 循环遍历 👉 可以从任意节点开始
- 固定大小 👉 适合缓冲区
- 任意类型 👉 存储 interface{}
使用场景
- 循环缓冲区 👉 固定大小缓冲
- 轮转调度 👉 Round Robin 调度
- 时间片轮转 👉 进程调度
- 固定缓存 👉 淘汰最旧数据
- 音频/视频 👉 流媒体缓冲
最佳实践
- ✅ 使用类型断言时检查类型
- ✅ 并发环境加锁保护
- ✅ 及时清理不需要的数据
- ✅ 根据场景选择合适的数据结构
- ⚠️ 注意:长度计算需要 O(n)
性能提示
- 遍历 👉 O(n)
- 移动 👉 O(|n|)
- 连接 👉 O(1)
- 长度计算 👉 O(n)
- 访问当前节点 👉 O(1)
container/ring 包提供了高效的环形链表实现,适合循环缓冲区、轮转调度等场景!