Go realizes bidirectional linked list


This paper introduces what is a linked list and what are the common linked lists. Then it introduces where the linked list data structure can be used, and the redis queue is the underlying implementation. Through a small example, it demonstrates the functions of redis queue, and finally realizes a bidirectional linked list through go.

Go realizes bidirectional linked list


  • 1. Linked list

    • 1.1 description
    • 1.2 one way linked list
    • 1.3 circular linked list
    • 1.4 double linked list
  • 2. Redis queue

    • 2.1 description
    • 2.2 application scenarios
    • 2.3 demonstration
  • 3. Go double linked list

    • 3.1 description
    • 3.2 implementation
  • 4. Summary
  • 5. References

1. Linked list

1.1 description

Go realizes bidirectional linked list

Linked list is a common basic data structure. It is a kind of linear table. However, it does not store data in linear order. Instead, it stores the pointer to the next node in each node. Because it does not have to be stored in order, a linked list can achieve o (1) complexity when inserted, which is much faster than another linear sequential list. However, it takes O (n) to find a node or access a node with a specific number. The corresponding time complexity of the sequential list is O (logn) and O (1), respectively.

There are many different types of linked lists: one-way lists, two-way lists and circular lists.

  • Advantages:

It can overcome the shortcoming that the array linked list needs to know the data size in advance. The linked list structure can make full use of the computer memory space and realize flexible dynamic memory management. Linked lists allow you to insert and remove nodes at any location on the list.

  • inferiority:

Because the node pointer is added to the linked list, the space cost is relatively large. Generally, when looking for data in a linked list, you need to start from the first node and visit the next node each time until you get to the required location. It is slow to find data.

  • Purpose:

It is often used to organize and retrieve less data, but delete, add and traverse more data.

For example: file system, LRU cache, redis list, memory management, etc.

1.2 one way linked list

The simplest kind of linked list is one-way list,

The nodes of a one-way linked list are divided into two parts. It contains two fields, an information field and a pointer field. The first part stores or displays information about the node, the second part stores the address of the next node, and the last node points to a null value. One way linked list can only traverse in one direction.

A single linked list has a head node head, which points to the first address of the linked list in memory. The data type of each node in the linked list is structure type, and the node has two members: integer member (the actual data to be saved) and the pointer to the next structure type node, that is, the address of the next node (in fact, this single linked list is a dynamic array used to store integer data). According to this structure, the access to each node of the linked list needs to start from the head of the list, and the address of the subsequent node is given by the current node. No matter which node is accessed in the table, you need to start from the head of the linked list and search backward. Since there is no subsequent node at the end of the linked list, its pointer field is null, which is written as null.

1.3 circular linked list

The circular linked list is a kind of chain storage structure, just like the one-way list. The difference is that the pointer of the last node of the circular list points to the first node or header node of the circular list, thus forming a circular chain.

The operation of circular linked list is basically consistent with that of single linked list. The differences are as follows:

1. When establishing a circular linked list, the pointer of the last node must point to the header node, instead of being set to null like the single linked list.

2. When judging whether to reach the end of the table, it is to judge whether the value of the chain field of the node is the header node. When the value of the chain field is equal to the header pointer, it indicates that the tail of the table has been reached. Instead of judging whether the value of the chain field is null as in a single linked list.

1.4 double linked list

Go realizes bidirectional linked list

The bidirectional linked list is actually an improvement of the single linked list. When we operate the single linked list, sometimes you have to search from the header when you want to operate the direct precursor of a node. This is limited by the structure of the node of the single chain list. Since each node of a single chain list has only one chain field to store the address of the direct successor node, can we define a double chain domain node structure with both the chain field storing the address of the direct successor node and the chain domain storing the address of the direct predecessor node? This is the double linked list.

In a bidirectional linked list, a node has two chain domains besides the data domain. One stores the address of the direct successor node, which is generally called the right chain domain (when this “connection” is the last “connection”, it points to null value or empty list); the other stores the address of the direct predecessor node, which is generally called the left chain domain (when the “connection” is the first “connection”, it points to null value) Or an empty list).

2. Redis queue

2.1 description

Redis list is a simple list of strings, sorted in insertion order. You can add an element to the head (left) or tail (right) of the list

Redis list uses two data structures as the underlying implementation: linked list and zip list

Select the implementation mode through the configuration file (list Max ziplist entries, list Max ziplist value)

When the amount of data is relatively small, there is no significant difference between the performance of double ended linked list and compressed list, but the use of compressed list can save more memory space

Redis Src / adlist. H

2.2 application scenarios

Message queue, seckill project

Seckill project:

The required commodity code information is stored in the redis queue in advance. During the rush purchase, each user gets the commodity code from the redis queue. Because redis is single threaded, only one commodity code can be taken out at the same time. The user who gets the commodity code is successful in the purchase, and the redis performance is relatively high, and it can resist greater user pressure.

2.3 demonstration

How to prevent the oversold of goods in concurrent situation through redis queue.


The website has three products to sell, so we put the data into the redis queue

1. Store three commodity codes (10001, 10002, 10003) into redis queue

