Java 集合深入理解 :LinkedList链表源码研究,及双向队列如何实现

Posted 踩踩踩从踩

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Java 集合深入理解 :LinkedList链表源码研究,及双向队列如何实现相关的知识,希望对你有一定的参考价值。

简介:

LinkedList是java集合框架很常用数据结构;在开发中运用也很多,经常对比也是和arrayList进行对比。
ArrayList 是以数组实现的,遍历时很快,但是插入、删除时都需要移动后面的元素;
LinkedList 则是以链表实现的,插入、删除时只需要改变前后两个节点指针指向即可,省事不少。

关键方法时间复杂度

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

代码示例

public static void main(String[] args) {
		
		System.out.println("--------------LinkedList--------------");
		LinkedList<Integer> v=new LinkedList<Integer>();
		v.add(1);
		v.add(2);
		v.add(3);
		v.add(7);
		v.add(5);
		v.add(6);
		
		v.stream().forEach(m->{
			System.out.println(m);
		});
	}
--------------LinkedList--------------
1
2
3
7
5
6

链表实现的集合,能保证数据插入顺序和取出顺序,不保证多线程情况下数据的安全性

全篇注释

/**
*是基于{@code list}和{@code Deque}的双链表实现
*接口。实现所有可选的列表操作,并允许
*元素(包括{@code null})。
*<p>所有操作的性能都与双链接列表相同。索引到列表中的操作将从
*开始或结束,以接近指定索引的为准。
*<p><strong>请注意,此linkedlist实现不同步。</strong>
*如果多个线程同时访问一个链表其中一个线程从结构上修改列表,它必须
*外部同步(结构修改是指任何操作添加或删除一个或多个元素;只是设定元素不是结构修改。)这通常是通过在某个自然封装列表。如果不存在这样的对象,则应该使用
*{@link Collections#synchronizedList集合.synchronizedList}方法。这最好在创建时完成,以防止意外对集合列表的非同步访问:<pre>
*List=Collections.synchronizedList(新建LinkedList(…))</预处理>
*<p>这个类的{@code iterator}返回的迭代器和{@codelistiterator}方法是<i>快速失败的</i>:如果列表是在迭代器创建之后的任何时候,在除了通过迭代器自己的{@code remove}或{@code add}方法,迭代器将抛出一个{@链接ConcurrentModificationException}。因此,面对
*修改后,迭代器会快速而干净地失败,而不是冒着任意的,不确定的行为未来的时间。
*<p>请注意,不能保证迭代器的快速失败行为一般说来,不可能在未来作出任何硬性保证存在未同步的并发修改。失败快速迭代器
*尽最大努力抛出{@code ConcurrentModificationException}。因此,编写依赖于此的程序是错误的
*其正确性例外:<i>迭代器的快速失败行为
*应仅用于检测错误。</i>
*<p>这个class是
*<a href=“{@docRoot}/./technotes/guides/collections/index.html”>
*Java集合框架</a>。
 @author  Josh Bloch
  @see     List
 @see     ArrayList
  @since 1.2
  @param <E> the type of elements held in this collection
 */

注释关键点

  • 允许空数据
  • 所有操作的性能都与双链接列表相同。
  • 通过双向链表实现的,并且不是线程安全的
  • 需要线程安全的类,请使用Collections#synchronizedList使之变成安全的
  • 该类不能进行遍历同时remove和add,否则会抛出ConcurrentModificationException异常,直接使用迭代器提供的remove方法则可以使用删除

接口实现

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

实现list 和 deque接口 AbstractSequentialList 实现抽象的类

  • 实现双链表操作
  • 可序列化
  • 实现list的操作

成员变量分析双向链表

     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;

在这里插入图片描述

成员属性分析
相比于arraylist的成员属性会少很多

  • 相同点
    采用transient 关键字,来提高序列化的性能;都是实现了list接口的操作
  • 不同点
    通过first节点 和last节点来达找到节点,添加删除操作 没有动态扩容操作
  • size 包含的元素大小 并不是整个length的长度

transient 非私有以简化嵌套类访问
被transient修饰的变量不参与序列化和反序列化;
LinkedList是实现了Serializable说明他能够被序列化和反序列化传输的;

什么是writeObject 和readObject?可定制的序列化过程

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

构造方法

