LinkedList原理及源码解析
简介
LinkedList是一个双向线性链表,但是并不会按线性的顺序存储数据,而是在每一个节点里存到下一个节点的指针(Pointer)。由于不必须按顺序存储,链表在插入的时候可以达到O(1)的复杂度,比另一种线性表顺序表快得多,但是查找一个节点或者访问特定编号的节点则需要O(n)的时间,而顺序表相应的时间复杂度分别是O(logn)和O(1)。
UML关系图
使用示例
LinkedList list = new LinkedList<>();
//新增
list.add("a");
list.add("a");
list.add("b");
System.out.println(list);
//删除
list.remove("a");//删除第一个对应对象
list.remove(0);//删除下标为0的元素
list.removeFirst();//删除第一个元素
list.removeLast();//删除最后一个元素
System.out.println(list);
//修改
list.set(0,"c");//修改下标为0的元素
System.out.println(list);
//插入
list.add(1,"a");//只能在已有元素的前后或中间位置插入
System.out.println(list);
//获取
list.get(0);//根据下标获取元素
list.getFirst();//获取第一个元素
list.getLast();//获取最后一个元素
//循环
//普通循环
for(int i=0;i<list.size();i++){
System.out.println(list.get(i));
}
//foreach循环
for(Object o:list){
System.out.println(o);
}
//迭代循环
Iterator iterator = list.iterator();
while (iterator.hasNext()){
System.out.println(iterator.next());
}
源码解析
初始化
节点
//链表的核心类-节点
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;
}
}
构造方法
//默认大小
transient int size = 0;
//第一个节点
transient Node<E> first;
//最后一个节点
transient Node<E> last;
//无参构造
public LinkedList() {
}
//带有初始化集合构造
public LinkedList(Collection<? extends E> c) {
this();
addAll(c);
}
addAll的分析
public boolean addAll(int index, Collection<? extends E> c) {
//检测是否越界
//初始化是不会发生越界,主要是用于初始化后批量增加集合时候同时修改集合则会发生越界异常
checkPositionIndex(index);
Object[] a = c.toArray();
//判断集合是否为空
int numNew = a.length;
if (numNew == 0)
return false;
//
Node<E> pred, succ;
//如果index==size,说明链表只有一个节点
if (index == size) {
succ = null;
pred = last;
} else {
succ = node(index);
pred = succ.prev;
}
//将集合进行循环添加到链表
for (Object o : a) {
@SuppressWarnings("unchecked") E e = (E) o;
Node<E> newNode = new Node<>(pred, e, null);
if (pred == null)
first = newNode;
else
pred.next = newNode;
pred = newNode;
}
//当一个节点的时候last和当前的节点相同
if (succ == null) {
last = pred;
} else {
pred.next = succ;
succ.prev = pred;
}
//更新链表的长度
size += numNew;
modCount++;
return true;
}
常用函数分析
增加
//增加元素,默认向链表的结尾增加节点
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);
//链表尾部指向最新节点
last = newNode;
//如果原来尾部节点为空,说明为空链表需要设置链表头部节点
//否则将原来链表尾部节点的next指向新的节点
if (l == null)
first = newNode;
else
l.next = newNode;
//增加大小
size++;
//记录修改次数,主要用于集合迭代,保证迭代数据的准确性
//在迭代时候如果集合发生变化则会抛出异常
//throw new ConcurrentModificationException();
modCount++;
}
删除
//根据下标进行删除节点
public E remove(int index) {
//检测下标是否越界
checkElementIndex(index);
return unlink(node(index));
}
//获取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 removeFirst() {
final Node<E> f = first;
if (f == null)
throw new NoSuchElementException();
return unlinkFirst(f);
}
//断开第一个节点
private E unlinkFirst(Node<E> f) {
final E element = f.item;
final Node<E> next = f.next;
f.item = null;
f.next = null; // help GC
//将当前节点的下个节点设为第一个节点
first = next;
if (next == null)
last = null;
else
next.prev = null;
size--;
modCount++;
return element;
}
//删除最后一个节点
public E removeLast() {
final Node<E> l = last;
if (l == null)
throw new NoSuchElementException();
return unlinkLast(l);
}
//断开最后一个节点
private E unlinkLast(Node<E> l) {
final E element = l.item;
final Node<E> prev = l.prev;
l.item = null;
l.prev = null; // help GC
//将最后一个节点设为当前节点的上一个节点
last = prev;
if (prev == null)
first = null;
else
prev.next = null;
size--;
modCount++;
return element;
}
修改(替换)元素
//修改某个元素
public E set(int index, E element) {
//检测下标是否越界
checkElementIndex(index);
//根据下标获取节点
Node<E> x = node(index);
//获取节点的元素
E oldVal = x.item;
//赋值新的元素
x.item = element;
return oldVal;
}
插入元素
//插入指定位置元素
public void add(int index, E element) {
//检测下标是否越界
checkPositionIndex(index);
//判断下标是否为最后一个位置,如果是直接插入到最后一个节点
//否则查询出当前index的节点,将新的节点放到原来index节点之前
if (index == size)
linkLast(element);
else
linkBefore(element, node(index));
}
//插入index节点之前位置
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++;
}
获取元素
//根据下标获取元素
public E get(int index) {
//检测下标是否越界
checkElementIndex(index);
//根据下标获取节点,然后返回节点的元素
return node(index).item;
}
//获取第一个元素
public E getFirst() {
final Node<E> f = first;
if (f == null)
throw new NoSuchElementException();
return f.item;
}
//获取最后一个元素
public E getLast() {
final Node<E> l = last;
if (l == null)
throw new NoSuchElementException();
return l.item;
}
清除所有数据
public void clear() {
// Clearing all of the links between nodes is "unnecessary", but:
// - helps a generational GC if the discarded nodes inhabit
// more than one generation
// - is sure to free memory even if there is a reachable Iterator
//将所有数据进行逐个赋值为null,帮助gc的垃圾回收
for (Node<E> x = first; x != null; ) {
Node<E> next = x.next;
x.item = null;
x.next = null;
x.prev = null;
x = next;
}
first = last = null;
size = 0;
//记录清除的操作,保证迭代的准确性
modCount++;
}
总结
- 优点
1、插入和移除数据效率高 2、链表形式方便做为栈或队列场景使用 3、相对arraylist来说,linkedlist无需扩容 - 缺点
1、根据随机获取元素效率低(根据下标获取)
2、批量添加集合效率低
其他
在arraylist和linkedlist中的全局标量都出现了一个修饰符transient,此修饰符的属性默认是不能够被序列化的,jdk作者为了减少空间的浪费所以实现了writeObject(ObjectOutputStream)和readObject(ObjectInputStream)这两个方法,进行手动的序列化有存储数据的有用属性。
参考
更多请关注微信………
LinkedList原理及源码解析的更多相关文章
- Java 集合系列 04 LinkedList详细介绍(源码解析)和使用示例
java 集合系列目录: Java 集合系列 01 总体框架 Java 集合系列 02 Collection架构 Java 集合系列 03 ArrayList详细介绍(源码解析)和使用示例 Java ...
- 机器学习实战(Machine Learning in Action)学习笔记————03.决策树原理、源码解析及测试
机器学习实战(Machine Learning in Action)学习笔记————03.决策树原理.源码解析及测试 关键字:决策树.python.源码解析.测试作者:米仓山下时间:2018-10-2 ...
- Spring-Session实现Session共享实现原理以及源码解析
知其然,还要知其所以然 ! 本篇介绍Spring-Session的整个实现的原理.以及对核心的源码进行简单的介绍! 实现原理介绍 实现原理这里简单说明描述: 就是当Web服务器接收到http请求后,当 ...
- Spring MVC工作原理及源码解析(三) HandlerMapping和HandlerAdapter实现原理及源码解析
1.HandlerMapping实现原理及源码解析 在前面讲解Spring MVC工作流程的时候我们说过,前端控制器收到请求后会调⽤处理器映射器(HandlerMapping),处理器映射器根据请求U ...
- Java 集合系列05之 LinkedList详细介绍(源码解析)和使用示例
概要 前面,我们已经学习了ArrayList,并了解了fail-fast机制.这一章我们接着学习List的实现类——LinkedList.和学习ArrayList一样,接下来呢,我们先对Linked ...
- 【转】Java 集合系列05之 LinkedList详细介绍(源码解析)和使用示例
概要 前面,我们已经学习了ArrayList,并了解了fail-fast机制.这一章我们接着学习List的实现类——LinkedList.和学习ArrayList一样,接下来呢,我们先对Linked ...
- 有关LinkedList常用方法的源码解析
上文里解析了有关ArrayList中的几个常用方法的源码——<有关ArrayList常用方法的源码解析>,本文将对LinkedList的常用方法做简要解析. LinkedList是基于链表 ...
- Redux异步解决方案之Redux-Thunk原理及源码解析
前段时间,我们写了一篇Redux源码分析的文章,也分析了跟React连接的库React-Redux的源码实现.但是在Redux的生态中还有一个很重要的部分没有涉及到,那就是Redux的异步解决方案.本 ...
- ORB原理与源码解析
转载: http://blog.csdn.net/luoshixian099/article/details/48523267 CSDN-勿在浮沙筑高台 没有时间重新复制代码,只能一股脑的复制,所以代 ...
随机推荐
- 浏览器播放rtsp流媒体解决方案
老板提了一个需求,想让网页上播放景区监控的画面,估计是想让游客达到未临其地,已知其境的状态吧. 说这个之前,还是先说一下什么是rtsp协议吧. RTSP(Real Time Streaming ...
- 高性能mysql(二)——mysql的存储引擎
在文件系统中,mysql将每个数据库保存为数据目录下的一个子目录.创建表时,mysql会在子目录下创建一个和表同名的.frm文件保存表的定义.例如创建一个名为mytable的表,mysql会在myta ...
- web聊天室总结
前言: 最近在写一个聊天室的项目,前端写了挺多的JS(function),导致有点懵比,出了BUG,也迟迟找不到.所以昨天把写过的代码总结了一下,写成博客. 项目背景 参考博客: http://www ...
- 1.Node.js 接入微信公众平台开发
一.写在前面的话 Node.js是一个开放源代码.跨平台的JavaScript语言运行环境,采用Google开发的V8运行代码,使用事件驱动.非阻塞和异步输入输出模型等技术来提高性能,可优化应用程 ...
- ProxySQL的相关维护说明
背景: 前面的2篇文章MySQL ProxySQL读写分离使用初探和MySQL ProxySQL读写分离实践大致介绍了ProxySQL的使用说明,从文章的测试的例子中看到ProxySQL使用SQLIT ...
- 浅谈MVC数据验证
一.一般情况 对于使用过MVC框架的人来说,对MVC的数据验证不会陌生,比如,我有一个Model如下: public class UserInfo { [Required(ErrorMessage = ...
- Autotest添加测试用例小结
Autotest本身是一个自动化测试框架,可以在上面添加各种测试工具用于系统测试.前几天我在上面添加了几个基于龙芯桌面5.0系统的性能测试工具.现在做以下总结,大体写以下添加的过程. 以unixben ...
- Java之枚举----小试牛刀练习
1.定义一个电脑品牌枚举类,其中只有固定的几个电脑品牌. 1.1简单枚举类,不设置属性和方法 package 第十四章枚举; public enum Brand { Lenovo,Dell,Accer ...
- JAVA设计模式初探之装饰者模式
定义:动态给一个对象添加一些额外的职责,就象在墙上刷油漆.使用Decorator模式相比用生成子类方式达到功能的扩充显得更为灵活.设计初衷:通常可以使用继承来实现功能的拓展,如果这些需要拓展的功能的种 ...
- java基础(六章)
一.for循环的使用场合 l while循环--先判断,再循环 while(1.条件表达式){ //2.循环操作 //3.更改循环条件表达式 } l do-while--先循环 ...