[集合]LinkedList源码和时间复杂度

  • Post author:
  • Post category:其他

目录

前言:

1.LinkedList的复杂度?

2.源码分析

2.1 LinkedList的继承关系?

2.2 属性

2.2.1 Node结构

2.3 构造函数

2.3.1 addAll(重磅)

2.4 添加元素

2.4.1 add(E e)

2.4.2 addAll(Collection c)

2.4.3 addFirst(E e)

2.4.4 addLast(E e)

2.4.5 void add(int index, E element)

2.5 删除元素

2.5.1 remove()

2.5.2 remove(Object o)

2.6 get

2.6.1 get(int index)

2.6.2 getFirst()

2.6.3 getLast()

2.7 contains

2.8 set 设置对应index的节点的值

2.9 队列相关

2.9.1 peek

2.9.2 poll

2.9.3 offer

2.9.4 push

2.9.5 pop

2.10 序列化

2.10.1 writeObject

2.10.2 readObject


前言:

基于jdk1.8.

同上一篇ArrayList的源码。

两个半天撸完= =。DK加油

上一篇:https://blog.csdn.net/pmdream/article/details/106995088

1.LinkedList的复杂度?

get() 获取第几个元素,依次遍历,复杂度O(n)
add(E) 添加到末尾,复杂度O(1)
add(index, E) 添加第几个元素后,需要先查找到第几个元素,直接指针指向操作,复杂度O(n) (这个比较容易想错)
remove()删除元素,直接指针指向操作,复杂度O(1)

2.源码分析

2.1 LinkedList的继承关系?

public class LinkedList<E>
    extends AbstractSequentialList<E>
    implements List<E>, Deque<E>, Cloneable, java.io.Serializable
{
//...
}

对比ArrayList:

public class ArrayList<E> extends AbstractList<E>
        implements List<E>, RandomAccess, Cloneable, java.io.Serializable
{
//...
}

没有了快速访问,多了Deque接口。

因为实现了队列接口,所以可以让LinkedList作为双向队列。

和ArrayList不同的地方是,LinkedList继承的是AbstractSequentialList,AbstractSequentialList继承AbstractList,而ArrayList直接继承AbstractList。

2.2 属性

2.2.1 Node结构

因为不同于ArrayList,对于LinkedList每个元素都有着Node节点(这是一个内部类,有这前后指针):

双向链表,那么顺序访问的效率很高,随机访问的效率低。因为通过索引访问,会比较索引值和链表长度的1/2,如果索引大,那么会从链表尾开始找,否则是头。所以才有了下面的first和last节点。

    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;
        }
    }
    transient int size = 0;

    /**
     * Pointer to first node.
     * Invariant: (first == null && last == null) ||
     *            (first.prev == null && first.item != null)
     */
    transient Node<E> first;

    /**
     * Pointer to last node.
     * Invariant: (first == null && last == null) ||
     *            (last.next == null && last.item != null)
     */
    transient Node<E> last;

2.3 构造函数

无参构造函数:

    /**
     * Constructs an empty list.
     */
    public LinkedList() {
    }

 有参构造函数:(因为链表形式不需要初始化空间给它)

    /**
     * Constructs a list containing the elements of the specified
     * collection, in the order they are returned by the collection's
     * iterator.
     *
     * @param  c the collection whose elements are to be placed into this list
     * @throws NullPointerException if the specified collection is null
     */
    public LinkedList(Collection<? extends E> c) {
        this();
        addAll(c);
    }