addAll 方法

  • 通过checkPositionIndex校验index 是否达到数据满值
  • 传入 Collection 中toarray方法转换为object数组
  • 获取插入节点的对象,以及前一个节点
  • 将所有object数组遍历,生成节点,连接起来
  • 将连接组装到链表中
  • size 添加numNew modCount 加1
    /**
     *构造一个包含指定元素的列表
     *集合,按集合的迭代器。
     * @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);
    }
    /**
     *将指定集合中的所有元素插入此列表,从指定位置开始。移动元素目前在该位置(如有)以及右边(增加他们的指数)。新元素将出现
     *在列表中按指定集合的迭代器。
     * @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) {
        checkPositionIndex(index);

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

        Node<E> pred, succ;
        if (index == size) {
            succ = null;
            pred = last;
        } else {
            succ = node(index);
            pred = succ.prev;
        }

        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.next = newNode;
            pred = newNode;
        }

        if (succ == null) {
            last = pred;
        } else {
            pred.next = succ;
            succ.prev = pred;
        }

        size += numNew;
        modCount++;
        return true;
    }

toArray方法

  • 判断传进来的数组是否小于内部数据的大小

  • 小于则通过反射 去创建一个新数组

  • 根据首节点通过遍历添加元素到新的数组中,未满的情况则设置为null

/**
*返回一个数组,数组中包含此列表中的所有元素
*正确的顺序(从第一个元素到最后一个元素);的运行时类型
*返回的数组是指定数组的数组。如果名单合适的话
*在指定的数组中,它在其中返回。否则,一个新的
*使用指定数组的运行时类型和此列表的大小。
*<p>如果列表适合指定的数组,并且有空闲空间(即。,
*数组中的元素比列表中的元素多
*紧跟在列表末尾的是{@code null}。
*(这在确定列表长度时非常有用,如果
*调用者知道列表不包含任何空元素。)
*<p>与{@link#toArray()}方法类似,此方法充当
*基于数组和基于集合的API。此外,该方法允许
*对输出数组的运行时类型的精确控制,
*在某些情况下,可以用来节省配置成本。
*<p>假设{@codex}是一个只包含字符串的列表。
*以下代码可用于将列表转储到新的
*已分配的{@code String}数组:
 * <pre>
     *     String[] y = x.toArray(new String[0]);</pre>
     *
     * Note that {@code toArray(new Object[0])} is identical in function to
     * {@code toArray()}.
*@param a要将列表元素放入的数组
*如果足够大的话,就要储存起来;否则,一个新的
*为此目的分配了相同的运行时类型。
*@返回包含列表元素的数组
*如果指定数组的运行时类型
*不是中每个元素的运行时类型的超类型
*此列表
*如果指定的数组为null,@throws NullPointerException
*/
    @SuppressWarnings("unchecked")
    public <T> T[] toArray(T[] a) {
        if (a.length < size)
            a = (T[])java.lang.reflect.Array.newInstance(
                                a.getClass().getComponentType(), size);
        int i = 0;
        Object[] result = a;
        for (Node<E> x = first; x != null; x = x.next)
            result[i++] = x.item;

        if (a.length > size)
            a[size] = null;

        return a;
    }

node节点

node节点 包含内容对象 下个节点对象 上个节点对象
所有操作基于node节点 或者 node对象实现的

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

关键方法头部添加删除,尾部添加删除,获取指定节点,指定节点的添加删除

linkFirst方法
添加首节点

  • 得到链表中first节点
  • 创建新节点 ,第一个节点等于新的节点,并将原first节点的前节点设置为添加节点
  • size+1 modCount加1
 /**
     * 添加第一个节点数据
     */
    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++;
    }

在这里插入图片描述

linkLast方法
添加末尾添加节点

  • 得到链表中last节点
  • 创建新节点 ,末尾节点等于新的节点,并将原last节点的后节点设置为添加节点
  • size+1 modCount加1
 /**
     * 添加末尾添加节点
     */
    void linkLast(E e) {
        final Node<E> l = last;
        final Node<E> newNode = new Node<>(l, e, null);
        last = newNode;
        if (l == null)
            first = newNode;
        else
            l.next = newNode;
        size++;
        modCount++;
    }

linkBefore方法
在非空节点之前插入元素e

  • 拿到非空节点的前置节点 pred
  • 创建新节点 ,前置节点 pred等于新的节点,并将前置节点 pred的后节点设置为添加节点
  • size+1 modCount加1
 /**
     * 在非空节点之前插入元素e。
     */
    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;
        if (pred == null)
            first = newNode;
        else
            pred.next = newNode;
        size++;
        modCount++;
    }

