This is a creation in Article, where the information may have evolved or changed.
Originally intended to use go to implement the common Java collection, simple implementation of ArrayList, after the official website of the package, found container/list
that its implementation is very concise, so the study record as follows:
List Implementation Readiness
If you want to implement a list, first think of solving the problem:
- What is data type handling?
- Does the go have a universal data type like object in Java?
Thanks interface{}
to this versatile data type in go, the problem is solved!
Take a look at my ArrayList
implementation design:
type ArrayList struct { size int32 data []interface{}}
I use slice
it to implement a simple list
(one-way list) operation, and then look at the implementation of the official website:
type Element struct { prev, next *Element list *List Value interface{}}type List struct { root Element len int}
Haha, such a contrast, I am a little shy! The official network is more object-oriented, the List
elements are abstracted into Element
, Element
coexist in their own read before and after the node method.
Get operation in Element
- Gets the value of itself
- Get precursor node
- Get subsequent nodes
One of the features of Go is that its access rights are distinguished by the first letter case, the element can get its value directly, and the front and back nodes provide a method:
func (e *Element) Next() *Element { if p := e.next; e.list != nil && p != &e.list.root { return p } return nil}func (e *Element) Prev() *Element { if p := e.prev; e.list != nil && p != &e.list.root { return p } return nil}
No wonder, why the type is *Element
? Of course, it's changing its value!
The pointer passes the object's reference, not the pointer to the object's copy, and the pointer uses the following rule:
- You must use pointers whenever you need to modify an object, which is not a go language constraint, but a natural constraint.
- Sometimes the object is small and it's not worth the use of pointers.
Initialization of the list
List
By invoking the New()
method to initialize a list, the New()
method implements the following code, which Init()
root.next
root.prev
all points to root
, which will pave the way for the following implementation
//Init initializes or clears list lfunc (l *List) Init() *List { l.root.next = &l.root // next ---> root l.root.prev = &l.root // next ---> root //l.root.list = l // l.len = 0 return l}func New() *List { //new 申请内存初始化 list := new(List) return list.Init()}
New()
after execution, a similar {{0x000001, 0x000001, nil, nil}, 0} object (0x000001 is just a chestnut) is generated, but this address is element.list, which guarantees consistency in the list.
Storage operations in the list
The following methods are stored in the list:
- Func (L List) InsertAfter (v interface{}, Mark Element) *element: Adding elements after the mark element
- Func (L List) insertbefore (v interface{}, Mark Element) *element: Adding elements before the Mark element
- Func (L List) pushback (v interface{}) element: Adding elements at the end of the list
- Func (L list) pushbacklist ( other list): Add element list at the end of list
- Func (L List) Pushfront (v interface{}) element: Adding elements to the List header
- Func (L list) pushfrontlist ( other list): Adds an element list to the list header
As these public methods are built on insert(e, at *Element)和insertValue(v interface{}, at *Element)
the method, the code is as follows:
//insert e after atfunc (l *List) insert(e, at *Element) *Element { n := at.next at.next = e e.prev = at e.next = n n.prev = e e.list = l l.len++ fmt.Println("l.root.prev:", l.root.prev, " l.root.next:", l.root.next) return e}func (l *List) insertValue(v interface{}, at *Element) *Element { //创建新的节点 return l.insert(&Element{Value: v}, at)}
It is important to note that the insert(e, at *Element)
implementation is to put E at the back of the, which is helpful in understanding the following code.
Attached PushBack(v interface{})
Flowchart:
Pushback.png
As the PushFront(v interface{})
execution process is the PushBack(v interface{})
opposite, the following figure is attached, and the difference is known by careful observation:
Pushfront.png
Assuming that the above element addresses are 0x001,0x002,0x003, 2 and 3 are placed in the list, respectively.
The implementation of the above method is very simple, all in the establishment of "Insertvalue (v interface{}, at *element)" above, so do not paste the code in the article!
Get operation in List
- Func (L List) back () Element: Get last Node
- Func (L List) Front () Element: Gets the first node
Initially initialized node, is to hold the head node and tail nodes pointer, so the Back()
operation returns its L.root.prev, the Front()
operation returns its l.root.next to be done!
Delete operation in list
- Func (L List) Remove (e Element) interface{}
However, the above method is called internally remove(e *Element)
, and the code is as follows:
func (l *List) remove(e *Element) *Element { e.prev.next = e.next e.next.prev = e.prev //avoid memory leaks e.prev = nil e.next = nil e.list = nil l.len-- return e}
The function of this method is to modify the precursor and successor nodes that need to delete the nodes , and finally, the front and back node pointers are empty to prevent memory anomalies.
Move operations in the list
- 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)
So the mobile operation, all with the help of insertValue(v interface{}, e *Element)
and method to do remove(e *Element)
, the use of is really ingenious, the specific view of the source bar!
Note: The list is not a co-process safe.
Overall, by flipping through the list source, mastered its code implementation of the list storage structure, more experience of the Ood thought! (This article is mainly the two pictures above)
Reference documents
- https://golang.org/pkg/container/list/#pkg-overview
- Https://golang.org/src/container/list/list.go?s=922:955#L21