概述
双链表实现了List和Deque接口。 实现所有可选列表操作,并允许所有元素(包括null )。
所有的操作都能像双向列表一样预期。 索引到列表中的操作将从开始或结束遍历列表,以更接近指定的索引为准。
请注意,此实现不同步。 如果多个线程同时访问链接列表,并且至少有一个线程在结构上修改列表,则必须在外部进行同步。 (结构修改是添加或删除一个或多个元素的任何操作;仅设置元素的值不是结构修改。)
这通常通过在自然封装列表的对象上进行同步来实现。 如果没有这样的对象存在,列表应该使用 Collections.synchronizedList 方法“包装”。 这最好在创建时完成,以防止意外的不同步访问列表:
List list = Collections.synchronizedList(new LinkedList(...));
这个类的 iterator 和 listIterator 方法返回的迭代器是故障快速的 :如果列表在迭代器创建之后的任何时间被结构化地修改,除了通过迭代器自己的remove或add方法之外,
迭代器将会抛出一个ConcurrentModificationException 。 因此,面对并发修改,迭代器将快速而干净地失败,而不是在未来未确定的时间冒着任意的非确定性行为。
请注意,迭代器的故障快速行为无法保证,因为一般来说,在不同步并发修改的情况下,无法做出任何硬性保证。
失败快速迭代器尽力投入ConcurrentModificationException 。 因此,编写依赖于此异常的程序的正确性将是错误的:迭代器的故障快速行为应仅用于检测错误。
(以上来自 Java8 api)
分析
首先看一下 LinkedList 的继承关系:
定义
1 | public class LinkedList<E> |
- LinkedList 是一个继承于 AbstractSequentialList 的双向链表。它也可以被当作堆栈、队列或双端队列进行操作。
最大限度地减少了实现受“连续访问”数据存储(如链接列表)支持的此接口所需的工作,从而以减少实现 List 接口的复杂度。 - LinkedList 实现 List 接口,能对它进行序列(有序集合)操作。
- LinkedList 实现 Deque 接口,即能将LinkedList当作双端队列使用。
- LinkedList 实现了 Cloneable 接口,即覆盖了函数 clone(),能克隆。
- LinkedList 实现 java.io.Serializable 接口,这意味着 LinkedList 支持序列化,能通过序列化去传输。
- LinkedList 是非同步的。
属性
1 | public class LinkedList<E> extends AbstractSequentialList<E> implements List<E>, Deque<E>, Cloneable, java.io.Serializable{ |
构造方法
由于采用的是链表结构,所以不像 ArrayList 一样,有指定容量的构造方法
1 | public class LinkedList<E> extends AbstractSequentialList<E> implements List<E>, Deque<E>, Cloneable, java.io.Serializable{ |
增加 add(E e)
作为链表,添加新元素就是在链表的末尾插入新元素。
注意,如果末尾元素是 null ,又该如何处理?
1 | public class LinkedList<E> extends AbstractSequentialList<E> implements List<E>, Deque<E>, Cloneable, java.io.Serializable{ |
LinkedList 还有其他的增加方法:
- add(int index, E element):在此列表中指定的位置插入指定的元素。
- addAll(Collection<? extends E> c):添加指定 collection 中的所有元素到此列表的结尾,顺序是指定 collection 的迭代器返回这些元素的顺序。
- addAll(int index, Collection<? extends E> c):将指定 collection 中的所有元素从指定位置开始插入此列表。
- AddFirst(E e): 将指定元素插入此列表的开头。
- addLast(E e): 将指定元素添加到此列表的结尾。
移除
处理思路:
- 由于插入的元素可能为null,所以要对o进行判断,否则不论是o为null还是遍历的时候元素为null,都会导致报空指针异常
- 找到元素后,对前后的元素关系重新维护,要考虑到元素是否在头尾的情况
1 | public class LinkedList<E> extends AbstractSequentialList<E> implements List<E>, Deque<E>, Cloneable, java.io.Serializable{ |
其他的移除方法:
- clear(): 从此列表中移除所有元素。
- remove():获取并移除此列表的头(第一个元素)。
- remove(int index):移除此列表中指定位置处的元素。
- remove(Objec o):从此列表中移除首次出现的指定元素(如果存在)。
- removeFirst():移除并返回此列表的第一个元素。
- removeFirstOccurrence(Object o):从此列表中移除第一次出现的指定元素(从头部到尾部遍历列表时)。
- removeLast():移除并返回此列表的最后一个元素。
- removeLastOccurrence(Object o):从此列表中移除最后一次出现的指定元素(从头部到尾部遍历列表时)。
查询
查询的方法非常简单,
1 | public class LinkedList<E> extends AbstractSequentialList<E> implements List<E>, Deque<E>, Cloneable, java.io.Serializable{ |
其它的查询方法:
- getFirst():返回此列表的第一个元素。
- getLast():返回此列表的最后一个元素。
- indexOf(Object o):返回此列表中首次出现的指定元素的索引,如果此列表中不包含该元素,则返回 -1。
- lastIndexOf(Object o):返回此列表中最后出现的指定元素的索引,如果此列表中不包含该元素,则返回 -1。
迭代器 listIterator
关于集合的快速失败机制的详细了解可以看这里
iterator() 调用的其实是 listIterator() 方法,对于不同的实现类,都会实现不同的方法,但是其原理是一致的,
都是为了防止多线程操作同一个集合而出现的问题
1 | public class LinkedList<E> extends AbstractSequentialList<E> implements List<E>, Deque<E>, Cloneable, java.io.Serializable{ |
有关队列、栈的方法
- peek():返回第一个节点,若LinkedList的大小为0,则返回null
- peekFirst():返回第一个节点,若LinkedList的大小为0,则返回null
- peekLast():返回最后一个节点,若LinkedList的大小为0,则返回null
- element():返回第一个节点,若LinkedList的大小为0,则抛出异常
- poll():删除并返回第一个节点,若LinkedList的大小为0,则返回null
- pollFirst():删除并返回第一个节点,若LinkedList的大小为0,则返回null
- pollLast():删除并返回最后一个节点,若LinkedList的大小为0,则返回null
- offer(E e):将e添加双向链表末尾
- offerFirst(E e):将e添加双向链表开头
- offerLast(E e):将e添加双向链表末尾
- push(E e):将e插入到双向链表开头
- pop():删除并返回第一个节点
LinkedList 作为 FIFO(先进先出) 的队列, 下表的方法等效:
队列方法 | 等效方法 |
---|---|
add(e) | addLast(e) |
offer(e) | offerLast(e) |
remove() | removeFirst() |
poll() | pollFirst() |
element() | getFirst() |
peek() | peekFirst() |
LinkedList 作为 LIFO(后进先出) 的栈, 下表的方法等效:
栈方法 | 等效方法 |
---|---|
push(e) | addFirst(e) |
pop() | removeFirst() |
peek() | peekFirst() |
LinkedList 的遍历方法和性能比较
使用示例
总结
- LinkedList 实际上是通过双向链表去实现的。它包含一个非常重要的内部类:
Node
。Node
是双向链表节点所对应的数据结构,
它包括的属性有:当前节点所包含的值,上一个节点,下一个节点。 - 从 LinkedList 的实现方式中可以发现,它不存在LinkedList容量不足的问题。
- LinkedList 的克隆函数,即是将全部元素克隆到一个新的LinkedList对象中。
- LinkedList 实现java.io.Serializable。当写入到输出流时,先写入“容量”,再依次写入“每一个节点保护的值”;
当读出输入流时,先读取“容量”,再依次读取“每一个元素”。 - 由于 LinkedList 实现了Deque,而 Deque 接口定义了在双端队列两端访问元素的方法。提供插入、移除和检查元素的方法。
每种方法都存在两种形式:一种形式在操作失败时抛出异常,另一种形式返回一个特殊值(null 或 false,具体取决于操作)。