list为golang中的双向链表实现,存入list中的元素都会被封装成list中的节点放到双向链表中进行存储。
简单的list使用代码如下:
func main() {
list := list.New()
fmt.Println("list length is " + strconv.Itoa(list.Len()))
list.PushBack(1)
list.PushBack(2)
list.PushBack(3)
fmt.Println("list length is " + strconv.Itoa(list.Len()))
for i := list.Front(); i != nil; i = i.Next() {
fmt.Println(i.Value)
}
for i := list.Front(); i != nil; i = i.Next() {
if i.Value == 2 {
list.Remove(i)
}
}
fmt.Println("***************")
for i := list.Front(); i != nil; i = i.Next() {
fmt.Println(i.Value)
}
}
在上述代码中,简单的完成了对于list的初始化,元素的添加,list的遍历与元素的移除。可以看到,基于list的遍历都是基于元素的指针进行,这里的操作类似java中的LinkedList。
list的节点type Element struct {
next, prev *Element
list *List
Value interface{}
}
从上面的代码可以看到,在list中,单个节点会被封装成Element存储在队列中,其中next与prev为节点前后的指针,list为该节点所处的list指针,value则来存放具体的值。这里的实现是与java中的LinkedList极为相似的,在java中的LinkedList,每个节点也被封装成了Node存储在链表中。
list本身的结构type List struct {
root Element
len int
}
在List自身的结构体中,root作为根节点,虽然称为根节点,但是在具体的双向链表中,root节点实则将该双向链表连成了一个圈,通过其next可以得到链表中的首节点,而通过其prev则可以得到其尾节点,这里与java的LinkedList维护首尾两个节点的做法不同,更加高效。同时,只需要从根节点的next出发即可完成对于双向链表的遍历,直到通过next到达根节点为止。
而len则记录了当前双向链表的长度,当需要得到当前链表的长度的时候只需要返回len即可,同样在java的LinkedLis中也通过size记录了当前链表的长度。
func (l *List) Init() *List {
l.root.next = &l.root
l.root.prev = &l.root
l.len = 0
return l
}
list的初始化非常简单,将根节点的前后指针都指向自己,并初始化长度为0即可/
list的插入与移除当双向链表中向链表尾部插入元素的时候,如上文所说,由于list中通过根节点的prev一直维护着尾节点,只需要将插入的值包装为list中的节点后插入到根节点的prev之前即可。
func (l *List) PushBack(v interface{}) *Element {
l.lazyInit()
return l.insertValue(v, l.root.prev)
}
func (l *List) insert(e, at *Element) *Element {
e.prev = at
e.next = at.next
e.prev.next = e
e.next.prev = e
e.list = l
l.len++
return e
}
func (l *List) insertValue(v interface{}, at *Element) *Element {
return l.insert(&Element{Value: v}, at)
}
同样的,当需要将元素插入到队列的头部的时候,只需要将其包装成节点后插入到根节点的next即可。
而与java的LinkedList不同的是,当需要移除list当中的节点的时候,必须要传入需要移除的list中的节点才行。
func (l *List) Remove(e *Element) interface{} {
if e.list == l {
l.remove(e)
}
return e.Value
}
func (l *List) remove(e *Element) *Element {
e.prev.next = e.next
e.next.prev = e.prev
e.next = nil
e.prev = nil
e.list = nil
l.len--
return e
}
相较于java的做法,这样的移除方式虽然存在一定的局限性,但是首先,由于节点本身存在指向所处list的指针,可以避免在错误的队列中移除对应的元素,同时不需要在移除前对整个链表进行遍历(当然在获取该节点的时候已经遍历过了),只需要将前后节点相连并将其next,pre置nil来防止内存泄漏即可。