This is a creation in Article, where the information may have evolved or changed.
See the TCP/IP protocol stack code today when you see a doubly linked list, linked list? Listen to its top name, know that it is composed of nodes, each node also has a pointer to the next node, but never realized one, no practice can not understand deeply, hence the article.
All of the following views are personal humble opinion, with different suggestions or additions to the Welcome emial I Aboutme
What is a linked list?
A linked list (Linked list) is a common basic data structure, a linear table, but does not store data in a linear order, but rather as a pointer to the next node (Pointer) in each node. Since they do not have to be stored sequentially, the list can achieve an O (1) complexity at the time of insertion, much faster than another linear table-order table, but the time to find a node or to access a particular number of nodes requires O (n), while the corresponding time complexity of the sequential table is O (logn) and O (1) respectively.
Simply put, a linked list is a linear table with a logical order, each node being a pointer to the next node.
Icon
Single linked list
Doubly linked list
What's the use of linked lists?
Because the list is inserted very quickly, and dynamic, want to add a few elements to add a few (enough memory space), not as rigid as the array, because of the flexibility of the list, all the use of the list is big AH.
A linked list is best suited for Frequent update changes data, such as a sequence of commands that need to be executed asynchronously and cannot be lost, a driver that needs to be loaded and unloaded in real time, and an unordered and unknown number, which requires a list structure to assist in the management of the data. If you don't need to be overly concerned about the order of your data, you can easily and quickly insert or delete an element in any place with a linked list, without affecting other elements.
Or I'm looking at the TCP/IP source today, the list is used to construct the queue as a queue for the data segment. I think the list should be the most used for queues. If you look at the Linux kernel source code, you should find that the Linux kernel uses a list of the same structure in many places.
Go standard Library's doubly linked list
The Golang standard library implements a doubly linked list, which can store any data, first looking at examples using the standard library chain list:
package list_test
import (
"container/list"
"fmt"
"testing"
)
func TestList(t *testing.T) {
// Create a new list and put some numbers in it.
l := list.New()
e4 := l.PushBack(4)
e1 := l.PushFront(1)
l.InsertBefore(3, e4)
l.InsertAfter(2, e1)
// Iterate through list and print its contents.
for e := l.Front(); e != nil; e = e.Next() {
fmt.Println(e.Value)
}
}
// output
// 1
// 2
// 3
// 4
The linked list realizes all the functions of the linked list, the list of additions and deletions to check and change.
Implement the data structure of the linked list
// List represents a doubly linked list.
// The zero value for List is an empty list ready to use.
type List struct {
root Element // sentinel list element, only &root, root.prev, and root.next are used
len int // current list length excluding (this) sentinel element
}
// Element is an element of a linked list.
type Element struct {
// Next and previous pointers in the doubly-linked list of elements.
// To simplify the implementation, internally a list l is implemented
// as a ring, such that &l.root is both the next element of the last
// list element (l.Back()) and the previous element of the first list
// element (l.Front()).
next, prev *Element
// The list to which this element belongs.
list *List
// The value stored with this element.
Value interface{}
}
You can see that the struct sees the structure of theElementlinked listnext,prevpointing to the pointer to the next and previous element, respectively.Valueis the data segment in the linked list, which can be understood as an object in.
Intrusive linked list (intrusive list)
The list of the front is the ordinary linked list, remember that the list is the<<c language programming>>same, that is, the node pointer and data segment of the list is placed in the same struct, each implementation of a different struct will have to re-implement the linked list function, which for "lazy" programmer is intolerable, So it came out. An intrusive list that distinguishes between data segments and linked list functions. The most classic example of Linux kernel list_head, see the link klist or Linked list in Linux Kernel,linux is implemented in C, I want to use go to implement an intrusive list.
Implementation code
package list
type Intrusive interface {
Next() Intrusive
Prev() Intrusive
AddNext(Intrusive)
AddPrev(Intrusive)
}
// List provides the implementation of intrusive linked lists
type List struct {
prev Intrusive
next Intrusive
}
func (l *List) Next() Intrusive {
return l.next
}
func (l *List) Prev() Intrusive {
return l.prev
}
func (l *List) AddNext(i Intrusive) {
l.next = i
}
func (l *List) AddPrev(i Intrusive) {
l.prev = i
}
func (l *List) Reset() {
l.prev = nil
l.next = nil
}
func (l *List) Empty() bool {
return l.prev == nil
}
// Front returns the first element of list l or nil.
func (l *List) Front() Intrusive {
return l.prev
}
// Back returns the last element of list l or nil.
func (l *List) Back() Intrusive {
return l.next
}
// PushFront inserts the element e at the front of list l.
func (l *List) PushFront(e Intrusive) {
e.AddPrev(nil)
e.AddNext(l.prev)
if l.prev != nil {
l.prev.AddPrev(e)
} else {
l.next = e
}
l.prev = e
}
// PushBack inserts the element e at the back of list l.
func (l *List) PushBack(e Intrusive) {
e.AddNext(nil)
e.AddPrev(l.next)
if l.next != nil {
l.next.AddNext(e)
} else {
l.prev = e
}
l.next = e
}
// InsertAfter inserts e after b.
func (l *List) InsertAfter(e, b Intrusive) {
a := b.Next()
e.AddNext(a)
e.AddPrev(b)
b.AddNext(e)
if a != nil {
a.AddPrev(e)
} else {
l.next = e
}
}
// InsertBefore inserts e before a.
func (l *List) InsertBefore(e, a Intrusive) {
b := a.Prev()
e.AddNext(a)
e.AddPrev(b)
a.AddPrev(e)
if b != nil {
b.AddNext(e)
} else {
l.prev = e
}
}
// Remove removes e from l.
func (l *List) Remove(e Intrusive) {
prev := e.Prev()
next := e.Next()
if prev != nil {
prev.AddNext(next)
} else {
l.prev = next
}
if next != nil {
next.AddPrev(prev)
} else {
l.next = prev
}
}
We use theListexpression to implement theIntrusiveinterface, but also to achieve the basic function of the list, so any object that implements theIntrusiveinterface can be used as a link list node, the use of this intrusive list is very simple, as long as the existing struct embedded in theListstructure can be, In an example:
package list
import (
"fmt"
"testing"
)
func TestIntrusiveList(t *testing.T) {
type E struct {
List
data int
}
// Create a new list and put some numbers in it.
l := List{}
e4 := &E{data: 4}
e1 := &E{data: 1}
l.PushBack(e4)
l.PushFront(e1)
l.InsertBefore(&E{data: 3}, e4)
l.InsertAfter(&E{data: 2}, e1)
for e := l.Front(); e != nil; e = e.Next() {
fmt.Printf("e: %+v\n", e)
fmt.Printf("data: %d\n", e.(*E).data)
}
}
// output
// e: &{List:{prev:<nil> next:0xc4200789c0} data:1}
// data: 1
// e: &{List:{prev:0xc420078960 next:0xc420078990} data:2}
// data: 2
// e: &{List:{prev:0xc4200789c0 next:0xc420078930} data:3}
// data: 3
// e: &{List:{prev:0xc420078990 next:<nil>} data:4}
// data: 4
In theEembeddedListcan be used as a linked list of nodes, is not very convenient, in fact, when I finished writing the chain list of chestnuts, found in fact the standard library of the list more convenient, haha. Because Golang hasinterface{}.
Reference
https://blog.goquxiao.com/posts/2013/07/06/intrusive-list/
http://blog.nlogn.cn/linked-list-in-linux-kernel/