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-勿在浮沙筑高台 没有时间重新复制代码,只能一股脑的复制,所以代 ...
随机推荐
- 致DBA:为什么你经常犯错,是因为你做的功课不够
专职做DBA已经6年多的事件了,看同行.同事犯了太多的错误,自己也犯了非常多的错误.一路走来,感触非常深.然而绝大多数的错误其实都是很低级的错误.有的是因为不了解某个引擎的特性导致:有的是因为对线上环 ...
- [问题贴]mui.openWindow+自定义事件监听操作让alert()执行两次
仔细看,Alert函数执行了两次 共两个页面:index.html和detail.html, detail.html为按钮设置了自定义事件监听(newsId),触发alert. 在index.html ...
- 二识angularJS
前言:记得三月份时下定决心说每天要更新一篇博客,学习点新东西,实践下来发现太不现实,生活中的事情很多,再喜欢也不能让它一件占据生活的全部吧,所以呢,以后顺其自然吧.之前有一篇'初识angular'因为 ...
- An Introduction to Stock Market Data Analysis with R (Part 1)
Around September of 2016 I wrote two articles on using Python for accessing, visualizing, and evalua ...
- Creating beautiful charts in chinese with ggplot2
Before we start My chinese skills are poor and biased. I did learn during my internship and I contin ...
- 微信小程序封装http访问网络库实例代码
之前都是使用LeanCloud为存储,现在用传统API调用时做如下封装 var HOST = 'http://localhost/lendoo/public/index.php/'; // 网站请求接 ...
- Promise (1) 初步接触
总想着王者荣耀排位赛再提升个等级就弃掉游戏好好学习,然而打了两个周也没升上去,看来是应该换个方向发挥了. 最近看了<javascript Promise迷离书>,对Promise的理解颇有 ...
- OVS + kernel datapath 的安装
***kernel datapath的OVS编译安装 下载源代码 $ git clone https://github.com/openvswitch/ovs.git 准备工具:生成configure ...
- JVM-3.内存
目录 一.运行时数据区 二.内存使用细节:以HotSpot的堆为例 三.实战:OutOfMemoryError异常 四.垃圾收集器(堆+方法区)与内存分配策略 一.运行时数据区 1.程序计 ...
- Spring学习(7)--- @Required注解
@Required注解是用于bean属性的setter方法 这个注解仅仅表示,受影响的bean属性必须在配置时被填充,通过在bean定义胡通过自动装配一个明确的属性值 package com.mypa ...