www.zhblog.net

list 包


如果想连续地看待一组数据,可以使用 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{}

PushBackPushFront方法的参数类型interface{}就能知道,list.List可以保存任意类型的数据,它们会返回*ElementElement是个结构,公开的字段有Value,公开的方法为NextPrev

type Element struct {
    Value interface{}
}

func (e *Element) Next() *Element

func (e *Element) Prev() *Element

因此,若你保留返回的*Element,可以透过Value获取放入list.List的值,必要时也可以透过NextPrev方法,往后探寻下一元素或往前探寻前一元素,NextPrev方法返回的也是*Element,因此随时可以往前探寻元素前或后全部的清单。

BackFront方法,分别返回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)
}

你可能会有问题,ElementValue类型是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来实现。


展开阅读全文

评论

Hi,您需要填写昵称和邮箱!

  • 昵称 (必填)
  • 邮箱 (必填)
  • 心情