集合系列 List(四):LinkedList
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的更多相关文章
- Java 集合系列05之 LinkedList详细介绍(源码解析)和使用示例
概要 前面,我们已经学习了ArrayList,并了解了fail-fast机制.这一章我们接着学习List的实现类——LinkedList.和学习ArrayList一样,接下来呢,我们先对Linked ...
- 【转】Java 集合系列05之 LinkedList详细介绍(源码解析)和使用示例
概要 前面,我们已经学习了ArrayList,并了解了fail-fast机制.这一章我们接着学习List的实现类——LinkedList.和学习ArrayList一样,接下来呢,我们先对Linked ...
- Java 集合系列(四)—— ListIterator 源码分析
以脑图的形式来展示Java集合知识,让零碎知识点形成体系 Iterator 对比 Iterator(迭代器)是一种设计模式,是一个对象,用于遍历集合中的所有元素. Iterator 包含四个方法 ...
- 【Java集合系列二】LinkedList解析
一.简介 1.LinkedList继承关系 2.LinkedList底层实现 LinkedList使用双向链表存储数据,所以没有默认的容量,也不会有扩容一说.只有两个指针,永远指向链表的两端:firs ...
- Java 集合系列08之 List总结(LinkedList, ArrayList等使用场景和性能分析)
概要 前面,我们学完了List的全部内容(ArrayList, LinkedList, Vector, Stack). Java 集合系列03之 ArrayList详细介绍(源码解析)和使用示例 Ja ...
- 【转】Java 集合系列08之 List总结(LinkedList, ArrayList等使用场景和性能分析)
概要 前面,我们学完了List的全部内容(ArrayList, LinkedList, Vector, Stack). Java 集合系列03之 ArrayList详细介绍(源码解析)和使用示例 Ja ...
- 【Java集合系列】目录
2017-07-29 13:49:40 一.Collection的全局继承关系 二.系列文章 [Java集合系列一]ArrayList解析 备注: 1.ArrayList本质上就是一个数组,所有对外提 ...
- Java 集合系列目录(Category)
下面是最近总结的Java集合(JDK1.6.0_45)相关文章的目录. 01. Java 集合系列01之 总体框架 02. Java 集合系列02之 Collection架构 03. Java 集合系 ...
- Java 集合系列 07 List总结(LinkedList, ArrayList等使用场景和性能分析)
java 集合系列目录: Java 集合系列 01 总体框架 Java 集合系列 02 Collection架构 Java 集合系列 03 ArrayList详细介绍(源码解析)和使用示例 Java ...
- Java 集合系列 04 LinkedList详细介绍(源码解析)和使用示例
java 集合系列目录: Java 集合系列 01 总体框架 Java 集合系列 02 Collection架构 Java 集合系列 03 ArrayList详细介绍(源码解析)和使用示例 Java ...
随机推荐
- Python3 函数进阶1
目录 闭包函数 什么是闭包函数 闭包函数的作用 装饰器 什么是装饰器 无参装饰器 有参装饰器 闭包函数 什么是闭包函数 闭包函数本质上就是函数嵌套和高阶函数 闭包函数的满足条件: 必须嵌套函数 内嵌函 ...
- 【JS】380- JavaScript 正则新特性
概括 如果你曾用 JavaScript 进行过复杂的文本处理操作,那么你将会喜欢 ES2018 中引入的新特性.本文将详细介绍第9版标准如何提高 JavaScript 的文本处理能力. 大多数编程语言 ...
- Shell排序 C&&C++
Shell排序 Shell排序是大量数据需要排序时,更为高效的插入排序.它的算法思想基于插入排序的算法思想 流程: (1)将n个元素数组分成n/2个数字序列,第一个数据和第n/2个数据为一对,等等 ...
- get请求被浏览器跨域的同源策略请求机制拦截,但是get请求是否请求到了服务器呢
浏览器会拦截跨域请求,但是只是拦截返回结果,请求还是会被发送到服务器. 请求因为跨域被拦截后,会改成 OPTIONS 请求送达服务器,这样服务器就可以知道有人在请求.
- 牛客练习赛31A 地、颜色、魔法(搜索+二维数组一维表示)
红色来源于山脉,象征着狂躁.愤怒.混乱,血雨腥风,电光火石. 蓝色来源于海岛,象征着控制.幻觉.诡计,运筹帷幄,谋定后动. 绿色来源于树林,象征着生命.蛮力.成长,横冲直撞,生生不息. 黑色来源于沼泽 ...
- 比较typeof与instanceof
相同点: JavaScript中typeof和instanceof常用来判断一个变量是否为空,或者是什么类型的. 不同点: typeof的定义和用法: 返回值是一个字符串,用来说明变量的数据类型. 细 ...
- SMTP email from C#
/// <summary> /// 一人一附件发送邮件 /// 2017-05-17 涂聚文 GeovinDu /// </summary> /// <param nam ...
- openssl 证书请求和签名命令req基本分析
一 基本概念: OpenSSL 是一个开源项目,其组成主要包括一下三个组件: openssl:多用途的命令行工具 libcrypto:加密算法库 libssl:加密模块应用库,实现了ssl及tls o ...
- javascript学习总结之函数
前言 在学习javascript函数的时候,有几个经常很容易混淆的方法,call,apply,bind,caller,callee,这些方法的使用,这些也可以说是会频繁使用的一些方法,在此通过查阅相关 ...
- Linux中获取本机的最新IPv6地址_更新ddns的脚本
Linux中获取本机的最新IPv6地址_更新ddns的脚本 转载注明来源: 本文链接 来自osnosn的博客,写于 2019-11-07. 运营商提供ipv6地址. 路由器后有台linux机器,通过e ...