简介

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原理及源码解析的更多相关文章

  1. Java 集合系列 04 LinkedList详细介绍(源码解析)和使用示例

    java 集合系列目录: Java 集合系列 01 总体框架 Java 集合系列 02 Collection架构 Java 集合系列 03 ArrayList详细介绍(源码解析)和使用示例 Java ...

  2. 机器学习实战(Machine Learning in Action)学习笔记————03.决策树原理、源码解析及测试

    机器学习实战(Machine Learning in Action)学习笔记————03.决策树原理.源码解析及测试 关键字:决策树.python.源码解析.测试作者:米仓山下时间:2018-10-2 ...

  3. Spring-Session实现Session共享实现原理以及源码解析

    知其然,还要知其所以然 ! 本篇介绍Spring-Session的整个实现的原理.以及对核心的源码进行简单的介绍! 实现原理介绍 实现原理这里简单说明描述: 就是当Web服务器接收到http请求后,当 ...

  4. Spring MVC工作原理及源码解析(三) HandlerMapping和HandlerAdapter实现原理及源码解析

    1.HandlerMapping实现原理及源码解析 在前面讲解Spring MVC工作流程的时候我们说过,前端控制器收到请求后会调⽤处理器映射器(HandlerMapping),处理器映射器根据请求U ...

  5. Java 集合系列05之 LinkedList详细介绍(源码解析)和使用示例

    概要  前面,我们已经学习了ArrayList,并了解了fail-fast机制.这一章我们接着学习List的实现类——LinkedList.和学习ArrayList一样,接下来呢,我们先对Linked ...

  6. 【转】Java 集合系列05之 LinkedList详细介绍(源码解析)和使用示例

    概要  前面,我们已经学习了ArrayList,并了解了fail-fast机制.这一章我们接着学习List的实现类——LinkedList.和学习ArrayList一样,接下来呢,我们先对Linked ...

  7. 有关LinkedList常用方法的源码解析

    上文里解析了有关ArrayList中的几个常用方法的源码——<有关ArrayList常用方法的源码解析>,本文将对LinkedList的常用方法做简要解析. LinkedList是基于链表 ...

  8. Redux异步解决方案之Redux-Thunk原理及源码解析

    前段时间,我们写了一篇Redux源码分析的文章,也分析了跟React连接的库React-Redux的源码实现.但是在Redux的生态中还有一个很重要的部分没有涉及到,那就是Redux的异步解决方案.本 ...

  9. ORB原理与源码解析

    转载: http://blog.csdn.net/luoshixian099/article/details/48523267 CSDN-勿在浮沙筑高台 没有时间重新复制代码,只能一股脑的复制,所以代 ...

随机推荐

  1. C#的内存管理原理解析+标准Dispose模式的实现

    本文内容是本人参考多本经典C#书籍和一些前辈的博文做的总结 尽管.NET运行库负责处理大部分内存管理工作,但C#程序员仍然必须理解内存管理的工作原理,了解如何高效地处理非托管的资源,才能在非常注重性能 ...

  2. C#中string,char[],byte[]互相转换

    string 转换成 Char[] string ss = "我爱你,中国"; char[] cc = ss.ToCharArray(); Char[] 转换成string str ...

  3. python求职Top10城市,来看看是否有你所在的城市

    前言 从智联招聘爬取相关信息后,我们关心的是如何对内容进行分析,获取用用的信息. 本次以上篇文章“5分钟掌握智联招聘网站爬取并保存到MongoDB数据库”中爬取的数据为基础,分析关键词为“python ...

  4. swift学习 - 单例实现(singleton)

    swift中实现单例的方式 class LGConfig: NSObject { static let instance = LGConfig() private override init() { ...

  5. Kotlin初探

    前几天看到新闻,Google将Kotlin语言作为Android应用开发的一级语言, 与Java并驾齐驱, 这则消息在开发界一下就炸开了锅( 好像平息的很快...)! 连Google的亲儿子go语言也 ...

  6. 开篇有益-解析微软微服务架构eShopOnContainers(一)

    为了推广.Net Core,微软为我们提供了一个开源Demo-eShopOnContainers,这是一个使用Net Core框架开发的,跨平台(几乎涵盖了所有平台,windows.mac.linux ...

  7. [原创]MySQL数据库忘记root密码解决办法

    MySQL数据库忘记root密码解决办法 1.在运行输入services.msc打开服务窗体,找到MYSQL服务.右键停止将其关闭.如图:

  8. java 与操作系统进程同步问题(一)————互斥问题

    最近学校开设了操作系统原理课程,老师要求用任意语言去模拟进程的同步和互斥问题. 在尝试的写了之后,发现这个问题非常有意思,故想记录在博客中,作为自己的学习轨迹. 个人还是比较喜欢用Java语言,所以采 ...

  9. Lock(二)解决Lock问题

    本文介绍通过Toad.EM及SQL语句来处理数据库产生的锁.在这之前需要对v$lock和v$session这两个数据字典有一定的了解. (一)使用Toad处理锁 (1)使用Toad的session b ...

  10. node async基础1

    async的基础使用 1 async each   语法格式each(collection, iteratee, [callback])   用途:遍历集合中的元素,并行对每个元素执行一定的操作,但是 ...