如果想连续地看待一组数据,可以使用 slice,优点是可以透过索引快速访问,透过append
也可以附加元素,若偶而需要安插、删除元素,可以透过切片等操作来实现。
然而,如果经常性地需要安插、删除元素,透过 slice 实现缺乏效率时,Go 提供了container/list
包,可让开发者基于双向链结的list.List
实现来达成需求。
想要创建list.List
实例,可以透过list.New
,实例可使用的方法有:
func (l *List) Back() *Element
func (l *List) Front() *Element
func (l *List) Init() *List
func (l *List) InsertAfter(v interface{}, mark *Element) *Element
func (l *List) InsertBefore(v interface{}, mark *Element) *Element
func (l *List) Len() int
func (l *List) MoveAfter(e, mark *Element)
func (l *List) MoveBefore(e, mark *Element)
func (l *List) MoveToBack(e *Element)
func (l *List) MoveToFront(e *Element)
func (l *List) PushBack(v interface{}) *Element
func (l *List) PushBackList(other *List)
func (l *List) PushFront(v interface{}) *Element
func (l *List) PushFrontList(other *List)
func (l *List) Remove(e *Element) interface{}
从PushBack
、PushFront
方法的参数类型interface{}
就能知道,list.List
可以保存任意类型的数据,它们会返回*Element
,Element
是个结构,公开的字段有Value
,公开的方法为Next
与Prev
:
type Element struct {
Value interface{}
}
func (e *Element) Next() *Element
func (e *Element) Prev() *Element
因此,若你保留返回的*Element
,可以透过Value
获取放入list.List
的值,必要时也可以透过Next
或Prev
方法,往后探寻下一元素或往前探寻前一元素,Next
与Prev
方法返回的也是*Element
,因此随时可以往前探寻元素前或后全部的清单。
Back
、Front
方法,分别返回list.List
最后、最前一个元素,因此,若要从清单头遍历至尾,基本的模式就是:
package main
import (
"fmt"
"container/list"
)
func printAll(lt *list.List) {
for e := lt.Front(); e != nil; e = e.Next() {
fmt.Println(e.Value)
}
}
func main() {
lt := list.New()
for i := 1; i <= 10; i++ {
lt.PushBack(i)
}
printAll(lt)
}
你可能会有问题,Element
的Value
类型是interface{}
,那么想操作保存的元素值上的字段、方法时,不就要知道类型吗?就目前来说,Go 不支持泛型,必须透过类型断言:
package main
import (
"fmt"
"container/list"
)
type Person struct {
Name string
Age int
}
func printAllPerson(persons *list.List) {
for e := persons.Front(); e != nil; e = e.Next() {
p := e.Value.(*Person)
fmt.Printf("姓名:%s\t年龄:%d\n", p.Name, p.Age)
}
}
func main() {
persons := list.New()
persons.PushBack(&Person{"Irene", 12})
persons.PushBack(&Person{"Justin", 45})
persons.PushBack(&Person{"Monica", 42})
printAllPerson(persons)
}
你可能还会有其他问题,例如list.List
怎么不支持索引?要怎么进行排序等?…唔…list.List
提供的方法怎么这么少?
严格来说,不会直接使用list.List
来保存数据,而是如果某数据结构底层需要双向链结的特性,可以透过list.List
来实现。例如,实现一个PersonQueue
:
package main
import (
"fmt"
"container/list"
)
type Person struct {
Name string
Age int
}
type PersonQueue struct {
list *list.List
}
func NewPersonQueue() *PersonQueue {
return &PersonQueue{list.New()}
}
func (q *PersonQueue) Len() int {
return q.list.Len()
}
func (q *PersonQueue) Offer(p *Person) {
q.list.PushBack(p)
}
func (q *PersonQueue) Peek() *Person {
if q.list.Len() == 0 {
return nil
}
e := q.list.Remove(q.list.Front())
return e.(*Person)
}
func main() {
q := NewPersonQueue()
q.Offer(&Person{"Irene", 12})
q.Offer(&Person{"Justin", 45})
q.Offer(&Person{"Monica", 42})
for p := q.Peek(); p != nil; p = q.Peek() {
fmt.Printf("姓名:%s\t年龄:%d\n", p.Name, p.Age)
}
}
因此,并不是list.List
不常用,而是你可能很少自行实现数据结构(都拿别人写好的来用?);另一种说法「每当想使用list.List
时,都该思考一下是否优先使用 slice。」的说法也不是完全正确…
若想使用list.List
,应该问的是,你的数据结构在实现上需要双向链结的特性吗?例如,也许你会需要有个具索引的数据结构,同时底层实现必须是双向链结(像是 Java 的LinkedList
)?那么就可以考虑透过list.List
来实现。