2.3.1 addAll(重磅)

   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;
        }
    }

    /**
     * Appends all of the elements in the specified collection to the end of
     * this list, in the order that they are returned by the specified
     * collection's iterator.  The behavior of this operation is undefined if
     * the specified collection is modified while the operation is in
     * progress.  (Note that this will occur if the specified collection is
     * this list, and it's nonempty.)
     *
     * @param c collection containing elements to be added to this list
     * @return {@code true} if this list changed as a result of the call
     * @throws NullPointerException if the specified collection is null
     */
    public boolean addAll(Collection<? extends E> c) {
        return addAll(size, c);
    }

    /**
     * Inserts all of the elements in the specified collection into this
     * list, starting at the specified position.  Shifts the element
     * currently at that position (if any) and any subsequent elements to
     * the right (increases their indices).  The new elements will appear
     * in the list in the order that they are returned by the
     * specified collection's iterator.
     *
     * @param index index at which to insert the first element
     *              from the specified collection
     * @param c     collection containing elements to be added to this list
     * @return {@code true} if this list changed as a result of the call
     * @throws IndexOutOfBoundsException {@inheritDoc}
     * @throws NullPointerException      if the specified collection is null
     */
    public boolean addAll(int index, Collection<? extends E> c) {
        //检查index是否合法
        checkPositionIndex(index);

        Object[] a = c.toArray();
        int numNew = a.length;
        if (numNew == 0)
            return false;

        //succ:待添加节点的位置。
        //pred:待添加节点的前一个节点。
        //新添加的元素的位置位于LinkedList最后一个元素的后面。
        //如果index==size;说明此时需要添加LinkedList中的集合中的每一个元素都是在LinkedList
        //最后面。所以把succ设置为空,pred指向尾节点。
        //否则的话succ指向插入待插入位置的节点。pred指向succ节点的前一个节点。
        Node<E> pred, succ;
        if (index == size) {
            //其实如果没有元素,也会进入第一个if判断
            //说明想要插入的是在链表的末尾
            succ = null;
            pred = last;
        } else {
            //这种情况返回要插入的是哪个节点那里,但是肯定是插在succ之前,pred之后,所以接下来要操作pred
            succ = node(index);
            pred = succ.prev;
        }

        //循环a 每个节点存储着数组a的值,该节点的prev用来存储pred节点,next设置为空。接着判断一下该节点的前一个节点是否为
        //空,如果为空的话,则把当前节点设置为头节点。否则的话就把当前节点的前一个节点的next值
        //设置为当前节点。最后把pred指向当前节点,以便后续新节点的添加。
        for (Object o : a) {
            @SuppressWarnings("unchecked") E e = (E) o;
            Node<E> newNode = new Node<>(pred, e, null);
            if (pred == null)
                //说明之前都是空的
                first = newNode;
            else
                //那么如果pred不是空的了,把当前节点的前一个节点pred,的next指向新的内容
                pred.next = newNode;
            //pred指向newNode
            pred = newNode;
        }

        if (succ == null) {
            //新添加的节点位于LinkedList集合的最后一个元素的后面
            //pred指向的是LinkedList中的最后一个元素
            //所以把last指向pred
            last = pred;
        } else {
            //中间位置插入的,相当于把一块内容插了进去,然后把前后指针都重新指向
            pred.next = succ;
            succ.prev = pred;
        }

        //加上新的集合大小
        size += numNew;
        //相当于整体链表修改的次数增加
        modCount++;
        return true;
    }


    //检查Pos是否合法,其实就是检查是否大于0并且在总大小内
    private void checkPositionIndex(int index) {
        if (!isPositionIndex(index))
            throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
    }

    /**
     * Tells if the argument is the index of a valid position for an
     * iterator or an add operation.
     */
    private boolean isPositionIndex(int index) {
        return index >= 0 && index <= size;
    }

    /**
     * Returns the (non-null) Node at the specified element index.
     * 最后返回一个
     */
    Node<E> node(int index) {
        // assert isElementIndex(index);

        //与数组大小的一半进行比较
        if (index < (size >> 1)) {
            //如果index小于总大小的一半
            //将first给x
            Node<E> x = first;
            //从头指针往后找
            for (int i = 0; i < index; i++)
                x = x.next;
            return x;
        } else {
            Node<E> x = last;
            //从尾指针开始往前找
            for (int i = size - 1; i > index; i--)
                x = x.prev;
            return x;
        }
    }

读了蛮久,总结就是有succ指针,指向当前index节点,pred是succ之前的节点,

那么想插入到index的时候,那么就得放到succ之前,pred之后,曹总pred指针呗~

最后再处理一下这两个的前后指针。

2.4 添加元素

