container/list 不适合高频增删或按索引访问,它是无随机访问能力的双向链表;应优先使用切片或第三方数组列表。

别用 container/list 做高频增删或按索引访问——它不是你要的“链表”。 它是双向链表,但没有随机访问能力,不支持下标,遍历慢,且接口设计反直觉;真正需要的是切片、slice 或第三方结构(如 github.com/emirpasic/gods/lists/arraylist)。
为什么 container/list 的 Front() 和 Back() 返回的是 *List 而不是元素值?
因为 Front() 和 Back() 返回的是 *list.Element,不是值本身。这个类型里封装了 Value 字段,但必须手动解包:
l := list.New()
l.PushBack("hello")
elem := l.Front() // 类型是 *list.Element
if elem != nil {
fmt.Println(elem.Value) // 输出 "hello"
}
-
elem.Value是interface{},用前务必类型断言,比如elem.Value.(string) - 如果链表为空,
Front()/Back()返回nil,直接取.Value会 panic - 没有
Get(i int)方法——想按位置取值?只能从头/尾开始Next()/Prev()遍历
PushFront / PushBack 之后怎么拿到刚插入的元素指针?
这两个方法返回的是新创建的 *list.Element,不是 *List。这是唯一能直接拿到节点引用的方式:
Go 配置库,使用 spf13/viper — 分层优先级(flag > env >file > KV > default),提供 BindPFlag/BindPFlags、SetEnvPrefix + SetEnvKeyReplace 等功能。
l := list.New()
e1 := l.PushBack(42) // e1 是 *list.Element
e2 := l.PushFront("hi") // e2 是 *list.Element
fmt.Println(e1.Value, e2.Value) // 42 hi
- 必须用返回值接收,否则无法后续操作该节点(比如
InsertAfter、Remove) -
PushBack插入到尾部,但返回的Element不代表“最后一个”,只是刚插入的那个节点 - 若要反复在某节点后插入,保存好那个
*list.Element,别依赖l.Back()——后者可能已被其他操作改变
用 Remove 删除节点时常见 panic 场景
最常触发 panic 的是传入了 nil 指针,或重复删除同一节点:
立即学习“go语言免费学习笔记(深入)”;
e := l.PushBack(100) l.Remove(e) l.Remove(e) // panic: runtime error: invalid memory address or nil pointer dereference
-
Remove不检查节点是否已从链表中移除,也不校验是否属于当前*List - 删除后
e.Next()和e.Prev()变为nil,但e.Value仍可读(未清空) - 安全做法:删除后立即将变量置为
nil,或用if e != nil { l.Remove(e); e = nil } - 遍历时删除需格外小心:不能边
for e := l.Front(); e != nil; e = e.Next()边调用l.Remove(e),否则e.Next()已失效;应先保存next := e.Next()再删
真正难的不是写对语法,而是意识到 container/list 的定位:它只为极少数需要稳定节点地址、频繁在任意位置插入/删除、且不关心索引和性能的场景服务(比如实现 LRU 缓存的骨架)。日常开发中,95% 的“我需要一个链表”实际想要的是带扩容能力的 []T,或者一个封装了查找逻辑的 map+slice 组合。

















