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)这两个方法,进行手动的序列化有存储数据的有用属性。

参考

更多请关注微信………

时间: 2024-12-17 12:00:49

LinkedList原理及源码解析的相关文章

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

java 集合系列目录: Java 集合系列 01 总体框架 Java 集合系列 02 Collection架构 Java 集合系列 03 ArrayList详细介绍(源码解析)和使用示例 Java 集合系列 04 LinkedList详细介绍(源码解析)和使用示例 概要  和学习ArrayList一样,接下来呢,我们先对LinkedList有个整体认识,然后再学习它的源码:最后再通过实例来学会使用LinkedList.内容包括:第1部分 LinkedList介绍第2部分 LinkedList数

【特征匹配】BRIEF特征描述子原理及源码解析

相关:Fast原理及源码解析 Harris原理及源码解析 SIFT原理及源码解析 SURF原理及源码解析 转载请注明出处: http://blog.csdn.net/luoshixian099/article/details/48338273 传统的特征点描述子如SIFT,SURF描述子,每个特征点采用128维(SIFT)或者64维(SURF)向量去描述,每个维度上占用4字节,SIFT需要128×4=512字节内存,SURF则需要256字节.如果对于内存资源有限的情况下,这种描述子方法显然不适应

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

概要  前面,我们已经学习了ArrayList,并了解了fail-fast机制.这一章我们接着学习List的实现类——LinkedList.和学习ArrayList一样,接下来呢,我们先对LinkedList有个整体认识,然后再学习它的源码:最后再通过实例来学会使用LinkedList.内容包括:第1部分 LinkedList介绍第2部分 LinkedList数据结构第3部分 LinkedList源码解析(基于JDK1.6.0_45)第4部分 LinkedList遍历方式第5部分 LinkedL

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

概要  前面,我们已经学习了ArrayList,并了解了fail-fast机制.这一章我们接着学习List的实现类——LinkedList.和学习ArrayList一样,接下来呢,我们先对LinkedList有个整体认识,然后再学习它的源码:最后再通过实例来学会使用LinkedList.内容包括:第1部分 LinkedList介绍第2部分 LinkedList数据结构第3部分 LinkedList源码解析(基于JDK1.6.0_45)第4部分 LinkedList遍历方式第5部分 LinkedL

LinkedList: 详细介绍(源码解析)和使用示例 [From skywang12345]

概要  前面,我们已经学习了ArrayList,并了解了fail-fast机制.这一章我们接着学习List的实现类——LinkedList. 和学习ArrayList一样,接下来呢,我们先对LinkedList有个整体认识,然后再学习它的源码:最后再通过实例来学会使用LinkedList.内容包括: 第1部分 LinkedList介绍 第2部分 LinkedList数据结构 第3部分 LinkedList源码解析(基于JDK1.6.0_45) 第4部分 LinkedList遍历方式 第5部分 L

【特征匹配】RANSAC算法原理与源码解析

转载请注明出处:http://blog.csdn.net/luoshixian099/article/details/50217655 随机抽样一致性(RANSAC)算法,可以在一组包含"外点"的数据集中,采用不断迭代的方法,寻找最优参数模型,不符合最优模型的点,被定义为"外点".在图像配准以及拼接上得到广泛的应用,本文将对RANSAC算法在OpenCV中角点误匹配对的检测中进行解析. 1.RANSAC原理 OpenCV中滤除误匹配对采用RANSAC算法寻找一个最佳

Sprig AOP原理及源码解析

在介绍AOP之前,想必很多人都听说AOP是基于动态代理和反射来实现的,那么在看AOP之前,你需要弄懂什么是动态代理和反射及它们又是如何实现的. 想了解JDK的动态代理及反射的实现和源码分析,请参见下面三篇文章 JDK的动态代理源码分析之一 (http://blog.csdn.net/weililansehudiefei/article/details/73655925) JDK的动态代理源码分析之二(http://blog.csdn.net/weililansehudiefei/article/

Spring核心框架 - AOP的原理及源码解析

一.AOP的体系结构 如下图所示:(引自AOP联盟) 层次3语言和开发环境:基础是指待增加对象或者目标对象:切面通常包括对于基础的增加应用:配置是指AOP体系中提供的配置环境或者编织配置,通过该配置AOP将基础和切面结合起来,从而完成切面对目标对象的编织实现. 层次2面向方面系统:配置模型,逻辑配置和AOP模型是为上策的语言和开发环境提供支持的,主要功能是将需要增强的目标对象.切面和配置使用AOP的API转换.抽象.封装成面向方面中的逻辑模型. 层次1底层编织实现模块:主要是将面向方面系统抽象封

【Spring】Spring IOC原理及源码解析之scope=request、session

一.容器 1. 容器 抛出一个议点:BeanFactory是IOC容器,而ApplicationContex则是Spring容器. 什么是容器?Collection和Container这两个单词都有存放什么东西的意思,但是放在程序猿的世界,却注定是千差万别.Collection,集合,存放obj instanceof Class为true的一类对象,重点在于存放:Container,容器,可以存放各种各样的obj,但不仅仅是存放,他被称为容器,更重要的是他能管理存放对象的生命周期和依赖. 容器: