Java Collection System LinkedList Features and Internal Analysis of Common Methods

LinkedList

  1. features
    1. Allow null values
    2. Internally save the elements in the collection in the form of a doubly linked list, the query is slow, and the addition and deletion are fast (compared to ArrayList, there are fewer array copies)
    3. thread unsafe
    4. All operations at the specified position are traversed from the beginning
    5. The elements are ordered, and the output order is the same as the input order

2. Theory and practice are equally important, and knowing why is also necessary

LinkedList query is slow, adding and deleting fast is opposite to ArrayList, but at the same time it is not thread safe

First analyze its construction method

//空参构造(使用最多)
public LinkedList() {
    
    
    }
//有参构造
public LinkedList(Collection<? extends E> c) {
    
    
        this();
        addAll(c);//添加所有集合元素
    }

Source code of analysis method realization principle

1. Add method one

  • Principle:
    First look at how to add a one-way linked list:
    click here to operate and observe
    insert image description here
    each time an element is added, the last element node will point to the newly added element (2 points to 3), and I am used to calling it the successor set node of the previous element. This element
    is the same. In java, because it is a doubly linked list, there is another node. I am used to calling it the precursor node of the element pointing to the previous element (3 pointing to 2). Let's explore how to implement this operation in
    java
/
 public boolean add(E e) {
    
    
        linkLast(e);//实则调用这个方法,如下
        return true;
    }
/**
 * 先分析: 1、双向链表包括首节点first,尾节点last,元素节点
 *        2、每一个元素节点包括三部分:元素、前驱节点、后继节点
 *        3、添加元素又分为首次添加、非首次添加
 *        4、首次添加:first为空节点,last为空节点,此时创建新元素节点
 *      前继节点则为空,后继节点也为空,添加元素后,此元素节点既是first也为last
 *        5、非首次添加:创建新元素节点,添加到链表最后,此时应让新元素节点的前驱节点指向当前尾节点(在创建新节点时调用的构造函数中完成)
 *      然后新元素变为新的尾节点,因为是双向链表,需要之前尾节点的后继节点同样指向新的尾节点节点完成双向
 */
 void linkLast(E e) {
    
    
        final Node<E> l = last;//此时l代表当前尾节点
        /**创建一个节点:l代表尾节点(元素前驱节点)、e当前节点、null代表尾节点(元素后继节点)*/
        final Node<E> newNode = new Node<>(l, e, null);//Node方法如下文
        //此时让尾节点指向新添加的元素,作为尾节点的标记(上述新的尾节点)
        last = newNode;
        if (l == null)//在第一次添加元素时,last为空所以l为空,则这个元素就是第一个节点(首节点)
            first = newNode;//引出首节点,作为首节点的标记(同时也是尾节点)
        else//若不是第一次添加元素,则尾节点last不为空,l则也不为空,
            l.next = newNode;//(为了满足双向链表)当前尾节点的后继节点指向新添加的元素节点,至此新添加元素节点变为尾节点(完成双向链接)
        size++;//链表元素个数加一
        modCount++;//链表操作次数记录加一
    }
 private static class Node<E> {
    
    
        E item;//元素
        Node<E> next;//后继节点
        Node<E> prev;//前驱节点
		//每一个节点包含前继节点,元素,后继节点
        Node(Node<E> prev, E element, Node<E> next) {
    
    
            this.item = element;
            this.next = next;
            this.prev = prev;//在创建新节点时,前继节点在这里完成了指向当前尾节点
        }
    }

2. Add the second method

Principle: Always consider the characteristics of the doubly linked list. When adding an element at a specified position, change the front and rear nodes of the new element, the forward point of the subsequent element node, and the backward point of the previous element node. Add a schematic diagram (probably a schematic diagram, a bit ugly), the only difference from the above is that this method adds elements in the middle
insert image description here
Source code:

//index指定添加元素的位置,element元素值
 public void add(int index, E element) {
    
    
 		//判断索引是否越界或者非法(小于零或者大于链表长度)
        checkPositionIndex(index);
		//若index==size证明需要在链表末尾添加元素(和第一种添加方法实现一致)
        if (index == size)
            linkLast(element);//同上方法一
        else//反之,在指定索引添加元素 首先node(index)找出该索引对应的元素节点
            linkBefore(element, node(index));
    }

