概述
之前介绍了 ArrayList 的整体架构。接下来我们来继续看List的另一个实现 LinkedList。主要分为以下部分:
- 第一部分 AbstractSequentialList简介
- 第二部分 LinkedList简介
- 第三部分 数据结构分析
- 第四部分 源码分析
- 第五部分 遍历方式
AbstractSequentialList简介
在介绍LinkedList之前,我们先介绍下AbstractSequentailList,因为它是LinkedList是它的子类。
AbstractSequentialList 的定义如下:
AbstractSequentialList 实现了List接口中的大部分方法,让其他类的实现者花费更小的精力去实现一个可以顺序访问的列表。如果要实现随机访问,应该优先使用AbstractList这个类。
This class is the opposite of the AbstractList class in the sense that it implements the “random access” methods (get(int index), set(int index, E element), add(int index, E element) and remove(int index)) on top of the list’s list iterator, instead of the other way around.
上面这段话的意思是:”随机访问”方法(get(int index), set(int index, E element), add(int index, E element) 和 remove(int index))是在列表的列表迭代器的基础上实现的,而不是反过来。在这种理解下,这个类的实现其实是与AbstractList类相反的(译者注:因为AbstractList是在真正的随机访问的数据结构上实现的)。
此外,如果我们要通过AbstractSequentailList实现自己的列表,只需要扩展此类,并提供 ListIterator() 和 Size() 方法的实现即可。若要实现不可修改的列表,则只需要实现列表迭代器的hasNext, next, hasPrevious, previous 和 index 方法即可。
LinkedList简介
LinkedList的定义如下:
LinkedList是一个双向链表,它可以用来做堆栈、队列或者双端队列(一种具有队列和栈性质的数据结构。双端队列中的元素可以从两端弹出,插入和删除操作限定在队列的两边进行)。
LinkedList继承了AbstractSequentialList,说明它是顺序访问数据的。
LinkedList实现了List,能进行列表操作。
LinkedList实现了Deque,即可以把LinkedList当双端队列使用。
LinkedList实现了Cloneable,即clone()被覆盖,可以被克隆。
LinkedList实现了java.io.Serializable,这意味着LinkedList支持序列化,能通过序列化去传输。
LinkedList的构造方法
|
|
LinkedList的API
|
|
数据结构分析
LinkedList的继承关系
|
|
LinkedList与Collection的关系
LinkedList包含3个重要数据:first、last和size。
- first 是链表的表头,它是双向链表节点所对应的Node实例。Node包含item/next/prev,item是该节点的值,next是该节点的下一个节点,prev是该节点的上一个节点,first的prev是null;
- last 是链表的尾部,与first类似,但它的next是null;
- size 是链表的节点个数。
LinkedList源码分析(基于JDK 1.8.0_40)
源码分析
为了更了解LinkedList的实现原理,我们先对LinkedList的源码做简要分析:
LinkedList是一个双向链表,表示它可以向前或者向后去获取节点,它也是一个双端队列,表示可以从头部或者尾部操作元素。既然是双向链表,那么它的顺序访问将会非常高效,但是随机访问效率比较低。
那LinkedList是如何实现List接口的get(int index)的随机访问方法的呢?如何将双向链表和索引值联系起来呢?
其实实现非常简单,LinkedList通过一个计数索引值来实现的。例如,当我们调用get(int index)的时候,LinkedList首先会比较index和“双向链表长度的1/2”,如果前者大,则从双向链表的尾部往前进行查找,如果后者大,则从双向链表的头部往后进行查找,直到找到index位置。这就是双向链表和索引值联系起来的方法。
接下来开始阅读源码:
总结
- LinkedList实际上是通过双向链表来实现的。它包含一个重要的内部类:Node,Node双向链表所对应的数据结构,它包含的属性有:item当前值,next下一个节点,prev上一个节点;
- 从LinkedList的实现方式上可以发现,LinkedList不存在容量不足的情况;
- LinkedList的克隆方法,是将所有的节点元素克隆到一个新的LinkedList对象中;
- LinkedList实现java.io.Serializable。写入到输出流时,先写入“容量”,再依次写入每个节点元素;当读入输入流时,先读取“容量”,再依次读取每一个节点元素;
- 由于LinkedList实现了Deque,而Deque接口定义在双端队列访问元素的方法。提供插入、移除和检查元素的方法。每种方法都有2种形式:一种形式在操作失败时抛出异常(NoSuchElementException),另一种形式在失败时返回一个特殊值(null或者false,取决于具体操作);总结如下:
头部 | 尾部 | |||
---|---|---|---|---|
抛出异常 | 特殊值 | 抛出异常 | 特殊值 | |
插入 | addFirst(e) | offerFirst(e) | addLast(e) | offerLast(e) |
移除 | removeFirst() | pollFirst() | removeLast() | pollLast() |
查找 | getFirst() | peekFirst() | getLast() | peekLast() |
- LinkedList作为FIFO先进先出的队列,作为FIFO的队列时,下列方法等价:
FIFO队列方法 | 等价方法 | 描述 |
---|---|---|
add(e) | addLast(e) | 添加元素,失败抛出异常 |
offer(e) | offerLast(e) | 添加元素,失败返回false |
remove() | removeFirst() | 删除元素,失败抛出异常 |
poll() | pollFirst() | 删除元素,失败返回null |
element() | getFirst() | 获取元素,失败抛出异常 |
peek() | peekFirst() | 获取元素,失败返回null |
- LinkedList作为LIFO后进先出的栈,下列方法等价:
LIFO队列方法 | 等价方法 |
---|---|
push(e) | addFirst(e) |
pop() | removeFirst() |
peek() | peekFirst() |
LinkedList遍历方式
第一种:迭代器遍历
|
|
第二种:随机访问遍历
|
|
第三种:forEach遍历
|
|
下面通过实例来测试这3种遍历方式的耗时:
执行结果:
结论:
遍历LinkedList的时候,效率的排序依次是: forEach遍历 > 迭代器遍历 > 随机访问遍历;
千万不要使用随机访问去遍历LinkedList!!!
千万不要使用随机访问去遍历LinkedList!!!
千万不要使用随机访问去遍历LinkedList!!!