概览
同 ArrayList 一样,LinkedList 也是对 List 接口的一种具体实现。不同的是,ArrayList 是基于数组来实现的,而 LinkedList 是基于双向链表实现的。LinkedList 类的声明如下:
1 2 3
|
public class LinkedList<E> extends AbstractSequentialList<E> implements List<E>, Deque<E>, Cloneable, java.io.Serializable
|
LinkedList 继承自 AbstractSequentialList,实现了 List 接口,同时还实现了 Deque 接口。AbstractSequentialList 是 AbstractList 的子类,为基于顺序访问的链表提供了一些基本的实现。LinkedList 实现了 Deque 接口,Deque 接口是一种双端队列,可以作为 FIFO 队列和 LIFO 队列(栈)来使用。LinkedList 支持所有元素,包括 null。
下面基于JDK 8 中的代码对LinkedList的实现加以分析。
底层结构
LinkedList 基于双向链表进行实现,并使用两个引用 first 和 last 分别指向双向链表的头尾元素。同 ArrayList 一样,使用 modCount 来记录结构化修改的次数,并依此实现 fail-fast 机制。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
|
transient int size = 0;
/** * Pointer to first node. * Invariant: (first == null && last == null) || * (first.prev == null && first.item != null) */ transient Node<E> first;
/** * Pointer to last node. * Invariant: (first == null && last == null) || * (last.next == null && last.item != null) */ transient Node<E> last;
|
双向链表的节点结构如下,分别用 prev 和 next 指向该节点的前驱和后继结点。
1 2 3 4 5 6 7 8 9 10 11
|
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 提供的所有操作都是在双向链表的基础上完成的。Dqeue 接口的一些实现就是在 双向链表的两端进行操作,也是基于对头和尾部元素的操作。总的来说,双向链表的操作并不复杂,下面简单地进行解析,大部分操作都是对以下几种操作的封装。
向头部添加元素
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
|
//将一个元素加入双向链表的头部 private void linkFirst(E e) { final Node<E> f = first; //新节点的前驱节点为null,后继节点为原来的头节点 final Node<E> newNode = new Node<>(null, e, f); first = newNode; if (f == null) //原来的头节点为空 //新加入的节点是第一个也是最后一个 last = newNode; else //修改原来头节点的前驱指向 f.prev = newNode; //调整链表大小 size++; //修改计数器 modCount++; }
|
向尾部添加元素
1 2 3 4 5 6 7 8 9 10 11
|
void linkLast(E e) { final Node<E> l = last; final Node<E> newNode = new Node<>(l, e, null); last = newNode; if (l == null) first = newNode; else l.next = newNode; size++; modCount++; }
|
从中间插入元素
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
|
//在一个非空节点前插入元素 void linkBefore(E e, Node<E> succ) { // assert succ != null; final Node<E> pred = succ.prev; //获取前驱 //注意新建节点的前驱与后继 final Node<E> newNode = new Node<>(pred, e, succ); //调整相关节点的前驱与后继关系 succ.prev = newNode; if (pred == null) first = newNode; else pred.next = newNode; //修改大小 size++; modCount++; }
|
移除头部节点
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
|
//将头节点从链表移除 private E unlinkFirst(Node<E> f) { // assert f == first && f != null; final E element = f.item; final Node<E> next = f.next; //引用修改为null,方便GC f.item = null; f.next = null; // help GC //调整头节点 first = next; if (next == null) //移除后链表为空 last = null; else next.prev = null; size--; modCount++; return element; }
|
移除尾部节点
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
|
private E unlinkLast(Node<E> l) { // assert l == last && l != null; final E element = l.item; final Node<E> prev = l.prev; l.item = null; l.prev = null; // help GC last = prev; if (prev == null) first = null; else prev.next = null; size--; modCount++; return element; }
|
移除任意一个非空节点
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28
|
//移除一个非空节点 E unlink(Node<E> x) { // assert x != null; final E element = x.item; final Node<E> next = x.next; //前驱 final Node<E> prev = x.prev; //后继
//注意对前驱为null的处理 if (prev == null) { first = next; } else { prev.next = next; x.prev = null; }
//注意对后继为null的处理 if (next == null) { last = prev; } else { next.prev = prev; x.next = null; }
x.item = null; //GC size--; modCount++; return element; }
|
清空链表
主要是为了方便垃圾回收器进行垃圾回收。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
|
public void clear() { // Clearing all of the links between nodes is "unnecessary", but: // - helps a generational GC if the discarded nodes inhabit // more than one generation // - is sure to free memory even if there is a reachable Iterator for (Node<E> x = first; x != null; ) { Node<E> next = x.next; x.item = null; x.next = null; x.prev = null; x = next; } first = last = null; size = 0; modCount++; }
|
根据索引获取元素
因为是双向链表,可向前遍历,也可向后遍历。查找时双向进行,靠近头节点则从前向后查找;靠近尾部,则从后向前查找。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
|
//根据索引获取元素 Node<E> node(int index) { // assert isElementIndex(index); //双向查找,根据index和size判断 //前半段,从头节点向后查找 //后半段,从尾节点向前查找 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; } }
|
反查一个元素的索引
第一次出现:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
|
public int indexOf(Object o) { int index = 0; if (o == null) { for (Node<E> x = first; x != null; x = x.next) { if (x.item == null) return index; index++; } } else { for (Node<E> x = first; x != null; x = x.next) { if (o.equals(x.item)) return index; index++; } } return -1; }
|
最后一次出现,从后向前查找:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
|
public int lastIndexOf(Object o) { int index = size; if (o == null) { for (Node<E> x = last; x != null; x = x.prev) { index--; if (x.item == null) return index; } } else { for (Node<E> x = last; x != null; x = x.prev) { index--; if (o.equals(x.item)) return index; } } return -1; }
|
迭代器
通过 next 的指向依次进行遍历。还提供了反向的迭代(从尾部到头部),通过 prev 的指向依次遍历。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102
|
private class ListItr implements ListIterator<E> { private Node<E> lastReturned; private Node<E> next; private int nextIndex; //创建迭代器时的modCount,检测并发修改,fail-fast private int expectedModCount = modCount;
ListItr(int index) { // assert isPositionIndex(index); next = (index == size) ? null : node(index); nextIndex = index; }
public boolean hasNext() { return nextIndex < size; }
public E next() { checkForComodification(); if (!hasNext()) throw new NoSuchElementException();
lastReturned = next; next = next.next; nextIndex++; return lastReturned.item; }
public boolean hasPrevious() { return nextIndex > 0; }
public E previous() { checkForComodification(); if (!hasPrevious()) throw new NoSuchElementException();
//如果next == null, 前一个为尾节点 lastReturned = next = (next == null) ? last : next.prev; nextIndex--; return lastReturned.item; }
public int nextIndex() { return nextIndex; }
public int previousIndex() { return nextIndex - 1; }
public void remove() { checkForComodification(); if (lastReturned == null) throw new IllegalStateException();
Node<E> lastNext = lastReturned.next; //调用unlink方法移除元素 unlink(lastReturned); if (next == lastReturned) next = lastNext; else nextIndex--; lastReturned = null; //修改modCount expectedModCount++; }
public void set(E e) { if (lastReturned == null) throw new IllegalStateException(); checkForComodification(); lastReturned.item = e; }
public void add(E e) { checkForComodification(); lastReturned = null; if (next == null) linkLast(e); else linkBefore(e, next); nextIndex++; expectedModCount++; }
public void forEachRemaining(Consumer<? super E> action) { Objects.requireNonNull(action); while (modCount == expectedModCount && nextIndex < size) { action.accept(next.item); lastReturned = next; next = next.next; nextIndex++; } checkForComodification(); } //检查并发修改,fail-fast final void checkForComodification() { if (modCount != expectedModCount) throw new ConcurrentModificationException(); } }
|
小结
LinkedList 是 List 接口基于双向链表的一种实现,同时还实现了 Deque 接口,可以作为 FIFO 和 LIFO 队列使用。双向链表的实现使得插入和删除操作的代价降低,可以在常数时间内完成;然而查找操作需要遍历列表,尽管双向列表使得可以从两端进行查找,但在长度较长时仍然需要较长的时间。
在大多数情况下会选择使用 ArrayList,尽管插入和删除代价相较于 LinkedList 更高,但随机访问的特性使得在查找方面 ArrayList 比 LinkedList 具有更多的优势。关于 ArrayList 和 LinkedList 的使用选择上可以参考 StackOverflow 上的这个问答。
- 基于JDK1.8,Java容器源码分析
容器源码分析 如果没有特别说明,以下源码分析基于 JDK 1.8. 在 IDEA 中 double shift 调出 Search EveryWhere,查找源码文件,找到之后就可以阅读源码. Lis ...
- Java 容器源码分析之Map-Set-List
HashMap 的实现原理 HashMap 概述 HashMap 是基于哈希表的 Map 接口的非同步实现.此实现提供所有可选的映射操作,并允许使用 null 值和 null 键.此类不保证映射的顺序 ...
- Java集合源码分析之LinkedList
1. LinkedList简介 public class LinkedList<E> extends AbstractSequentialList<E> implements ...
- Java容器源码解析之——LinkedList
我们直接从源码来分析LinkedList的结构: public class LinkedList<E> extends AbstractSequentialList<E> im ...
- Java 容器源码分析之1.7HashMap
以下内容基于jdk1.7.0_79源码: 什么是HashMap 基于哈希表的一个Map接口实现,存储的对象是一个键值对对象(Entry<K,V>): HashMap补充说明 基于数组和链表 ...
- java容器源码分析及常见面试题笔记
概览 容器主要包括 Collection 和 Map 两种,Collection 存储着对象的集合,而 Map 存储着键值对(两个对象)的映射表. List Arraylist: Object数组 ...
- Java 容器源码分析之 ArrayList
概览 ArrayList是最常使用的集合类之一了.在JDK文档中对ArrayList的描述是:ArrayList是对list接口的一种基于可变数组的实现.ArrayList类的声明如下: 12 pub ...
- Java 容器源码分析之ConcurrentHashMap
深入浅出ConcurrentHashMap(1.8) 前言 HashMap是我们平时开发过程中用的比较多的集合,但它是非线程安全的,在涉及到多线程并发的情况,进行put操作有可能会引起死循环,导致CP ...
- Java 容器源码分析之 TreeMap
TreeMap 是一种基于红黑树实现的 Key-Value 结构.在使用集合视图在 HashMap 中迭代时,是不能保证迭代顺序的: LinkedHashMap 使用了双向链表,保证按照插入顺序或者访 ...
随机推荐
- mycat 主从切换分析过程
67 68互为主从 66为67从 区分双主写的数据,设置不同的自增id 67: SET @@auto_increment_offset=2;SET @@auto_increment_increment ...
- spark入门
这一两年Spark技术很火,自己也凑热闹,反复的试验.研究,有痛苦万分也有欣喜若狂,抽空把这些整理成文章共享给大家.这个系列基本上围绕了Spark生态圈进行介绍,从Spark的简介.编译.部署,再到编 ...
- jQuery-爱奇艺图片切换
<!DOCTYPE html><html> <head> <meta charset="UTF-8"> <title>& ...
- python学习总结(一)
1.编码格式发展历史 ASCII 255 Ibytes --> 1980 gb2312 7000+ -->1995 GBK1.0 2W+ -->2000 GB18030 2.7W+ ...
- 自定义Token的CAS登录
工作中实际遇到的需求,我们有一个旧系统,用了CAS的单点登录,现在有一个外部系统,准备从它那里单点进来,这个外部系统提供了一个token参数来标记这是哪一个用户,我们用他们提供的方式解析出对应的用户, ...
- delphi 窗体最大化 最小化
procedure TForm1.SpeedButton2Click(Sender: TObject); begin sendmessage(form1.WindowHandle,WM_SYSCOMM ...
- Java并发编程:Lock(锁)
一.synchronized的缺陷 synchronized是java中的一个关键字,也就是说是Java语言内置的特性.那么为什么会出现Lock呢? 在上面一篇文章中,我们了解到如果一个代码块被syn ...
- Jenkins 定时构建语法规则
1.Jenkins自由风格任务定时构建 2.语法规则 定时构建语法 * * * * * 第一个*表示分钟,取值0~59 第二个*表示小时,取值0~23 第三个*表示一个月的第几天,取值1~31 第四个 ...
- react-native 项目初始化
react-native 项目初始化 搭建java,android,node环境 http://www.cnblogs.com/morang/p/react-native-java-build.htm ...
- 京东购物车的 Java 架构实现及原理!
今天来写一下关于购物车的东西, 这里首先抛出四个问题: 1)用户没登陆用户名和密码,添加商品, 关闭浏览器再打开后 不登录用户名和密码 问:购物车商品还在吗? 2)用户登陆了用户名密码,添加商品,关闭 ...