JDK1.8源码(二)——java.util.LinkedList
LinkedList定义
LinkedList 是链表实现的线性表(双链表),元素有序且可以重复。
public class LinkedList<E>
extends AbstractSequentialList<E>
implements List<E>, Deque<E>, Cloneable, java.io.Serializable

蓝色实线箭头是指Class继承关系
绿色实线箭头是指interface继承关系
绿色虚线箭头是指接口实现关系
由上可知LinkedList继承AbstractSequentialList并且实现了List和Deque,Cloneable, Serializable接口。
①、实现 List 接口
List 接口定义了实现该接口的类都必须要实现的一组方法,如下所示,下面我们会对这一系列方法的实现做详细介绍。

字段属性
//链表元素(节点)的个数
transient int size = 0;
//第一个元素的指针
transient Node<E> first;
//最后一个元素的指针
transient Node<E> last;
注意这里出现了一个 Node 类,这是 LinkedList 类中的一个内部类,集合里每一个元素就代表一个 Node 类对象,LinkedList 集合就是由许多个 Node 对象类似于手拉着手构成,由此可知,LinkedList是一个双向链表。
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;
}
}

如图所示:每个节点都prev都保存上一个节点的引用,next保存下一个节点的引用;

需要注意:第一个节点prev没有指向的节点,为null,最后一个节点next也没有指向的节点,也为null
构造函数
①、无参构造函数
public LinkedList() {
}
注意:这里不需要初始化链表的大小,不像ArrayList,需要初始化数组的大小才能添加元素
②、泛型参数有参构造函数
public LinkedList(Collection<? extends E> c) {
this();
addAll(c);
}
将已有元素的集合Collection 的实例添加到 LinkedList 中,下面详细介绍
添加元素
①、add(E e)
public boolean add(E e) {
//将元素添加到链表的尾部
linkLast(e);
return true;
}
void linkLast(E e) {
final Node<E> l = last;//先用变量存储链表尾部节点
final Node<E> newNode = new Node<>(l, e, null);//新添加一个元素,需要新增一个节点,元素内容为e,prev为当前last节点的地址引用,next为null
last = newNode;//将链表尾部指向新节点
if (l == null)
first = newNode;//如果链表为空,将这个新节点也设为头部节点,那么新增节点既是头节点也是尾节点,并且头节点的prev和next都是null
else
l.next = newNode;//链表不为空,将之前存储的原链表尾部节点的next指向新增节点
size++;//节点数加1
modCount++;//和ArrayList中一样,防止集合遍历时做元素的添加
}
此时需要注意,如果链表为空时,第一个元素的指针和最后一个元素的指针都指向当前节点
②、addFirst(E e)
public void addFirst(E e) {
linkFirst(e);
}
private void linkFirst(E e) {
final Node<E> f = first;//先用变量 f 存储链表头部节点
final Node<E> newNode = new Node<>(null, e, f);//将指定元素构造成一个新节点,元素内容为e,prev为null,next为当前first节点的地址引用,
first = newNode;//将链表头部指向新节点
if (f == null)
last = newNode;//如果链表为空,将这个新节点也设为尾节点,那么新增节点既是头节点也是尾节点
else
f.prev = newNode;//如果链表不为空,讲原链表的头部节点 f 的上一个节点指向新节点
size++;//节点数加1
modCount++;//和ArrayList中一样,防止集合遍历时做元素的添加
}
③、addLast(E e)
public void addLast(E e) {
linkLast(e);
}
和add(E e) 实现相同,在链表尾部添加元素
④、public void add(int index, E element)
将元素插入指定位置
public void add(int index, E element) {
//判断索引是否越界 return index >= 0 && index <= size;
checkPositionIndex(index);
if (index == size)//如果索引值等于链表大小
linkLast(element);//将节点直接插入链表尾部;上面已经分析过
else
linkBefore(element, node(index));
}
//根据索引获取节点,因为是链表,不像数组,在内存中并不是一块连续的位置存储,不能根据索引直接取到值,需要从头部或者尾部一个个向下找
Node<E> node(int index) {
//size >> 1表示移位,指除以2的1次方
if (index < (size >> 1)) {//如果索引比链表的一半小
Node<E> x = first;//设x为头节点,表示从头节点开始遍历
for (int i = 0; i < index; i++)//因为只需要找到index处,所以遍历到index处就可以停止
x = x.next;//从第一个节点开始向后移动,直到移动到index前一个节点就能找到index处的节点
return x;
} else {//如果索引比链表的一半大
Node<E> x = last;//设x为尾部节点,表示从最后一格节点开始遍历
for (int i = size - 1; i > index; i--)
x = x.prev;//从最后一个节点开始向前移动,直到移动到index后一个节点就能找到index处的节点
return x;
}
}
void linkBefore(E e, Node<E> succ) {
final Node<E> pred = succ.prev;//获取index 节点的上一个节点
final Node<E> newNode = new Node<>(pred, e, succ);//构造新插入的节点,新节点pred设为原链表index的上一个节点,next处设为原始链表的index处的节点
succ.prev = newNode;//原始index处的节点的上一个几点引用设为新节点
if (pred == null)//如果插入节点的上一个节点引用为空
first = newNode;//头部节点设为新节点
else
pred.next = newNode;//原始index的上一个节点的next 设为新节点
size++;
modCount++;
}
我们发现LinkedList 每次添加元素只是改变元素的上一个指针引用和下一个指针引用,而且没有扩容。对比于 ArrayList ,需要扩容,而且在中间插入元素时,后面的所有元素都要移动一位,两者插入元素时的效率差异很大
查找元素
①、get(int index)
public E get(int index) {
//判断索引是否越界 return index >= 0 && index <= size;
checkElementIndex(index);
//node(index)上面已经讲过,这里获取节点的实际元素
return node(index).item;
}
②、indexOf(Object o)
返回链表中第一个出现指定元素的索引
public int indexOf(Object o) {
int index = 0;
if (o == null) {//查找的元素为null
for (Node<E> x = first; x != null; x = x.next) {//从头结点开始不断向下一个节点进行遍历
if (x.item == null)
return index;//找到了则返回index
index++;
}
} else {//查找的元素不为null
for (Node<E> x = first; x != null; x = x.next) {
if (o.equals(x.item))//使用equals 比较
return index;//找到了则返回index
index++;
}
}
return -1;//找不到指定元素,则返回-1
}
修改元素
public E set(int index, E element) {
checkElementIndex(index);
Node<E> x = node(index);//获取指定索引处的节点
E oldVal = x.item;//获取指定索引处节点的实际元素
x.item = element;//将指定位置的节点元素替换成需要修改的元素
return oldVal;//返回索引处原来的节点的元素值
}
删除元素
①、通过索引位置删除
public E remove(int index) {
checkElementIndex(index);
//先获取指定位置的节点
return unlink(node(index));
}
E unlink(Node<E> x) {
final E element = x.item;
final Node<E> next = x.next;
final Node<E> prev = x.prev;
if (prev == null) {//如果删除节点的上一个节点引用为null,表示删除节点为头节点
first = next;//将头节点设置为删除节点的下一个节点
} else {
prev.next = next;//将删除节点的上一个节点的next指向删除节点的下一个节点
x.prev = null;//将删除节点的上一个节点引用设为null,否则链表就乱了
}
if (next == null) {//如果删除节点的下一个节点引用为null,表示删除节点为尾部节点
last = prev;//将尾部节点设为删除节点的上一个节点
} else {//不是尾部节点
next.prev = prev;//将删除节点的下一个节点的prev指向删除节点的上一个节点
x.next = null;//将删除节点的下一个节点引用设为null
}
x.item = null;//将删除节点的实际元素设为null,便于垃圾回收
size--;
modCount++;
return element;
}
与ArrayList比较而言,LinkedList的删除动作不需要“移动”很多数据,从而效率更高
总结
ArrayList和LinkedList在性能上各有优缺点:
1.对ArrayList和LinkedList而言,在列表末尾增加一个元素所花的开销都是固定的。对ArrayList而言,主要是在内部数组中增加一项,偶尔可能会导致对数组扩容;而对LinkedList而言,这个开销是统一的,都是新建一个Node对象节点。
2.在ArrayList的中间插入或删除一个元素意味着这个列表中剩余的元素都会被移动;而在LinkedList的中间插入或删除一个元素的开销是固定的,只需要改变元素邻近节点的上下引用地址。
3.LinkedList不支持高效的随机元素访问,因为需要从第一个元素或者最后一个元素开始遍历查找;ArrayList可以直接根据索引取到对应位置的元素。
4.ArrayList的空间浪费主要体现在在list列表的结尾预留一定的容量空间,而LinkedList的空间花费则体现在它的每一个元素都需要消耗相当的空间,每个元素不仅保存了当前元素的实际内容,还有上一个节点和下一个节点的引用
所以在我们进行对元素的增删查操作的时候,进行查操作时用ArrayList,进行增删操作的时候最好用LinkedList。
JDK1.8源码(二)——java.util.LinkedList的更多相关文章
- JDK1.8源码(六)——java.util.LinkedList 类
上一篇博客我们介绍了List集合的一种典型实现 ArrayList,我们知道 ArrayList 是由数组构成的,本篇博客我们介绍 List 集合的另一种典型实现 LinkedList,这是一个有链表 ...
- JDK1.8源码(五)——java.util.Vector类
JDK1.8源码(五)--java.lang. https://www.cnblogs.com/IT-CPC/p/10897559.html
- JDK1.8源码(七)——java.util.HashMap 类
本篇博客我们来介绍在 JDK1.8 中 HashMap 的源码实现,这也是最常用的一个集合.但是在介绍 HashMap 之前,我们先介绍什么是 Hash表. 1.哈希表 Hash表也称为散列表,也有直 ...
- JDK1.8源码(四)——java.util.Arrays类
一.概述 1.介绍 Arrays 类是 JDK1.2 提供的一个工具类,提供处理数组的各种方法,基本上都是静态方法,能直接通过类名Arrays调用. 二.类源码 1.asList()方法 将一个泛型数 ...
- JDK1.8源码(九)——java.util.LinkedHashMap 类
前面我们介绍了 Map 集合的一种典型实现 HashMap ,关于 HashMap 的特性,我们再来复习一遍: ①.基于JDK1.8的HashMap是由数组+链表+红黑树组成,相对于早期版本的 JDK ...
- JDK1.8源码(二)——java.lang.Integer类
一.初识 1.介绍 int 是Java八大基本数据类型之一,占据 4 个字节,范围是 -2^31~2^31 - 1,即 -2147483648~2147483647.而 Integer 是 int 包 ...
- JDK1.8源码(二)——java.lang.Integer 类
上一篇博客我们介绍了 java.lang 包下的 Object 类,那么本篇博客接着介绍该包下的另一个类 Integer.在前面 浅谈 Integer 类 博客中我们主要介绍了 Integer 类 和 ...
- JDK1.8源码(四)——java.util.Arrays 类
java.util.Arrays 类是 JDK 提供的一个工具类,用来处理数组的各种方法,而且每个方法基本上都是静态方法,能直接通过类名Arrays调用. 1.asList public static ...
- JDK1.8源码(五)——java.util.ArrayList 类
关于 JDK 的集合类的整体介绍可以看这张图,本篇博客我们不系统的介绍整个集合的构造,重点是介绍 ArrayList 类是如何实现的. 1.ArrayList 定义 ArrayList 是一个用数组实 ...
随机推荐
- pycharm 记录
一.pycharm字体放大.缩小的设置 File —> setting —> Keymap —>在搜寻框中输入:increase —> Increase Font Size(双 ...
- 彻底解决eclipse中tomcat启动速度缓慢的问题
问题: Tomcat启动提示At least one JAR was scanned for TLDs yet contained no TLDs tomcat启动速度总是很慢,检查后发现tomcat ...
- 六、OpenStack—neutron组件介绍与安装
一.neutron介绍 Neutron 概述:传统的网络管理方式很大程度上依赖于管理员手工配置和维护各种网络硬件设备:而云环境下的网络已经变得非常复杂,特别是在多租户场景里,用户随时都可能需要创建.修 ...
- mysql 8.0 密码加密方式的坑
问题:新安装好MySQL 8.0和Navicat之后,连接时总是报: 1251 Client does not support authentication protocol requested by ...
- ORM之SQLALchemy
今天来聊一聊 Python 的 ORM 框架 SQLAlchemy SQLAlchemy 没有 Django 的 Models 好用!因为models是Django自带的ORM框架,也正是因为是Dja ...
- 输出九九乘法表(Python、Java、C、C++、JavaScript)
最近在学python的过程中,接触到了循环的知识,以及这个案例,于是写了下!感觉还不错,然后就用其它几种语言也试了下!! 接下来,就跟大家分享下实现方法!! 实现输出九九乘法表,主要用到的是循环的知识 ...
- [HEOI2016/TJOI2016]游戏
Description: 在2016年,佳缘姐姐喜欢上了一款游戏,叫做泡泡堂.简单的说,这个游戏就是在一张地图上放上若干个炸弹,看是否能炸到对手,或者躲开对手的炸弹.在玩游戏的过程中,小H想到了这样一 ...
- GitLab在centos7上安装和使用
git的优点 git是分布式的,svn不是 git分布式本地就可以用,可以随便保存各种历史痕迹,不用担心污染服务器,连不上服务器也能提交代码.查看log. GIT分支和SVN的分支不同 分支在SVN中 ...
- 移动端 rem适配方法
rem适配 一, 网易适配方法 屏幕宽度/设计稿rem宽度=页面动态font-size值(如:375/7.5=50) document.documentElement. ...
- 可道云kodexplorer网盘未清理造成linux服务器爆满的解决方法
今天登陆宝塔面板的时候发现硬盘占用37GB,已经变红提示我空间不足了,惊呆了, 还以为是宝塔抽风了,去远程连接服务器看了一下,懵逼了. df -h 查看挂载目录使用情况 还是不相信现实的我又重启了一下 ...