LinkedList源码学习和总结

概括

LinkedList见名知意,底层由链表实现,内存不连续

增删快,查改慢(相对而言),不支持随机访问(没有实现RandomAccess接口)

非线程安全,用    Collections.synchronizedList(new LinkedList<>())

类继承关系图

 

 既可以用作链表,也可以用作队列和双端队列,还可以用作栈

复杂度

      get(int index)                 需要从first开始遍历,复杂度O(n)
      add(E e)                        添加到末尾,调用的linkLast(E e),复杂度O(1)
      add(index, E)                在第几个元素的位置添加新元素,调用的是linkList或linkBefore,复杂度O(n) 
      remove(int index)  和 remove(Object o)                 删除元素,复杂度O(n)

源码

类说明翻译

/**
 * Doubly-linked list implementation of the {@code List} and {@code Deque} interfaces.  Implements all optional list operations, and permits all elements (including {@code null}).
 * 双向链表List(这里已经告诉你它的实现是 Doubly-linked) 实现了 List 和 Deque接口,实现了list 接口的所有操作,允许包括null在内的所有的值 
 * All of the operations perform as could be expected for a doubly-linked list.  Operations that index into the list will traverse the list from the beginning or the end, whichever is closer to the specified index.
 * 它的所有操作都符合对双向链表的预期 ,关于index(下标,或者是索引)的操作都会从头或者从尾部遍历整个链表,直到遍历到这个下标
 * Note that this implementation is not synchronized 
 * If multiple threads access a linked list concurrently, and at least one of the threads modifies the list structurally, it must be synchronized externally.  
* (A structural modification is any operation that adds or deletes one or more elements; merely setting the value of an element is not a structural modification.)
* This is typically accomplished by synchronizing on some object that naturally encapsulates the list. * 上面这一段是说它不是线程安全的,如果多个线程并发访问一个链表,并且至少一个线程要做结构性修改(结构性修改是指增删一个或多个元素的操作,仅仅对一个元素的值做修改不是结构性更改),那么需要在对它操作之前,增加同步手段来控制对它的访问,比如对象加锁的方式 * If no such object exists, the list should be "wrapped" using the {
@link Collections#synchronizedList Collections.synchronizedList} method.
* This is best done at creation time, to prevent accidental unsynchronized access to the list:
* <pre> List list = Collections.synchronizedList(new LinkedList(...));</pre> * 如果没有这样的可加锁的对象,可以使用LinkedList 的包装类对象,也就是调用List list = Collections.synchronizedList(new LinkedList(...))来创建包装类对象来创建一个线程安全的链表 * <p>The iterators returned by this class's {
@code iterator} and {@code listIterator} methods are <i>fail-fast</i>:
* if the list is structurally modified at any time after the iterator is created, in any way except through the Iterator's own {
@code remove} or {@code add} methods,
* the iterator will throw a {
@link ConcurrentModificationException}. Thus, in the face of concurrent modification, the iterator fails quickly and cleanly, rather than * risking arbitrary, non-deterministic behavior at an undetermined time in the future. * 这一段讲的是快速失败的特性,以及如何避免,和ArrayList相似,下面是说快速失败机制不应该用来设计程序编码,只是用来排查bug的 * <p>Note that the fail-fast behavior of an iterator cannot be guaranteed * as it is, generally speaking, impossible to make any hard guarantees in the * presence of unsynchronized concurrent modification. Fail-fast iterators * throw {@code ConcurrentModificationException} on a best-effort basis. * Therefore, it would be wrong to write a program that depended on this * exception for its correctness: <i>the fail-fast behavior of iterators * should be used only to detect bugs.</i> * @author Josh Bloch * @see List * @see ArrayList * @since 1.2 * @param <E> the type of elements held in this collection */

主要属性

 transient int size = 0;

    /**
     * Pointer to first node.       ----------first的prev一定是null
     * Invariant: (first == null && last == null) ||      ------就是说first==null的话,那么last一定也是null,因为first==null,则这个链表就是一个空链表,没任何元素;所以只要出现if(first==null)的话,一定要给last赋同一个新node值
     *            (first.prev == null && first.item != null)------  如果first的prev为null,说明first一定是有元素的,item一定不为null
     */
    transient Node<E> first;

    /**
     * Pointer to last node.        ----------last的next一定是null
     * Invariant: (first == null && last == null) ||
     *            (last.next == null && last.item != null)
     */
    transient Node<E> 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;
        }
  }

几个多次用到的公共方法

  • 返回指定索引位置的Node

这个方法在源码多个位置被调用到,而且充分利用到了双向链表的特性,根据index和size/2的大小关系决定从前还是从后遍历

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

作为队列和栈时的主要方法

Queue操作:
E peek()     返回head(就是first.item).相似的一个方法是E element(),head不存在时直接抛异常,所以这个方法可以忽略
E poll() 删除并返回head。E remove()相同,可能抛出异常,所以忽略
boolean offer(E e),调用的是add(),添加元素作为tail(也就是last)

Dequeue操作
E peekFirst(),E peekLast()
E pollFirst(),E pollLast()
boolean offerFirst(E e),boolean  offerLast(E e)

Stack操作
void push(E e)   调用的是addFirst(e)
E pop() 

 遍历的最佳方式

前面复习ArrayList的时候已经讲过了,不支持随机访问的情况下,当然是迭代器遍历最快了,实验数据很简单

和ArrayList的对比

实现原理,性能(从存和取,结构修改三个角度)

posted @ 2022-07-11 16:55  鼠标的博客  阅读(30)  评论(0编辑  收藏  举报