void linkBefore(E e, Node<E> succ) {
    
    
        // 获取指定位置元素的前驱节点
        final Node<E> pred = succ.prev;
        //创建新节点,前驱节点指向原指定位置的前继节点,后继结点指向succ节点
        //例:1<——2(newNode)——>3
        final Node<E> newNode = new Node<>(pred, e, succ);
        //上述例子 因为是双向链表所以要同时让2<——3
        succ.prev = newNode;
        if (pred == null)
        //pred 为空证明新添加的元素是第一个节点,那么更新首节点
            first = newNode;
        else
        //反之,也要设置1——>2,保证双向链表
            pred.next = newNode;
        //元素个数加一
        size++;
        //记录操作次数
        modCount++;
    }

The principle of other adding methods is basically the same

3. Deletion method

  • The principle is to complete the whole process of the picture below (it is possible to realize it with an idea!)

insert image description here指定对象删除

public boolean remove(Object o) {
    
    
        if (o == null) {
    
    //若删除null元素
            for (Node<E> x = first; x != null; x = x.next) {
    
    //从第一个元素开始,首节点给x,判断条件是x是否有值,有的话进入循环体,然后将x的后继节点付给x(后继节点就相当于下一个元素)
                if (x.item == null) {
    
    
                    unlink(x);//找到后执行删除
                    return true;
                }
            }
        } else {
    
    
            for (Node<E> x = first; x != null; x = x.next) {
    
    //同上
                if (o.equals(x.item)) {
    
    //做判断
                    unlink(x);//执行删除
                    return true;
                }
            }
        }
        return false;
    }

指定索引删除

public E remove(int index) {
    
    
        checkElementIndex(index);//判断索引是否为合法(小于零或者大于链表长度)
        return unlink(node(index));//node(index)找到元素节点直接删除如下
    }


 Node<E> node(int index) {
    
    
        //根据索引查找元素这里体现了双向链表
        if (index < (size >> 1)) {
    
    //index小于元素总个数的一半,从前找更快
        	//从前往后找
            Node<E> x = first;
            for (int i = 0; i < index; i++)
                x = x.next;//顺藤摸瓜找到index位置元素
            return x;
        } else {
    
    //反之从后找更快
        //从后往前找
            Node<E> x = last;
            for (int i = size - 1; i > index; i--)
                x = x.prev;
            return x;
        }
    }

发现上述方法就是通过传入的条件找到了要删除的元素最终通过unlink()方法删除下面分析这个方法

 E unlink(Node<E> x) {
    
    
 		//x就是要删除的节点,从节点中取出要删除的元素
        final E element = x.item;
        //取出后继节点
        final Node<E> next = x.next;
        //取出前驱节点
        final Node<E> prev = x.prev;
		//下方两个if语句的执行代表了上图过度过程的实现(解链操作)
        if (prev == null) {
    
    //若前驱节点为null证明,这是第一个元素
            first = next;//将要删除的元素节点的下一个元素节点作为第一个元素节点(后继节点指向的元素)
        } else {
    
    //反之则为中间元素节点
            prev.next = next;//上图中过度过程的上方蓝线形成1的后继节点指向3(通过2),同时1指向2红线断裂(被重新赋值覆盖了)
            x.prev = null;//断开2的前驱节点(断开了2指向1的那条红线)
        }

        if (next == null) {
    
    //若是最后一个元素
            last = prev;//将最后一个元素的前一个元素设置为最后一个元素,记录到尾节点中
        } else {
    
    
            next.prev = prev;//上图中过度过程的下方蓝线形成3的前驱节点指向1(通过2)同时3指向2红线断裂(被重新赋值覆盖了)
            x.next = null;//断开2的后继节点(断开了2指向3的那条红线)
        }
		//最后清空元素,到此元素节点被成功删除
        x.item = null;
        size--;//元素个数自减
        modCount++;//操作次数自增
        return element;//返回这个被删除的元素
    }

掌握上述方法后再看其他相关删除方法代码,逻辑都一样

4. Find element get() method

 public E get(int index) {
    
    
        checkElementIndex(index);
        return node(index).item;//在删除方法中已经说过node方法,得到节点后取出元素返回
    }

5. traverse

  • Principle: Start traversing from the first element or the last element. The logic is to follow the vines to the last element node according to the obtained first element node, which is relatively simple and not cumbersome. (Refer to the previous article. However, the linked list is to search down the elements, if you are interested, you can follow up the source code to view), the traversal is very slow, and each search has to start from the beginning, which is generally not used.

好了LinkedList讨论到此结束,下篇文章讨论List集合下的Vector,每天进步一点点!!大家一起讨论学习,不足的地方还请指出来!

Guess you like

Origin blog.csdn.net/weixin_44078653/article/details/104092054