LinkedList 是链表的经典实现,其底层采用链表节点的方式实现。

public class LinkedList<E>
extends AbstractSequentialList<E>
implements List<E>, Deque<E>, Cloneable, java.io.Serializable

从类继承结构图可以看到,LinkedList 不仅实现了 List 接口,还实现了 Deque 双向队列接口。

原理

为了深入理解 LinkedList 的原理,我们将从类成员变量、构造方法、核心方法两个方面逐一介绍。

类成员变量

// 链表大小
transient int size = 0;
// 首节点
transient Node<E> first;
// 尾节点
transient Node<E> last;
// Node节点
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 总共有 2 个构造方法:

public LinkedList() {
}
public LinkedList(Collection<? extends E> c) {
this();
addAll(c);
}

构造方法比较简单,这里不深入介绍。

核心方法

在 LinkedList 中最为核心的是查找、插入、删除、扩容这几个方法。

查找

LinkedList 底层基于链表结构,无法向 ArrayList 那样随机访问指定位置的元素。LinkedList 查找过程要稍麻烦一些,需要从链表头结点(或尾节点)向后查找,时间复杂度为 O(N)。相关源码如下:

public E get(int index) {
checkElementIndex(index);
return node(index).item;
} Node<E> node(int index) {
/*
* 如果获取的元素小于容量的一般,则从头结点开始查找,否则从尾节点开始查找。
*/
if (index < (size >> 1)) {
Node<E> x = first;
// 循环向后查找,直至 i == index
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;
}
}

上面的代码比较简单,主要是通过遍历的方式定位目标位置的节点。获取到节点后,取出节点存储的值返回即可。这里面有个小优化,即通过比较 index 与节点数量 size/2 的大小,决定从头结点还是尾节点进行查找。

插入

LinkedList 除了实现了 List 接口相关方法,还实现了 Deque 接口的很多方法,例如:addFirst、addLast、offerFirst、offerLast 等。但这些方法的实现思路大致都是一样的,所以我只讲 add 方法的实现。

add 方法有两个方法,一个是直接插入队尾,一个是插入指定位置。

我们先来看第一个add方法:直接插入队列。

public boolean add(E e) {
linkLast(e);
return true;
}

可以看到其直接调用了 linkLast 方法,其实它就是 Deque 接口的一个方法。

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++;
}

上述代码进行了节点的创建以及引用的变化,最后增加链表的大小。

我们继续看第二个add方法:插入指定位置。

public void add(int index, E element) {
checkPositionIndex(index); if (index == size)
linkLast(element);
else
linkBefore(element, node(index));
}

如果我们插入的位置还是链表尾部,那么还是会调用 linkLast 方法。否则调用 node 方法取出插入位置的节点,否则调用 linkBefore 方法插入。

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++;
}

上述代码进行了节点的创建以及引用的变化,最后增加链表的大小。

删除

删除节点有两个方法,第一个是移除特定的元素,第二个是移除某个位置的元素。

我们先看第一个删除方法:移除特定的元素。

public boolean remove(Object o) {
if (o == null) {
for (Node<E> x = first; x != null; x = x.next) {
if (x.item == null) {
unlink(x);
return true;
}
}
} else {
for (Node<E> x = first; x != null; x = x.next) {
if (o.equals(x.item)) {
unlink(x);
return true;
}
}
}
return false;
}

上述代码的大致思路为:遍历找到删除的节点,之后调用 unlink() 方法解除引用。我们继续看看 unlink() 方法的代码。

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; if (prev == null) {
first = next;
} else {
prev.next = next;
x.prev = null;
} if (next == null) {
last = prev;
} else {
next.prev = prev;
x.next = null;
} x.item = null;
size--;
modCount++;
return element;
}

unlink() 代码里就是做了一系列的引用修改操作。下面的步骤图非常详细地解释了整个删除过程。

本文图片来源于田小波的博客

总结

经过上面的分析,我们可以知道 LinkedList 有如下特点:

  • 底层基于链表实现,修改速度快,读取速度慢(读取时间复杂度O(N),修改时间复杂度O(N),因为要查找元素,所以修改也是O(N))。
  • 非线程安全。
  • 与 ArrayList 不同,LinkedList 没有容量限制,所以也没有扩容机制。