2.4.1 add(E e)

    /**
     * Appends the specified element to the end of this list.
     *
     * <p>This method is equivalent to {@link #addLast}.
     *
     * @param e element to be appended to this list
     * @return {@code true} (as specified by {@link Collection#add})
     */
    public boolean add(E e) {
        linkLast(e);
        return true;
    }

    /**
     * Links e as last element.
        当然连接到尾部了。
     */
    void linkLast(E e) {
//这个last是内部属性,代表尾部节点指针
        final Node<E> l = last;
//声明一个新的newNode,next指针指向null,前置指针指向之前的last
        final Node<E> newNode = new Node<>(l, e, null);
        last = newNode;
//这个书名之前的last是null,那么是相当于初始化,得吧first也得指向新的newNode
        if (l == null)
            first = newNode;
        else
            l.next = newNode;
        size++;
        modCount++;
    }

2.4.2 addAll(Collection<? extends E> c)

见上面构造函数。

2.4.3 addFirst(E e)

    /**
     * Inserts the specified element at the beginning of this list.
     *
     * @param e the element to add
     */
    public void addFirst(E e) {
        linkFirst(e);
    }

    /**
     * Links e as first element.
     */
    private void linkFirst(E e) {
        final Node<E> f = first;
        final Node<E> newNode = new Node<>(null, e, f);
        first = newNode;
        if (f == null)
            last = newNode;
        else
            f.prev = newNode;
        size++;
        modCount++;
    }

基本思想一样,就是更改头部指针。

2.4.4 addLast(E e)

    /**
     * Appends the specified element to the end of this list.
     *
     * <p>This method is equivalent to {@link #add}.
     *
     * @param e the element to add
     */
    public void addLast(E e) {
        linkLast(e);
    }

这个等同于add,只是没有返回添加称没成功的boolean

2.4.5 void add(int index, E element)

    /**
     * Inserts the specified element at the specified position in this list.
     * Shifts the element currently at that position (if any) and any
     * subsequent elements to the right (adds one to their indices).
     *
     * @param index index at which the specified element is to be inserted
     * @param element element to be inserted
     * @throws IndexOutOfBoundsException {@inheritDoc}
     */
    public void add(int index, E element) {
        checkPositionIndex(index);

        if (index == size)
            linkLast(element);
        else
//这种已经确保了,不会为null,因为如果链表长度只有1,那你却想插入5的index,就会报错的,运行期会报错
            linkBefore(element, node(index));
    }

这里面需要注意linkBefore这个方法。node(index)找到了对应的要插入的那个节点的succ,当前index指向的节点。

    /**
     * Inserts element e before non-null Node succ.
     */
    void linkBefore(E e, Node<E> succ) {
        // assert succ != null;
        final Node<E> pred = succ.prev;
        final Node<E> newNode = new Node<>(pred, e, succ);
        succ.prev = newNode;
//为什么会有判断pred == null??接下来讲
        if (pred == null)
            first = newNode;
        else
            pred.next = newNode;
        size++;
        modCount++;
    }

在上面代码中,我不太明白为什么pred == null 有这个判断?

首先,进入这个方法的,肯定是有大小的。因为链表长度为1,但是比如我要插入5,就会报错。

    public static void main(String[] args) {
        //这样会报异常
        LinkedList linkedList = new LinkedList();
        linkedList.add("hh");
        linkedList.add(5,"ss");
        System.out.println(linkedList);
    }

那么怎么进入pred==null这个判断呢?

    public static void main(String[] args) {
        LinkedList linkedList = new LinkedList();
        linkedList.add("hh");
        linkedList.add(0,"ss");
        System.out.println(linkedList);
    }

这种情况下,pred 就是空的。因为只有一个节点的时候,他的前后指针都是空的。

只不过我们会给first指针和last指针,但这不等于 node中的前后指针。

在进行这个操作之后,我们发现第一个节点有next,第二个节点有prev~

2.5 删除元素

2.5.1 remove()

    /**
     * Retrieves and removes the head (first element) of this list.
     *
     * @return the head of this list
     * @throws NoSuchElementException if this list is empty
     * @since 1.5
     */
    public E remove() {
        return removeFirst();
    }

    /**
     * Removes and returns the first element from this list.
     *
     * @return the first element from this list
     * @throws NoSuchElementException if this list is empty
     */
    public E removeFirst() {
        final Node<E> f = first;
        if (f == null)
            throw new NoSuchElementException();
        return unlinkFirst(f);
    }

    /**
     * Unlinks non-null first node f.
     */
    private E unlinkFirst(Node<E> f) {
        // assert f == first && f != null;
        final E element = f.item;
        final Node<E> next = f.next;
        f.item = null;
        f.next = null; // help GC
        first = next;
        if (next == null)
            last = null;
        else
            next.prev = null;
        size--;
        modCount++;
        return element;
    }