#Goods deposited
RPUSH commodity:queue 10001 10002 10003

2. After saving, query whether the data meets the expectation

#View all elements
LRANGE commodity:queue 0 -1

#View the length of the queue
LLEN commodity:queue

3. At the beginning of the rush purchase, the user who grabs the commodity code can buy it (since redis is a single thread, the same commodity code can only be retrieved once

#Out of the team
LPOP commodity:queue

Here we learn how to use the redis list. Next, we use go language to implement a bidirectional linked list to realize these functions.

3. Go double linked list

3.1 description

Here, we only use go language to implement a bidirectional linked list, which can query the length of the linked list, insert data at the right end of the list, fetch data at the left end, and fetch nodes in the specified interval (similar to rpush, lrange, lpop, llen functions in the redis list).

3.2 implementation

Go realizes bidirectional linked list

  • Node definition

A bidirectional linked list has two pointers to the previous node and the next node

List headerprevThe pointer of the linked list is nullnextThe pointer to is null

//A node of a linked list
type ListNode struct {
    Prev * listnode // previous node
    Next * listnode // next node
    Value string // data

//Create a node
func NewListNode(value string) (listNode *ListNode) {
    listNode = &ListNode{
        value: value,


//The previous node of the current node
func (n *ListNode) Prev() (prev *ListNode) {
    prev = n.prev


//The previous node of the current node
func (n *ListNode) Next() (next *ListNode) {
    next = n.next


//Gets the value of the node
func (n *ListNode) GetValue() (value string) {
    if n == nil {

    value = n.value

  • Define a linked list

In order to facilitate the operation of linked list, a structure body is defined, which can be accessed directly from the header and the tail, and an attribute is definedlenIt can directly return the length of the linked list, and directly query the length of the linked list without traversing the time complexity from O (n) to o (1).

//Linked list
type List struct {
    Head * listnode // header node
    Tail * listnode // tail node
    len  int       //Linked list的长度

//Create an empty linked list
func NewList() (list *List) {
    list = &List{

//Header chain return node
func (l *List) Head() (head *ListNode) {
    head = l.head


//Back to the end of the list
func (l *List) Tail() (tail *ListNode) {
    tail = l.tail


//Return list length
func (l *List) Len() (len int) {
    len = l.len

  • Insert an element to the right of the linked list
//Insert an element to the right of the linked list
func (l *List) RPush(value string) {

    node := NewListNode(value)

    //When the list is not empty
    if l.Len() == 0 {
        l.head = node
        l.tail = node
    } else {
        tail := l.tail
        tail.next = node
        node.prev = tail

        l.tail = node

    l.len = l.len + 1

  • Take a node from the left side of the list
//Take a node from the left side of the list
func (l *List) LPop() (node *ListNode) {

    //Data is empty
    if l.len == 0 {


    node = l.head

    if node.next == nil {
        //The linked list is not empty
        l.head = nil
        l.tail = nil
    } else {
        l.head = node.next
    l.len = l.len - 1

  • Find nodes by index

The node is found by the index. If the index is a negative number, the search starts at the end of the table.

Natural number index and negative index search nodes through two ways respectively. Find the specified index or linked list, and the search is completed when all the search is completed.

//Find nodes by index
//If the node cannot be found, it will return null
func (l *List) Index(index int) (node *ListNode) {

    //If the index is negative, the tail of the table is searched
    if index < 0 {
        index = (-index) - 1
        node = l.tail
        for true {
            //Not found
            if node == nil {


            //We got the data
            if index == 0 {


            node = node.prev
    } else {
        node = l.head
        for ; index > 0 && node != nil; index-- {
            node = node.next

  • Returns the element of the specified interval
//Returns the element of the specified interval
func (l *List) Range(start, stop int) (nodes []*ListNode) {
    nodes = make([]*ListNode, 0)

    //Convert to natural number
    if start < 0 {
        start = l.len + start
        if start < 0 {
            start = 0

    if stop < 0 {
        stop = l.len + stop
        if stop < 0 {
            stop = 0

    //Number of intervals
    rangeLen := stop - start + 1
    if rangeLen < 0 {


    startNode := l.Index(start)
    for i := 0; i < rangeLen; i++ {
        if startNode == nil {

        nodes = append(nodes, startNode)
        startNode = startNode.next


4. Summary

  • At this point, the use of linked list has been finished. What are the linked lists (one-way linked list, two-way linked list and circular linked list) are introduced. The application scenarios of linked list (redis list uses the linked list as the underlying implementation) are also introduced. Finally, the bi-directional linked list is implemented with go, which demonstrates the linked list in go How to use the language in the project, you can use it in a more practical way.

5. References

Wikipedia linked list

github redis

Project address: go implementation queue


Recommended Today

Oracle scheduled tasks

Timing task query To query Oracle scheduled tasks, you can use: –Scheduled tasks for all users SELECT * FROM dba_jobs; –Timing task of the user select * from user_jobs; In the query results, the what field generally stores the name of the stored procedure (or the specific stored procedure content). Broken = n indicates that […]