golang中container/list包
Go 标准包container中包含了常用的容器类型 包括
conatiner/list
container/heap
container/ring
本篇介绍 Go标准容器 conatiner/list
conatiner/list实现了一个双向链表 该结构具有链表的所有功能
type Element
type Element struct {
Value interface{} //在元素中存储的值
}
func (e *Element) Next() *Element //返回该元素的下一个元素 如果没有下一个元素则返回nil
func (e *Element) Prev() *Element//返回该元素的前一个元素 如果没有前一个元素则返回nil
type List
func New() *List //返回一个初始化的list
func (l *List) Back() *Element //获取list l的最后一个元素
func (l *List) Front() *Element //获取list l的第一个元素
func (l *List) Init() *List //list l初始化或者清除list l
func (l *List) InsertAfter(v interface{}, mark *Element) *Element //在list l中元素mark之后插入一个值为v的元素 并返回该元素 如果mark不是list中元素 则list不改变
func (l *List) InsertBefore(v interface{}, mark *Element) *Element//在list l中元素mark之前插入一个值为v的元素 并返回该元素 如果mark不是list中元素 则list不改变
func (l *List) Len() int //获取list l的长度
func (l *List) MoveAfter(e, mark *Element) //将元素e移动到元素mark之后 如果元素e或者mark不属于list l 或者e==mark 则list l不改变
func (l *List) MoveBefore(e, mark *Element)//将元素e移动到元素mark之前 如果元素e或者mark不属于list l 或者e==mark 则list l不改变
func (l *List) MoveToBack(e *Element)//将元素e移动到list l的末尾 如果e不属于list l 则list不改变
func (l *List) MoveToFront(e *Element)//将元素e移动到list l的首部 如果e不属于list l 则list不改变
func (l *List) PushBack(v interface{}) *Element//在list l的末尾插入值为v的元素 并返回该元素
func (l *List) PushBackList(other *List)//在list l的尾部插入另外一个list 其中l和other可以相等
func (l *List) PushFront(v interface{}) *Element//在list l的首部插入值为v的元素 并返回该元素
func (l *List) PushFrontList(other *List)//在list l的首部插入另外一个list 其中l和other可以相等
func (l *List) Remove(e *Element) interface{}//如果元素e属于list l 将其从list中删除 并返回元素e的值
conatiner/list用法举例
package mainimport (
"container/list"
"fmt"
)
func main() {
l := list.New() //创建一个新的list
for i := 0; i < 5; i++ {
l.PushBack(i)
}
for e := l.Front(); e != nil; e = e.Next() {
fmt.Print(e.Value) //输出list的值,01234
}
fmt.Println("")
fmt.Println(l.Front().Value) //输出首部元素的值,0
fmt.Println(l.Back().Value) //输出尾部元素的值,4
l.InsertAfter(6, l.Front()) //首部元素之后插入一个值为10的元素
for e := l.Front(); e != nil; e = e.Next() {
fmt.Print(e.Value) //输出list的值,061234
}
fmt.Println("")
l.MoveBefore(l.Front().Next(), l.Front()) //首部两个元素位置互换
for e := l.Front(); e != nil; e = e.Next() {
fmt.Print(e.Value) //输出list的值,601234
}
fmt.Println("")
l.MoveToFront(l.Back()) //将尾部元素移动到首部
for e := l.Front(); e != nil; e = e.Next() {
fmt.Print(e.Value) //输出list的值,460123
}
fmt.Println("")
l2 := list.New()
l2.PushBackList(l) //将l中元素放在l2的末尾
for e := l2.Front(); e != nil; e = e.Next() {
fmt.Print(e.Value) //输出l2的值,460123
}
fmt.Println("")
l.Init() //清空l</span>
fmt.Print(l.Len()) //0
for e := l.Front(); e != nil; e = e.Next() {
fmt.Print(e.Value) //输出list的值,无内容
}
}
GO中container/list包 可能的坑
list包 对于e *Element操作的元素 可能导致程序崩溃原因 e是一个Element类型的指针 也可能为nil
但golang中list包 函数 没有对其进行是否为nil的检查
默认其非nil进行操作 所以 可能出现程序崩溃
例子 Remove 函数
package main
import (
"container/list"
"fmt"
)
func main() {
l := list.New()
l.PushBack(1)
fmt.Println(l.Front().Value) //1
value := l.Remove(l.Front())
fmt.Println(value) //1
value1 := l.Remove(l.Front()) //panic: runtime error: invalid memory address or nil pointer dereference
fmt.Println(value1)
}
程序崩溃 原因是list中只有1个元素 但是删除2个元素
进一步查看一下原因
golang中Front()函数实现
func (l *List) Front() *Element {
if l.len == 0 {
return nil
}
return l.root.next
}
第一次删除 list的长度变为0
此时 调用l.Remove(l.Front()) 其中l.Front()返回的是一个nil
golang中Remove函数实现 该函数 没有判定e是否为nil
直接默认其为非nil 对其进行e.list或者e.Value取值操作
当e为nil时 这两个操作都将会造成程序崩溃 就是为什么程序会崩溃的原因
func (l *List) Remove(e *Element) interface{} {
if e.list == l {
// if e.list == l, l must have been initialized when e was inserted
// in l or l == nil (e is a zero Element) and l.remove will crash
l.remove(e)
}
return e.Value
}
函数 PushBackList
(l *list)PushBackList(other *list)该函数用于将other list中元素添加在l list的后面
实现思想是取出other中所有元素
将其顺次挂载在l列表中 但是golang中实现有问题
func (l *List) PushBackList(other *List) {
l.lazyInit()
for i, e := other.Len(), other.Front(); i > 0; i, e = i-1, e.Next() {
l.insertValue(e.Value, l.root.prev)
}
}
首先获取 other 长度n 然后循环n次取出其元素将其插入l中
问题就出现在循环n次 如果在这个过程中other的元素变化
其中有些元素被删除了 这就导致e的指针可能为nil
此时再利用e.Value取值 程序便会崩溃
package main
import (
"container/list"
"runtime"
)
func main() {
runtime.GOMAXPROCS(8)
l := list.New()
ls := list.New()
for i := 0; i < 10000; i++ {
ls.PushBack(i)
}
go ls.Remove(l.Back())
l.PushBackList(ls) //invalid memory address or nil pointer dereference
}
ls中元素添加到l过程中 如果ls中元素减少 程序便会崩溃
建议在golang中如果对与list的操作只有串行操作
则只需要注意检查元素指针是否为nil便可避免程序崩溃
如果程序中会并发处理list中元素 建议对list进行加写锁(全局锁) 然后再操作
注意 读写锁无法保证并行处理list时程序的安全性
golang slice 与list 的性能分析
数据很多 频繁的插入和删除用list频繁的遍历查询选slice
尊贵的董事大人
英文标题不为空时 视为本栏投稿
需要关键字 描述 英文标题