LinkedList
LinkedList是基于双向链表数据结构实现的。
这个双向链表结构,链表中的每个节点都可以向前或者向后追溯,有几个概念如下:
链表每个节点我们叫做 Node,Node 有 prev 属性,代表前一个节点的位置,next 属性,代表后一个节点的位置;
first 是双向链表的头节点,它的前一个节点是 null。
last 是双向链表的尾节点,它的后一个节点是 null;
当链表中没有数据时,first 和 last 是同一个节点,前后指向都是 null;
因为是个双向链表,只要机器内存足够强大,是没有大小限制的。
Node结构中包含了3个部分:元素内容item、前指针prev以及后指针next,代码如下。
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; } }
LinkedList就是由Node结构对象连接而成的一个双向链表。
实现类
LinkedList类实现了List接口、Deque接口,同时继承了AbstractSequentialList抽象类,LinkedList既实现了List类型又有Queue类型的特点;LinkedList也实现了Cloneable和Serializable接口,同ArrayList一样,可以实现克隆和序列化。
由于LinkedList存储数据的内存地址是不连续的,而是通过指针来定位不连续地址,因此,LinkedList不支持随机快速访问,LinkedList也就不能实现RandomAccess接口。
public class LinkedList extends AbstractSequentialList implements List, Deque, Cloneable, java.io.Serializable
基本属性
transient int size = 0; transient Node first; transient Node last;
我们可以看到这三个属性都被transient修饰了,原因很简单,我们在序列化的时候不会只对头尾进行序列化,所以LinkedList也是自行实现readObject和writeObject进行序列化与反序列化。
下面是LinkedList自定义序列化的方法。
节点查询
链表查询某一个节点是比较慢的,需要挨个循环查找才行,我们看看 LinkedList 的源码是如何寻找节点的:
LinkedList 并没有采用从头循环到尾的做法,而是采取了简单二分法,首先看看 index 是在链表的前半部分,还是后半部分。
如果是前半部分,就从头开始寻找,反之亦然。通过这种方式,使循环的次数至少降低了一半,提高了查找的性能。
新增元素
LinkedList添加元素的实现很简洁,但添加的方式却有很多种。
默认的add (Ee)方法是将添加的元素加到队尾,首先是将last元素置换到临时变量中,生成一个新的Node节点对象,然后将last引用指向新节点对象,之前的last对象的前指针指向新节点对象。
LinkedList也有添加元素到任意位置的方法,如果我们是将元素添加到任意两个元素的中间位置,添加元素操作只会改变前后元素的前后指针,指针将会指向添加的新元素,所以相比ArrayList的添加操作来说,LinkedList的性能优势明显。
删除元素
在LinkedList删除元素的操作中,我们首先要通过循环找到要删除的元素,如果要删除的位置处于List的前半段,就从前往后找;若其位置处于后半段,就从后往前找。
这样做的话,无论要删除较为靠前或较为靠后的元素都是非常高效的,但如果List拥有大量元素,移除的元素又在List的中间段,那效率相对来说会很低。
遍历元素
LinkedList的获取元素操作实现跟LinkedList的删除元素操作基本类似,通过分前后半段来循环查找到对应的元素,但是通过这种方式来查询元素是非常低效的,特别是在for循环遍历的情况下,每一次循环都会去遍历半个List。
所以在LinkedList循环遍历时,我们可以使用iterator方式迭代循环,直接拿到我们的元素,而不需要通过循环查找List。