unlinkFirst方法包括该节点
清除一个非空节点之前的节点并设置为头节点

  • 拿到非空节点的数据,拿到节点的后置节点
  • 设置非空节点的数据为空,节点的后置节点为空
  • 设置首节点为非空节点的后置节点
  • 设置首节点的前置为空
  • size+1 modCount加1
  • 返回节点数据数据
/**
     * 清除一个非空节点之前的节点并设置为头节点
     */
    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;
    }

unlinkLast方法
清除非空节点的后面节点 并设置为空 返回数据

  • 拿到非空节点的数据,拿到节点的前置节点
  • 设置非空节点的数据为空,节点的前置节点为空
  • 设置末节点为非空节点的后置节点
  • 设置末节点的后置节点为空
  • size+1 modCount加1
  • 返回节点数据数据
 /**
     * 清除非空节点的后面节点 并设置为空
     */
    private E unlinkLast(Node<E> l) {
        // assert l == last && l != null;
        final E element = l.item;
        final Node<E> prev = l.prev;
        l.item = null;
        l.prev = null; // help GC
        last = prev;
        if (prev == null)
            first = null;
        else
            prev.next = null;
        size--;
        modCount++;
        return element;
    }

unlink方法
删除某个节点 并返回数据

  • 获得删除节点数据
  • 获取删除节点得前置节点 和后置节点
  • 设置前置节点得后置等于 后置节点 并设置删除节点得前置为null
  • 设置后置节点得前置置等于 前置节点 并设置删除节点得后置为null
  • 设置节点数据为空
  • size+1 modCount加1
  • 返回节点数据数据
     /**
     * 删除某个节点
     */
    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 {
            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;
    }

总结
这些内部方法实现了对 链表节点的 基本修改操作,每次操作都只要修改前后节点的指针节点,时间复杂度为 O(1) ,
很多需要中间进行操作的公开方法都是基于这些方法实现。

add方法

添加数据到此列表的末尾

  • 调用linkLast方法 插入链表末尾,并返回数据

在此列表中的指定位置插入指定的元素

  • checkPositionIndex 校验 index是否操作数据长度
  • index 等于size ,调用linkLast 方法直接添加末尾
  • index 不等于size,调用linkBefore 在非空节点之前插入元素e
  /**
    *将指定的元素追加到此列表的末尾。
     *<p>此方法等价于{@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;
    }
  /**
     *在此列表中的指定位置插入指定的元素。
     *移动当前位于该位置的元素(如果有)和
     *右边的后续元素(在其索引中添加一个)
     * @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
            linkBefore(element, node(index));
    }

实现自双端队列(Deque)的添加方法 push、pop、offer 方法等等

  • push 方法 将元素推送到由该列表表示的堆栈上。其他单词,在列表的前面插入元素
    直接调用addFirst 方法
    /**
     *将元素推送到由该列表表示的堆栈上。其他单词,在列表的前面插入元素。
     * <p>This method is equivalent to {@link #addFirst}.
     *
     * @param e the element to push
     * @since 1.6
     */
    public void push(E e) {
        addFirst(e);
    }
  • pop 从该列表表示的堆栈中弹出一个元素
    调用removeFirst 方法
/**
     *从该列表表示的堆栈中弹出一个元素。其他
    *单词,删除并返回此列表的第一个元素。
     *
     * <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();
    }
  • Deque 操作
    在此列表前面插入指定的元素
  // Deque operations
    /**
     * 在此列表前面插入指定的元素。
     *
     * @param e the element to insert
     * @return {@code true} (as specified by {@link Deque#offerFirst})
     * @since 1.6
     */
    public boolean offerFirst(E e) {
        addFirst(e);
        return true;
    }

总结
这个双向队列的操作 理论上来讲,不会有动态扩容 堆内存有多大 数据就有多大

删除方法

