Java集合系列[2]----LinkedList源码分析
上篇我们分析了ArrayList的底层实现,知道了ArrayList底层是基于数组实现的,因此具有查找修改快而插入删除慢的特点。本篇介绍的LinkedList是List接口的另一种实现,它的底层是基于双向链表实现的,因此它具有插入删除快而查找修改慢的特点,此外,通过对双向链表的操作还可以实现队列和栈的功能。LinkedList的底层结构如下图所示。

F表示头结点引用,L表示尾结点引用,链表的每个结点都有三个元素,分别是前继结点引用(P),结点元素的值(E),后继结点的引用(N)。结点由内部类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;
}
}
Node这个内部类其实很简单,只有三个成员变量和一个构造器,item表示结点的值,next为下一个结点的引用,prev为上一个结点的引用,通过构造器传入这三个值。接下来再看看LinkedList的成员变量和构造器。
//集合元素个数
transient int size = 0; //头结点引用
transient Node<E> first; //尾节点引用
transient Node<E> last; //无参构造器
public LinkedList() {} //传入外部集合的构造器
public LinkedList(Collection<? extends E> c) {
this();
addAll(c);
}
LinkedList持有头结点的引用和尾结点的引用,它有两个构造器,一个是无参构造器,一个是传入外部集合的构造器。与ArrayList不同的是LinkedList没有指定初始大小的构造器。看看它的增删改查方法。
//增(添加)
public boolean add(E e) {
//在链表尾部添加
linkLast(e);
return true;
} //增(插入)
public void add(int index, E element) {
checkPositionIndex(index);
if (index == size) {
//在链表尾部添加
linkLast(element);
} else {
//在链表中部插入
linkBefore(element, node(index));
}
} //删(给定下标)
public E remove(int index) {
//检查下标是否合法
checkElementIndex(index);
return unlink(node(index));
} //删(给定元素)
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;
} //改
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 get(int index) {
//检查下标是否合法
checkElementIndex(index);
//返回指定下标的结点的值
return node(index).item;
}
LinkedList的添加元素的方法主要是调用linkLast和linkBefore两个方法,linkLast方法是在链表后面链接一个元素,linkBefore方法是在链表中间插入一个元素。LinkedList的删除方法通过调用unlink方法将某个元素从链表中移除。下面我们看看链表的插入和删除操作的核心代码。
//链接到指定结点之前
void linkBefore(E e, Node<E> succ) {
//获取给定结点的上一个结点引用
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++;
} //卸载指定结点
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) {
//将头结点引用指向给定结点的下一个结点
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;
}