这个默认会删除掉头部的元素。

这边有一个点很可借鉴,当我们不用了一个元素的时候,需要把它的前后置镇 对于头结点就是next指针置为null,

这样可以帮助GC回收元素,因为就少了引用了。否则可能会持续的引用

2.5.2 remove(Object o)

    /**
     * Removes the first occurrence of the specified element from this list,
     * if it is present.  If this list does not contain the element, it is
     * unchanged.  More formally, removes the element with the lowest index
     * {@code i} such that
     * <tt>(o==null&nbsp;?&nbsp;get(i)==null&nbsp;:&nbsp;o.equals(get(i)))</tt>
     * (if such an element exists).  Returns {@code true} if this list
     * contained the specified element (or equivalently, if this list
     * changed as a result of the call).
     *
     * @param o element to be removed from this list, if present
     * @return {@code true} if this list contained the specified element
     */
    public boolean remove(Object o) {
        if (o == null) {
            for (Node<E> x = first; x != null; x = x.next) {
                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;
    }

如果是null直接用== 来判断相等,如果是对象类型使用equals。

unlink:

    /**
     * Unlinks non-null node x.
     */
    E unlink(Node<E> x) {
        // assert x != null;
        final E element = x.item;
        final Node<E> next = x.next;
        final Node<E> prev = x.prev;

        if (prev == null) {
//是头结点
            first = next;
        } else {
//否则,把之前节点的next指向next;并且释放x的前街店指针
            prev.next = next;
            x.prev = null;
        }

        if (next == null) {
//是尾结点
            last = prev;
        } else {
            next.prev = prev;
            x.next = null;
        }

        x.item = null;
        size--;
        modCount++;
        return element;
    }

2.6 get

2.6.1 get(int index)

    /**
     * Returns the element at the specified position in this list.
     *
     * @param index index of the element to return
     * @return the element at the specified position in this list
     * @throws IndexOutOfBoundsException {@inheritDoc}
     */
    public E get(int index) {
//验证index合法性
        checkElementIndex(index);
//使用node方法
        return node(index).item;
    }

    /**
     * Returns the (non-null) Node at the specified element index.
     */
    Node<E> node(int index) {
        // assert isElementIndex(index);

        if (index < (size >> 1)) {
            Node<E> x = first;
            for (int i = 0; i < index; i++)
                x = x.next;
            return x;
        } else {
            Node<E> x = last;
            for (int i = size - 1; i > index; i--)
                x = x.prev;
            return x;
        }
    }

2.6.2 getFirst()

    /**
     * Returns the first element in this list.
     *
     * @return the first element in this list
     * @throws NoSuchElementException if this list is empty
     */
    public E getFirst() {
        final Node<E> f = first;
        if (f == null)
            throw new NoSuchElementException();
        return f.item;
    }

可以用属性中的first指针。

2.6.3 getLast()

    /**
     * Returns the last element in this list.
     *
     * @return the last element in this list
     * @throws NoSuchElementException if this list is empty
     */
    public E getLast() {
        final Node<E> l = last;
        if (l == null)
            throw new NoSuchElementException();
        return l.item;
    }

2.7 contains

    /**
     * Returns {@code true} if this list contains the specified element.
     * More formally, returns {@code true} if and only if this list contains
     * at least one element {@code e} such that
     * <tt>(o==null&nbsp;?&nbsp;e==null&nbsp;:&nbsp;o.equals(e))</tt>.
     *
     * @param o element whose presence in this list is to be tested
     * @return {@code true} if this list contains the specified element
     */
    public boolean contains(Object o) {
        return indexOf(o) != -1;
    }

    /**
     * Returns the index of the first occurrence of the specified element
     * in this list, or -1 if this list does not contain the element.
     * More formally, returns the lowest index {@code i} such that
     * <tt>(o==null&nbsp;?&nbsp;get(i)==null&nbsp;:&nbsp;o.equals(get(i)))</tt>,
     * or -1 if there is no such index.
     *
     * @param o element to search for
     * @return the index of the first occurrence of the specified element in
     *         this list, or -1 if this list does not contain the element
     */
    public int indexOf(Object o) {
        int index = 0;
        if (o == null) {
            for (Node<E> x = first; x != null; x = x.next) {
                if (x.item == null)
                    return index;
                index++;
            }
        } else {
            for (Node<E> x = first; x != null; x = x.next) {
                if (o.equals(x.item))
                    return index;
                index++;
            }
        }
        return -1;
    }

做了一遍循环。

 

2.8 set 设置对应index的节点的值

    /**
     * Replaces the element at the specified position in this list with the
     * specified element.
     *
     * @param index index of the element to replace
     * @param element element to be stored at the specified position
     * @return the element previously at the specified position
     * @throws IndexOutOfBoundsException {@inheritDoc}
     */
    public E set(int index, E element) {
        checkElementIndex(index);
        Node<E> x = node(index);
        E oldVal = x.item;
        x.item = element;
        return oldVal;
    }

返回oldVal

2.9 队列相关

2.9.1 peek

    /**
     * Retrieves, but does not remove, the head (first element) of this list.
     *
     * @return the head of this list, or {@code null} if this list is empty
     * @since 1.5
     */
    public E peek() {
        final Node<E> f = first;
        return (f == null) ? null : f.item;
    }

返回头结点的值。(头结点为空,直接返回空)

2.9.2 poll

    /**
     * Retrieves and removes the head (first element) of this list.
     *
     * @return the head of this list, or {@code null} if this list is empty
     * @since 1.5
     */
    public E poll() {
        final Node<E> f = first;
        return (f == null) ? null : unlinkFirst(f);
    }

移除头结点,并返回移除的头结点的值。

2.9.3 offer

    /**
     * Adds the specified element as the tail (last element) of this list.
     *
     * @param e the element to add
     * @return {@code true} (as specified by {@link Queue#offer})
     * @since 1.5
     */
    public boolean offer(E e) {
        return add(e);
    }

在链表的头部添加一个新的元素

2.9.4 push

    /**
     * Pushes an element onto the stack represented by this list.  In other
     * words, inserts the element at the front of this list.
     *
     * <p>This method is equivalent to {@link #addFirst}.
     *
     * @param e the element to push
     * @since 1.6
     */
    public void push(E e) {
        addFirst(e);
    }

Push的话是添加头结点

2.9.5 pop


    /**
     * Pops an element from the stack represented by this list.  In other
     * words, removes and returns the first element of this list.
     *
     * <p>This method is equivalent to {@link #removeFirst()}.
     *
     * @return the element at the front of this list (which is the top
     *         of the stack represented by this list)
     * @throws NoSuchElementException if this list is empty
     * @since 1.6
     */
    public E pop() {
        return removeFirst();
    }

2.10 序列化

2.10.1 writeObject

    /**
     * Saves the state of this {@code LinkedList} instance to a stream
     * (that is, serializes it).
     *
     * @serialData The size of the list (the number of elements it
     *             contains) is emitted (int), followed by all of its
     *             elements (each an Object) in the proper order.
     */
    private void writeObject(java.io.ObjectOutputStream s)
        throws java.io.IOException {
        // Write out any hidden serialization magic
        s.defaultWriteObject();

        // Write out size
        s.writeInt(size);

        // Write out all elements in the proper order.
        for (Node<E> x = first; x != null; x = x.next)
            s.writeObject(x.item);
    }

和ArrayList很像。

2.10.2 readObject

    /**
     * Reconstitutes this {@code LinkedList} instance from a stream
     * (that is, deserializes it).
     */
    @SuppressWarnings("unchecked")
    private void readObject(java.io.ObjectInputStream s)
        throws java.io.IOException, ClassNotFoundException {
        // Read in any hidden serialization magic
        s.defaultReadObject();

        // Read in size
        int size = s.readInt();

        // Read in all elements in the proper order.
        for (int i = 0; i < size; i++)
            linkLast((E)s.readObject());
    }

 


版权声明:本文为pmdream原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。