一 概述
深入理解Java集合中的源代码,可以帮助我们更好地了解大佬的意图,规避不必要的bug。
源码中的一段注释,提取关键信息
Doubly-linked list implementation of the List and Deque interfaces. Implements all optional list operations, and permits all elements (including 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.
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
由上述注释中可以大概得知: LinkedList 是由一个双向链表来实现的,它允许插入所有元素,包括 null,同时,它是线程不同步的。
双向链表结构示意图:
双向链表每个结点除了数据域之外,还有一个前指针和后指针,分别指向前驱结点 和后继结点(如果有前驱/后继的话)。另外,双向链表还有一个 first 指针,指向头 节点,和 last 指针,指向尾节点。
二 属性
1 | //链表的节点个数 |
LinkedList属性非常少,由上述三个属性基本可以知道他是怎么实现的。
三 方法
1 节点结构
Node 是在 LinkedList 里定义的一个静态内部类,它表示链表每个节点的结构,包括一个数据域 item,一个后置指针 next,一个前置指针 prev。
1 | private static class Node<E> { |
2 添加元素
对于链表这种数据结构来说,添加元素的操作无非就是在表头/表尾插入元素,又或 者在指定位置插入元素。因为 LinkedList 有头指针和尾指针,所以在表头或表尾进 行插入元素只需要 O(1) 的时间,而在指定位置插入元素则需要先遍历一下链表, 所以复杂度为 O(n)。
在表头添加元素的过程如下:
当向表头插入一个节点时,很显然当前节点的前驱一定为 null,而后继结点是 first 指针指向的节点,当然还要修改 first 指针指向新的头节点。除此之外,原来的头节
点变成了第二个节点,所以还要修改原来头节点的前驱指针,使它指向表头节点, 源码的实现如下:
1 | private void linkFirst(E e) { |
在表尾添加元素跟在表头添加元素大同小异,如图所示:
当向表尾插入一个节点时,很显然当前节点的后继一定为 null,而前驱结点是 last 指针指向的节点,然后还要修改 last 指针指向新的尾节点。此外,还要修改原来尾 节点的后继指针,使它指向新的尾节点,源码的实现如下:
1 | void linkLast(E e) { |
最后,在指定节点之前插入,如图所示:
当向指定节点之前插入一个节点时,当前节点的后继为指定节点,而前驱结点为指 定节点的前驱节点。此外,还要修改前驱节点的后继为当前节点,以及后继节点的 前驱为当前节点,源码的实现如下:
1 | void linkBefore(E e, Node<E> succ) { |
3 删除元素
删除操作与添加操作大同小异,例如删除指定节点的过程如下图所示,需要把当前 节点的前驱节点的后继修改为当前节点的后继,以及当前节点的后继结点的前驱修 改为当前节点的前驱
删除头节点和尾节点跟删除指定节点非常类似
1 | //删除表头节点,返回表头元素的值 |
4 获取元素
1 | //获取表头元素 |
5 常用方法
上述方法都不是 public 的,LinkedList 是在这些基础的方法进行操作的,下面就来看看可以调用的方法有哪些
1 | //删除表头元素 |
四 总结
1、LinkedList 的底层结构是一个带头/尾指针的双向链表,可以快速的对头/尾节点 进行操作。
2、相比数组,链表的特点就是在指定位置插入和删除元素的效率较高,但是查找的 效率就不如数组那么高了。