这里注意

  • 从此列表中删除指定元素的第一个匹配项
  • 从首节点开始遍历,并没有使用二分查找法,我想应该清除数据应该不太考虑效率把
  /**
*从此列表中删除指定元素的第一个匹配项,
*如果有。如果此列表不包含元素,则为
*不变。更正式地说,删除索引最低的元素
*{@code i}这样
*<tt>(o==null&nbsp&nbsp;get(i)==null:&nbsp;o、 等于(get(i)))</tt>
*(如果存在这样的元素)。如果此列表
*包含指定的元素(如果此列表
*因呼叫而更改)。
     *
     * @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;
    }
 /**
     *检索并删除此列表的头(第一个元素)。
     *
     * @return the head of this list
     * @throws NoSuchElementException if this list is empty
     * @since 1.5
     */
    public E remove() {
        return removeFirst();
    }

get(int index)

返回此列表中指定位置的元素

  • 校验index是否大于size
  • 调用node方法 返回指定元素索引处的(非空)节点
  • 这里就使用了二分查找法 index < (size >> 1) 判断 当前index在数据大小左部分还是右部分
  • 根据元素进行遍历
 /**
     * 返回此列表中指定位置的元素。
     *
     * @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) {
        checkElementIndex(index);
        return node(index).item;
    }

  /**
     * 返回指定元素索引处的(非空)节点。
     */
    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;
        }
    }

在这里插入图片描述

DescendingIterator 倒序迭代器

这个方法返回一个迭代器在此双端队列逆向顺序的元素

  • next 这个方法也是 从指定指针节点往后遍历
  • previous 方法利用链表得属性从last节点往前遍历 lastReturned = next = (next == null) ? last : next.prev;
  /**
     * @since 1.6
     */
    public Iterator<E> descendingIterator() {
        return new DescendingIterator();
    }

    /**
     * 适配器通过ListItr.previous提供降序迭代器
     */
    private class DescendingIterator implements Iterator<E> {
        private final ListItr itr = new ListItr(size());
        public boolean hasNext() {
            return itr.hasPrevious();
        }
        public E next() {
            return itr.previous();
        }
        public void remove() {
            itr.remove();
        }
    }

   private class ListItr implements ListIterator<E> {
        private Node<E> lastReturned;
        private Node<E> next;
        private int nextIndex;
        private int expectedModCount = modCount;

        ListItr(int index) {
            // assert isPositionIndex(index);
            next = (index == size) ? null : node(index);
            nextIndex = index;
        }

        public boolean hasNext() {
            return nextIndex < size;
        }

        public E next() {
            checkForComodification();
            if (!hasNext())
                throw new NoSuchElementException();

            lastReturned = next;
            next = next.next;
            nextIndex++;
            return lastReturned.item;
        }

        public boolean hasPrevious() {
            return nextIndex > 0;
        }

        public E previous() {
            checkForComodification();
            if (!hasPrevious())
                throw new NoSuchElementException();

            lastReturned = next = (next == null) ? last : next.prev;
            nextIndex--;
            return lastReturned.item;
        }

        public int nextIndex() {
            return nextIndex;
        }

        public int previousIndex() {
            return nextIndex - 1;
        }

        public void remove() {
            checkForComodification();
            if (lastReturned == null)
                throw new IllegalStateException();

            Node<E> lastNext = lastReturned.next;
            unlink(lastReturned);
            if (next == lastReturned)
                next = lastNext;
            else
                nextIndex--;
            lastReturned = null;
            expectedModCount++;
        }

        public void set(E e) {
            if (lastReturned == null)
                throw new IllegalStateException();
            checkForComodification();
            lastReturned.item = e;
        }

        public void add(E e) {
            checkForComodification();
            lastReturned = null;
            if (next == null)
                linkLast(e);
            else
                linkBefore(e, next);
            nextIndex++;
            expectedModCount++;
        }

        public void forEachRemaining(Consumer<? super E> action) {
            Objects.requireNonNull(action);
            while (modCount == expectedModCount && nextIndex < size) {
                action.accept(next.item);
                lastReturned = next;
                next = next.next;
                nextIndex++;
            }
            checkForComodification();
        }

        final void checkForComodification() {
            if (modCount != expectedModCount)
                throw new ConcurrentModificationException();
        }
    }

快速失败机制

list实现结构图

在这里插入图片描述
Java 集合深入理解 (十) :集合框架体系图

以上是关于Java 集合深入理解 :LinkedList链表源码研究,及双向队列如何实现的主要内容,如果未能解决你的问题,请参考以下文章

实现MyLinkedList类深入理解LinkedList

Java集合四LinkedList

Java集合试读LinkedList源码

Java自学-集合框架 LinkedList

java集合之LinkedList

java集合之LinkedList源码分析