集合系列 List(四):LinkedList的更多相关文章

  1. Java 集合系列05之 LinkedList详细介绍(源码解析)和使用示例

    概要  前面,我们已经学习了ArrayList,并了解了fail-fast机制.这一章我们接着学习List的实现类——LinkedList.和学习ArrayList一样,接下来呢,我们先对Linked ...

  2. 【转】Java 集合系列05之 LinkedList详细介绍(源码解析)和使用示例

    概要  前面,我们已经学习了ArrayList,并了解了fail-fast机制.这一章我们接着学习List的实现类——LinkedList.和学习ArrayList一样,接下来呢,我们先对Linked ...

  3. Java 集合系列(四)—— ListIterator 源码分析

    以脑图的形式来展示Java集合知识,让零碎知识点形成体系 Iterator 对比   Iterator(迭代器)是一种设计模式,是一个对象,用于遍历集合中的所有元素.  Iterator 包含四个方法 ...

  4. 【Java集合系列二】LinkedList解析

    一.简介 1.LinkedList继承关系 2.LinkedList底层实现 LinkedList使用双向链表存储数据,所以没有默认的容量,也不会有扩容一说.只有两个指针,永远指向链表的两端:firs ...

  5. Java 集合系列08之 List总结(LinkedList, ArrayList等使用场景和性能分析)

    概要 前面,我们学完了List的全部内容(ArrayList, LinkedList, Vector, Stack). Java 集合系列03之 ArrayList详细介绍(源码解析)和使用示例 Ja ...

  6. 【转】Java 集合系列08之 List总结(LinkedList, ArrayList等使用场景和性能分析)

    概要 前面,我们学完了List的全部内容(ArrayList, LinkedList, Vector, Stack). Java 集合系列03之 ArrayList详细介绍(源码解析)和使用示例 Ja ...

  7. 【Java集合系列】目录

    2017-07-29 13:49:40 一.Collection的全局继承关系 二.系列文章 [Java集合系列一]ArrayList解析 备注: 1.ArrayList本质上就是一个数组,所有对外提 ...

  8. Java 集合系列目录(Category)

    下面是最近总结的Java集合(JDK1.6.0_45)相关文章的目录. 01. Java 集合系列01之 总体框架 02. Java 集合系列02之 Collection架构 03. Java 集合系 ...

  9. Java 集合系列 07 List总结(LinkedList, ArrayList等使用场景和性能分析)

    java 集合系列目录: Java 集合系列 01 总体框架 Java 集合系列 02 Collection架构 Java 集合系列 03 ArrayList详细介绍(源码解析)和使用示例 Java ...

  10. Java 集合系列 04 LinkedList详细介绍(源码解析)和使用示例

    java 集合系列目录: Java 集合系列 01 总体框架 Java 集合系列 02 Collection架构 Java 集合系列 03 ArrayList详细介绍(源码解析)和使用示例 Java ...

随机推荐

  1. Java中通过代码得到int类型数值的二进制形式

    一.完整代码 public class BigInteger { int sing; byte[] val; public BigInteger(int val){ // 将传递的初始值,按位取值,存 ...

  2. 小胖求学系列之-文档生成利器(下)-smart-doc

    叮叮叮....一阵铃声响起,大家都陆续来到了课堂,看老师没来,小张和小胖又闲聊起来,小张问:怎么样,smart-doc好用吧.小胖笑着说:挺好用的,不过? 小张看卖关子,问到:不过什么,有什么新发现? ...

  3. web开发中浏览器跨域问题

    <system.webServer> <httpProtocol> <customHeaders> <add name="Access-Contro ...

  4. 使用Gin+WebSocket在HTML中无插件播放RTSP

    在后台的开发中遇到了对接显示摄像头视频流的需求.目前获取海康及大华等主流的摄像头的视频流使用的基本都是RTSP协议.不过HTML页面并不能直接播放RTSP协议的视频流,查询了一番各种网页播放RTSP的 ...

  5. 深入解析Java反射

    因为本人最近正筹备Samsara框架的开发,而其中的IOC部分非常依靠反射,因此趁这个机会来总结一下关于Java反射的一些知识.本篇为基本篇,基于JDK 1.8. 一.回顾:什么是反射? 反射(Ref ...

  6. 《Java基础知识》Java断言

    断言:也就是所谓的assertion,是jdk1.4后加入的新功能. 它主要使用在代码开发和测试时期,用于对某些关键数据的判断,如果这个关键数据不是你程序所预期的数据,程序就提出警告或退出. 当软件正 ...

  7. 学习Python编程技术的流程与步骤,自学与参加培训学习都适用

     一.清楚学习目标 无论是学习什么知识,都要有一个对学习目标的清楚认识.只有这样才能朝着目标持续前进,少走弯路,从学习中得到不断的提升,享受python学习计划的过程. 虽然目前的编程语言有很多,但是 ...

  8. 使用node+express+mongodb实现用户注册、登录和验证功能

    无论是手机端还是pc端,几乎都包含登录注册方面功能,今天就使用node+express+mongodb实现一套登录注册功能,这里需要自己去安装MongoDB环境,如果没有安装可以看这篇关于MongoD ...

  9. python单元测试unittest、setUp、tearDown()

    单元测试反应的是一种以测试为驱动的开发模式,最大的好处就是保证一个程序模块的行为符合我们设计的测试用例,在将来修改的时候,可以极大程度保证该模块行为仍然是正确的. 下面我编写一个Dict来,这个类的行 ...

  10. .Net中WebService的Demo示例

    一.创建一个Web服务 1.新建一个项目WebserverDemo 2.在项目处添加新建项,添加一个web服务 3.编辑TestServer.asmx文件 3.1 TestServer.asmx默认的 ...