LinkedList源码分析:JDK源码分析系列
如果本文中有不正确的地方请指出
由于没有留言可以在公众号添加我的好友共同讨论。
1.介绍
LinkedList 是线程不安全的,允许元素为null的双向链表。
2.继承结构
我们来看一下LinkedList的继承结构图:
代码实现:
public class LinkedList<E>
extends AbstractSequentialList<E>
implements List<E>, Deque<E>, Cloneable, java.io.Serializable
- Cloneable实现克隆
- Serializable序列化
- List 定义了一些集合类的方法
- Deque双向队列接口(就是两端都可以进行增加删除操作)
注意一点LinkedList并没有实现RandomAccess所以随机访问是非常慢的。
3.属性
元素个数
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;
transient关键字的作用是保持变量不被序列化具体的百度。
不过我们注意到LinkedList是有Node类,这里的Node是一个内部类:
- item存储的元素
- next指向后置节点
- prev指向前置节点
- 内部类同时包含了一个构造函数
其实LinkedList 集合就是由许多个 Node 对象y一个连着一个组成手构成,可以看下方的图
可以看上面的四个元素,分别就是四个Node对象,可以看到node1所指向的prev上一个节点是空也就是没有上节点,node4所指向的next节点为空也就是没有下一个节点。
4.构造方法
LinkedList共有两个构造方法,一个是创建一个空的构造函数,一个是将已有的Collection添加到LinkedList中。
因为LinkedList不同于ArrayList所以初始化不需要指定大小取分配内存空间。
5.添加元素
LinkedList有几种添加方法我们先从add()开始。
5.1 add方法
checkPositionIndex(index);
可以看到在调用Add方法首先校验一下索引是否合法,如果不合法就会抛出异常。
if (index == size)
linkLast(element);
else
linkBefore(element, node(index));
然后如果索引值等于size的值则直接调用linkLast插入到尾部节点。
否则就linkBefore方法。
linkLast方法:
void linkLast(E e) {
//将l设为尾节点
final Node<E> l = last;
//构造一个新节点,节点上一个节点引用指向尾节点l
final Node<E> newNode = new Node<>(l, e, null);
//将尾节点设为创建的新节点
last = newNode;
//如果尾节点为空,表示原先链表为空
if (l == null)
//将头节点设为新创建的节点(尾节点也是新创建的节点)
first = newNode;
else
//将原来尾节点下一个节点的引用指向新节点
l.next = newNode;
size++;//节点数加1
modCount++;
}
注意一下这里出现了modCount方法,和ArrayList中一样,iterator和listIterator方法返回的迭代器和列表迭代器实现使用。
linkBefore:
void linkBefore(E e, Node<E> succ) {
//将pred设为插入节点的上一个节点
final Node<E> pred = succ.prev;
//将新节点的上引用设为pred,下引用设为succ
final Node<E> newNode = new Node<>(pred, e, succ);
//succ的上一个节点的引用设为新节点
succ.prev = newNode;
//如果插入节点的上一个节点引用为空
if (pred == null)
//新节点就是头节点
first = newNode;
else
//插入节点的下一个节点引用设为新节点
pred.next = newNode;
size++;
//同上
modCount++;
}
5.2 addAll()
在LinkedList中有两个addAll方法一个是 addAll(Collection<? extends E> c)一个是addAll(int index, Collection<? extends E> c),其实
addAll(Collection<? extends E> c)=addAll(int index, Collection<? extends E> c)
可以看下面的截图:
源码如下:
现在开始分析源码:
首先我们可以看到先调用了checkPositionIndex(index);方法来检查索引是否合法。
然后将传进来的Collection转成Object数组
Object[] a = c.toArray();
如果集合为空就直接返回false
if (numNew == 0)
return false;
如果插入的位置等于链表的长度就把新增加的元素放在尾部。
Node<E> pred, succ;
if (index == size) {
succ = null;
pred = last;
} else {
succ = node(index);
pred = succ.prev;
}
最后在循环要插入的元素。这里我们可以看到上面也有modCount。
5.3 addFirst()
addFirst是将元素插入到LinkedList的头部。
如果现在的机构是下图的:
addFirst执行的操作就是:
红色是使用addFirst插入后的位置。一下是源码
调用了linkFirst方法。
上面的操作时:
- 首先执行final Node f = first;将头节点赋值给 f
- final Node newNode = new Node<>(null, e, f);将指定元素构造成一个新节点,此节点的指向下一个节点的引用为头节点
//将新节点设为头节点,那么原先的头节点 f 变为第二个节点
first = newNode;
//如果第二个节点为空,也就是原先链表是空
if (f == null)
//将这个新节点也设为尾节点(前面已经设为头节点了)
last = newNode;
else
f.prev = newNode;//将原先的头节点的上一个节点指向新节点
size++;//节点数加1
modCount++;//和ArrayList中一样记录修改次数
5.4 addLast方法
addLast是将元素插入到尾部如图(黄色元素):
黄色node是新增加。注意add也是把元素添加到尾部。我们来看一下源码:
这里看到addLast和add是调用的同一方法,这里就不在讲解。可以看上方的代码。
由于文章比较长分为两次更新。下一篇文章继续分析
上次分析了LinkedList的结构和添加方法这次开始分析下面的。
注意源码版本为JDK1.8
直接进入正题。
1.删除
1.2remove()
在LinkedList中remove()和removeFirst()是相同的
在LinkedList中的删除其实就是通过修改上一个节点和指向下一个节点的引用完成的,可以看下面的图片:
我们可以看到把node6的prev指向清空然后把node3的next设置成空就完成了删除的操作。
看一下JDK的源码实现:
调用removeFirst,在调用removeFirst中先获取头部节点,如果头节点为空就抛异常。
调用unlinkFirst
private E unlinkFirst(Node<E> f) {
final E element = f.item;
//next 为头结点的下一个节点
final Node<E> next = f.next;
f.item = null;
// 将节点的元素以及引用都设为 null,便于垃圾回收
f.next = null;
//修改头结点为第二个节点
first = next;
//如果第二个节点为空(当前链表只存在第一个元素)
if (next == null)
//那么尾节点也置为 null
last = null;
else
//如果第二个节点不为空,那么将第二个节点的上一个引用置为 null
next.prev = null;
size--;
modCount++;
return element;
}
1.3 removeLast()
我们看一下removeLast,从列表中删除最后一个元素
首先看到先获取了last最后一个元素,如果为空然后就抛异常
然后调用了unlinkLast
看到unlinkLast方法中有一个 help gc ,它的主要作用就是帮助GC来做垃圾回收。
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;
//帮助GC垃圾回收
l.prev = null;
//尾节点为倒数第二个节点
last = prev;
//如果倒数第二个节点为null
if (prev == null)
//那么将节点也置为 null
first = null;
else
prev.next = null;
//如果倒数第二个节点不为空,那么将倒数第二个节点的下一个引用置为 null
size--;
modCount++;
return element;
}
同样执行了modCount++
1.3 remove(int index)
根据索引来删除元素
//删除此列表中指定位置的元素
public E remove(int index) {
//判断索引 index >= 0 && index <= size中时抛出IndexOutOfBoundsException异常
checkElementIndex(index);
return unlink(node(index));
}
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) {//如果删除节点位置的上一个节点引用为null(表示删除第一个元素)
first = next;//将头结点置为第一个元素的下一个节点
} else {//如果删除节点位置的上一个节点引用不为null
prev.next = next;//将删除节点的上一个节点的下一个节点引用指向删除节点的下一个节点(去掉删除节点)
x.prev = null;//删除节点的上一个节点引用置为null
}
if (next == null) {//如果删除节点的下一个节点引用为null(表示删除最后一个节点)
last = prev;//将尾节点置为删除节点的上一个节点
} else {//不是删除尾节点
next.prev = prev;//将删除节点的下一个节点的上一个节点的引用指向删除节点的上一个节点
x.next = null;//将删除节点的下一个节点引用置为null
}
//删除节点内容置为null,便于垃圾回收
x.item = null;
size--;
modCount++;
return element;
}
3.set(int index, E element)
简单粗暴直接贴代码
public E set(int index, E element) {
//检查索引是否合法否则IndexOutOfBoundsException异常
checkElementIndex(index);
//获取指定索引的元素
Node<E> x = node(index);
E oldVal = x.item;
//将指定索引的元素替换成新的元素
x.item = element;
/返回指定索引位置原来的元素
return oldVal;/
}
4.查找元素
很简单
4.1 getFirst()
返回第一个元素
public E getFirst() {
获取头
final Node<E> f = first;
判断是否为空
if (f == null)
throw new NoSuchElementException();
元素返回
return f.item;
}
4.2 getLast()
返回最后一个元素
public E getLast() {
final Node<E> l = last;
if (l == null)
throw new NoSuchElementException();
return l.item;
}
4.3 get
返回指定索引元素
public E get(int index) {
checkElementIndex(index);
return node(index).item;
}
暂时就这么多
原创不易,如果你喜欢本文或者对你有帮助就请转发
LinkedList源码分析:JDK源码分析系列的更多相关文章
- HashSet源码分析:JDK源码系列
1.简介 继续分析源码,上一篇文章把HashMap的分析完毕.本文开始分析HashSet简单的介绍一下. HashSet是一个无重复元素集合,内部使用HashMap实现,所以HashMap的特征耶继承 ...
- JDK源码分析—— ArrayBlockingQueue 和 LinkedBlockingQueue
JDK源码分析—— ArrayBlockingQueue 和 LinkedBlockingQueue 目的:本文通过分析JDK源码来对比ArrayBlockingQueue 和LinkedBlocki ...
- 【jdk源码分析】ArrayList的size()==0和isEmpty()
先看结果 分析源码 [jdk源码解析]jdk8的ArrayList初始化长度为0 java的基本数据类型默认值 无参构造 size()方法 isEmpty()方法
- eclipse查看jdk源码,及反编译查看
jdk中的包: dt.jar是关于运行环境的类库,主要是swing的包 tools.jar是关于一些工具的类库 rt.jar包含了jdk的基础类库,也就是你在java doc里面看到的所有的类的cla ...
- 关于JDK源码:我想聊聊如何更高效地阅读
简介 大家好,我是彤哥,今天我想和大家再聊聊JDK源码的几个问题: 为什么要看JDK源码 JDK源码的阅读顺序 JDK源码的阅读方法 为什么要看JDK源码 一,JDK源码是其它所有源码的基础,看懂了J ...
- 一言不合就开始搞JDK源码
Java是一门面向对象的编程语言,那什么是面向对象呢,下面将是历史上最通俗易懂的解释了,请看下图: 哈哈,解释的够清楚的了吧.闪. 从源码学编程的好处 学Java编程时,最好同时看一些Java的源码 ...
- JDK源码分析(三)—— LinkedList
参考文档 JDK源码分析(4)之 LinkedList 相关
- JDK源码分析(2)LinkedList
JDK版本 LinkedList简介 LinkedList 是一个继承于AbstractSequentialList的双向链表.它也可以被当作堆栈.队列或双端队列进行操作. LinkedList 实现 ...
- JDK源码分析(三)——HashMap 上(基于JDK7)
目录 HashMap概述 内部字段及构造方法 存储元素 扩容 取出元素 删除元素 判断 总结 HashMap概述 前面我们分析了基于数组实现的ArrayList和基于双向链表实现的LinkedLi ...
随机推荐
- WebAPI Delete方法报错405 Method Not Allowed
.net framework 在Web.config文件中添加如下配置: <system.webServer> <modules runAllManagedModulesForAll ...
- 基于 CSP 的设计思想和 OOP 设计思想的异同
LinkerLin Go语言推崇的CSP编程模型和设计思想,并没有引起很多Go开发者包括Go标准库作者的重视.标准库的很多设计保留了很浓的OOP的味道.本篇Blog想比较下从设计的角度看,CSP和OO ...
- WPF 4 目录树型显示
原文:WPF 4 目录树型显示 本篇将通过WPF4 制作简单的目录树型结构显示实例,完成本篇内容我们将作出下图所示的应用程序. 从图中我们可以看到程序主要分为两部分:左边显示本地驱 ...
- github网页
GitHub主页 创建仓库 想必大家都有自己的Github账号吧,没有的可以到GitHub官网注册账号,注册完后,我们来下一步,在我们的GitHub上面右上角的New repository来创建一个仓 ...
- Linux下获取arm的交叉编译工具链
转载请注明文章:Linux下获取arm的交叉编译工具链 出处:多客博图 这里介绍,Linux下获取arm的交叉编译工具链,比如arm-linux-gnueabihf-gcc.arm-linux-gne ...
- win32 htmlayout dom操作demo
之前两篇关于win32 htmlayout博文,记载了一个实现了简单的点击按钮弹出新窗口的demo,之后实践中发现,图形界面开发,最重要的还是要实现响应用户操作,改变原有界面的功能.比如说,界面上有一 ...
- 【备忘】WPF基础
XAML 为了避免生成用户界面(GUI)的代码和基于用户操作执行的代码混合在一起. 名称空间 值得注意的名称空间: xmlns="http://schemas.microsoft.com/w ...
- 毕设(一)C#的百度api调用
这个学期就要毕业了,选了一个无人机地面站软件设计的题目,这几天也开始着手做, 首先做了一个百度地图的调用,这里因为是上位机的开发,所有就不介绍Javascript的 调用方法,核心是用到一个类Http ...
- 声谱预测网络(Tacotron2)
整个特征预测网络是一个带有注意力机制(attention)的seq2seq网络. 编码器-解码器(Encoder-Decoder)结构 在原始的编码器-解码器结构中,编码器(encoder)输入一个序 ...
- 使用QPainter的drawPixmap()绘制多幅图片 good
众所周知,使用QLabel的setPixmap()就可以将图片显示出来,做视屏解码后显示也可以如此.但是为何我今天还要费力使用基函数drawPixmap()来做绘图?理由有这么些吧: 1.使用QLab ...