通过上面图示看到对链表的插入和删除操作的时间复杂度都是O(1),而对链表的查找和修改操作都需要遍历链表进行元素的定位,这两个操作都是调用的node(int index)方法定位元素,看看它是怎样通过下标来定位元素的。
//根据指定位置获取结点
Node<E> node(int index) {
//如果下标在链表前半部分, 就从头开始查起
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;
}
}
//获取头结点
public E peek() {
final Node<E> f = first;
return (f == null) ? null : f.item;
} //获取头结点
public E element() {
return getFirst();
} //弹出头结点
public E poll() {
final Node<E> f = first;
return (f == null) ? null : unlinkFirst(f);
} //移除头结点
public E remove() {
return removeFirst();
} //在队列尾部添加结点
public boolean offer(E e) {
return add(e);
}
双向队列操作:
//在头部添加
public boolean offerFirst(E e) {
addFirst(e);
return true;
} //在尾部添加
public boolean offerLast(E e) {
addLast(e);
return true;
} //获取头结点
public E peekFirst() {
final Node<E> f = first;
return (f == null) ? null : f.item;
} //获取尾结点
public E peekLast() {
final Node<E> l = last;
return (l == null) ? null : l.item;
}
栈操作:
//入栈
public void push(E e) {
addFirst(e);
} //出栈
public E pop() {
return removeFirst();
}
Java集合系列[2]----LinkedList源码分析的更多相关文章
- java集合系列之LinkedList源码分析
java集合系列之LinkedList源码分析 LinkedList数据结构简介 LinkedList底层是通过双端双向链表实现的,其基本数据结构如下,每一个节点类为Node对象,每个Node节点包含 ...
- Java集合系列[4]----LinkedHashMap源码分析
这篇文章我们开始分析LinkedHashMap的源码,LinkedHashMap继承了HashMap,也就是说LinkedHashMap是在HashMap的基础上扩展而来的,因此在看LinkedHas ...
- java集合系列之ArrayList源码分析
java集合系列之ArrayList源码分析(基于jdk1.8) ArrayList简介 ArrayList时List接口的一个非常重要的实现子类,它的底层是通过动态数组实现的,因此它具备查询速度快, ...
- Java集合系列:-----------03ArrayList源码分析
上一章,我们学习了Collection的架构.这一章开始,我们对Collection的具体实现类进行讲解:首先,讲解List,而List中ArrayList又最为常用.因此,本章我们讲解ArrayLi ...
- Java集合系列[3]----HashMap源码分析
前面我们已经分析了ArrayList和LinkedList这两个集合,我们知道ArrayList是基于数组实现的,LinkedList是基于链表实现的.它们各自有自己的优劣势,例如ArrayList在 ...
- Java集合系列[1]----ArrayList源码分析
本篇分析ArrayList的源码,在分析之前先跟大家谈一谈数组.数组可能是我们最早接触到的数据结构之一,它是在内存中划分出一块连续的地址空间用来进行元素的存储,由于它直接操作内存,所以数组的性能要比集 ...
- java多线程系列(九)---ArrayBlockingQueue源码分析
java多线程系列(九)---ArrayBlockingQueue源码分析 目录 认识cpu.核心与线程 java多线程系列(一)之java多线程技能 java多线程系列(二)之对象变量的并发访问 j ...
- Java并发系列[2]----AbstractQueuedSynchronizer源码分析之独占模式
在上一篇<Java并发系列[1]----AbstractQueuedSynchronizer源码分析之概要分析>中我们介绍了AbstractQueuedSynchronizer基本的一些概 ...
- Java并发系列[3]----AbstractQueuedSynchronizer源码分析之共享模式
通过上一篇的分析,我们知道了独占模式获取锁有三种方式,分别是不响应线程中断获取,响应线程中断获取,设置超时时间获取.在共享模式下获取锁的方式也是这三种,而且基本上都是大同小异,我们搞清楚了一种就能很快 ...
随机推荐
- html5中将图片的绝对路径转换成文件对象
html5中将图片的绝对路径转换成文件对象 将图片的绝对路径转换成base64编码,请看这篇文章 我们先来理解基本知识点: 1. 理解HTML5中的FileList对象与file对象. 在HTML5中 ...
- JMeter3.0脚本中文乱码解决方法
修改apache-jmeter-3.0\bin\jmeter.properties文件,编辑jsyntaxtextarea.font.family=宋体.
- spring mvc报错,数据库查询无限死循环
进行查询的陷入了无限死循环,原因是问题类中包含了回答,回答类中包含了问题,进入了无限死循环 解决方法:在回答类中的问题类属性上加注解:@JsonBackReference 问题中有回答的set集合,回 ...
- iOS 类似朋友圈的图片浏览器SDPhotoBrowser
SDPhotoBrowser.Demo 1.在文件SDBrowserImageView.m中有用SDWebImage到网络加载图片 需要的注释去掉即可 #import "ViewContro ...
- iOS 设置视图背景的透明度
p.p1 { margin: 0.0px 0.0px 0.0px 0.0px; font: 11.0px Menlo; color: #00afca } span.s1 { color: #fffff ...
- iOS APP内购
看到网上文章一大把,看了这个觉得挺不错的,谢谢 iOS大全 公众平台; 原文:http://mp.weixin.qq.com/s?__biz=MzAxMzE2Mjc2Ng==&mid=2652 ...
- 【NOIP模拟】cut
Description F大爷热爱切树.今天他找到一棵黑白树,不到两秒钟,F大爷就把这棵树切掉了.已知原先树上共n个点,每个点都是黑点或者白点,F大爷切去若干条边后,分成的若干个连通子树中每块恰有一个 ...
- NIO中的易筋经
匠心零度 转载请注明原创出处,谢谢! 前言 <易筋经>.天下武功出少林,而易筋经是少林寺的镇寺之宝.学好了易筋经就可以轻易地学好其它武功,只不过很少人学到了它的全部精髓.游坦之只是碰巧学了 ...
- 微信公众号H5支付遇到的那些坑
简史 官方文档说的很清楚,商户已有H5商城网站,用户通过消息或扫描二维码在微信内打开网页时,可以调用微信支付完成下单购买的流程. 当然,最近微信支付平台也加入了纯H5支付,也就是说用户可以在微信以外的 ...
- 视觉SLAM的方案总结
MoNoSLAM:https://github.com/hanmekim/SceneLib2 以扩展卡尔曼滤波为后端,追踪前端非常稀疏的特征点,以相机的当前状态和所有路标点为状态量,更新其均值和协方差 ...