LinkedList源码解析

简介: 本解析源码来自JDK1.7 LinkedList许多方法是为了适配其实现的接口,本质上都是双向链表的操作LinkedList概要基于双向链表,主要实现了List和Deque接口,Deque接口继承自Queue,...

本解析源码来自JDK1.7
LinkedList许多方法是为了适配其实现的接口,本质上都是双向链表的操作

LinkedList概要

  • 基于双向链表,主要实现了List和Deque接口,Deque接口继承自Queue,所以LinkedList同时实现了Queue接口
  • 由于其基于双向链表,操作需要操作连接指针数数较多,所以线性操作系数比ArrayList较大
  • 插入删除快,随机访问慢
  • 线程不安全,修改列表结构的操作需要外部同步,更新列表元素值不算结构修改
// 外部同步方法
List list = Collections.synchronizedList(new LinkedList(...));  
  • 允许null值
  • 随机访问index位置元素,需要遍历列表,从头部和尾部选择接近的一端开始遍历
  • 迭代器通过比较修改次数(modCount)实现fail-fast机制,由于线程不安全,无法保证modCount的值的可靠性,所以fail-fast可能出现非预期结果

实现接口

  • Cloneable 实现浅拷贝
  • Serializable 可序列化
  • List 可顺序访问
  • Deque 实现双端队列接口
public class LinkedList<E>
    extends AbstractSequentialList<E>
    implements List<E>, Deque<E>, Cloneable, java.io.Serializable  

接口方法

由于LinkedList同时实现了List和Deque接口,所以要实现较多的方法,但是都是基于双向链表的一些方法。
- List

public interface List<E> extends Collection<E> {
    // Query Operations
    int size();
    boolean isEmpty();
    boolean contains(Object o);
    Iterator<E> iterator();
    Object[] toArray();
    <T> T[] toArray(T[] a);
    // Modification Operations
    boolean add(E e);
    boolean remove(Object o);
    // Bulk Modification Operations
    boolean containsAll(Collection<?> c);
    boolean addAll(Collection<? extends E> c);
    boolean addAll(int index, Collection<? extends E> c);
    boolean removeAll(Collection<?> c);
    boolean retainAll(Collection<?> c);
    void clear();
    // Comparison and hashing
    boolean equals(Object o);
    int hashCode();
    // Positional Access Operations
    E get(int index);
    E set(int index, E element);
    void add(int index, E element);
    E remove(int index);
    // Search Operations
    int indexOf(Object o);
    int lastIndexOf(Object o);
    // List Iterators
    ListIterator<E> listIterator();
    ListIterator<E> listIterator(int index);
    // View
    List<E> subList(int fromIndex, int toIndex);
}
  • Queue
    注意对于Queue接口来说,方法可以分为两组
  • 抛出异常的方法 add(入队) remove(出队) element(查看对头)
  • 不抛出异常的方法 offer poll peek
    两组对应的方法功能基本相同,只是在处理队列为空时remove和element会抛出异常NoSuchElementException,poll和peek会返回null而不是抛出异常,在队列满时,add会抛出IllegalArgumentException,offer则不会抛出异常。
public interface Queue<E> extends Collection<E> {
    boolean add(E e);
    boolean offer(E e);
    E remove();
    E poll();
    E element();
    E peek();
}
  • Deque extends Queue
    Deque不仅定义FIFO(first-in-first-out)的Queue方法,而且定义了LIFO(last-in-first-out)的stack方法
    其方法可以分为以下几类
  • 抛出异常的FIFO方法 addFirst,addLast,removeFirst,removeLast,getFirst,getLast,add,remove,element
  • 不抛出异常的FIFO方法 offerfirst,offerLast,pollFirst,pollLast,peekFirst,peekLast,offer,poll,peek
  • 栈方法 LIFO方法 push,pop,peek
  • 迭代器 正向Iterator 反向 descendingIterator
public interface Deque<E> extends Queue<E> {
    void addFirst(E e);
    void addLast(E e);
    boolean offerFirst(E e);
    boolean offerLast(E e);
    E removeFirst();
    E removeLast();
    E pollFirst();
    E pollLast();
    E getFirst();
    E getLast();
    E peekFirst();
    E peekLast();
    boolean removeFirstOccurrence(Object o);
    boolean removeLastOccurrence(Object o);
    boolean add(E e);
    boolean offer(E e);
    E remove();
    E poll();
    E element();
    E peek();
    void push(E e);
    E pop();
    boolean remove(Object o);
    boolean contains(Object o);
    public int size();
    Iterator<E> iterator();
    Iterator<E> descendingIterator();
}

主要成员

双向链表主要成员就是其头尾节点

    transient int size = 0;  
    transient Node<E> first;  
    transient Node<E> last;  

基本节点

双向链表基本节点

    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;
        }
    }  

构造方法

  • 构造空的双向链表
  • 基于Collection构造链表,调用addAll方法将新的元素插入到链表末尾
  • addAll方法实现
    • 首先检查位置的合法性(0=<\pos<=size)包含开始和结尾
    • 检查要插入的元素的个数,为0直接返回
    • 找到要插入的元素的前驱和后继,如果在结尾插入,那么后继为null,前驱为last,否则后继为node(index)(在index位置前插入,index==lenght则在最后一个元素之后),前驱为succ.prev
    • node(index) 返回位置index的元素,首先要判断index距离first近还是last近
    • 根据找到的前驱进行元素插入,当前驱为空,说明first为null,更新此节点为first,否则连接到pred之后,更新pred
    • 将插入的末尾元素与succ连接起来,如果succ==null说明是在尾部插入,pred更新为新的last,否则将pred与succ连接起来
    public LinkedList() {
    } 
    public LinkedList(Collection<? extends E> c) {
        this();
        addAll(c);
    }
    public boolean addAll(Collection<? extends E> c) {
        return addAll(size, c);
    }  
    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;
        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;
        }
        if (succ == null) {
            last = pred;
        } else {
            pred.next = succ;
            succ.prev = pred;
        }
        size += numNew;
        modCount++;
        return true;
    } 
    Node<E> node(int index) {
        // assert isElementIndex(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;
        }
    }  

位置检查

有两种位置,一是元素位置,一是插入位置,区别在于能不能等于size,插入在index之前,当index==size时插入整个链表之后

    private void checkElementIndex(int index) {
        if (!isElementIndex(index))
            throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
    }
    private void checkPositionIndex(int index) {
        if (!isPositionIndex(index))
            throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
    }  
    private boolean isElementIndex(int index) {
        return index >= 0 && index < size;
    }
    private boolean isPositionIndex(int index) {
        return index >= 0 && index <= size;
    }  

插入节点

新建节点时只是连接了该节点到其他节点的指针,注意还需要连接其他节点到该节点的指针
- 插入头结点 如果仅有一个节点那么同时也是尾节点
- 插入尾节点 如果仅有一个节点那么同时也是头节点
- 插入普通节点 在给定的节点之前插入,(默认当前节点不为空),找到前驱,新建节点,连接左右节点,注意插入节点可能是头结点,但是不可能是尾
两个方法的可见性为什么不一样?

    private void linkFirst(E e) {
        final Node<E> f = first;
        final Node<E> newNode = new Node<>(null, e, f);
        first = newNode;
        if (f == null)
            last = newNode;
        else
            f.prev = newNode;
        size++;
        modCount++;
    }
    void linkLast(E e) {
        final Node<E> l = last;
        final Node<E> newNode = new Node<>(l, e, null);
        last = newNode;
        if (l == null)
            first = newNode;
        else
            l.next = newNode;
        size++;
        modCount++;
    }  
    void linkBefore(E e, Node<E> succ) {
        // assert succ != null;
        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++;
    }  

删除节点

  • 删除头结点,只需要first=first.next,然后把first的prev赋值null,考虑到可能只有一个节点,此时尾节点也会为null,否则头结点前驱置为null,f.item = null和f.next=null句没有程序逻辑依然正确,但是由于该节点还有指向其他节点的引用,所以不会被垃圾回收,置为null可以帮助垃圾回收器回收
  • 删除尾节点,类比删除头结点
  • 删除当前节点,prev.next = next next.prev = prev,考虑到可能是头结点,或尾节点需要更新头尾节点,其中x.item=null,x.prev = null,x.next=null帮助垃圾回收器确定回收
  • 清空链表 逻辑上只需要将first,last置为null即可,但是清空节点之间的连接可以帮助分代垃圾回收器回收存活多代的垃圾,即便还有迭代器指向这些节点,依然后释放内存
    private E unlinkFirst(Node<E> f) {
        // assert f == first && f != null;
        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;
    }
    private E unlinkLast(Node<E> l) {
        // assert l == last && l != null;
        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;
    }
    E unlink(Node<E> x) {
        // assert x != null;
        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;
    }  
    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
        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++;
    }  

查找节点

  • 按值查找,从头到尾遍历整个链表,注意由于列表可能存在null值,查找null需要单独处理,而不能用x.equals(null)来处理
  • 按位置查找,首先判断位置距离头结点近还是尾节点近
    public int indexOf(Object o) {
        int index = 0;
        if (o == null) {
            for (Node<E> x = first; x != null; x = x.next) {
                if (x.item == null)
                    return index;
                index++;
            }
        } else {
            for (Node<E> x = first; x != null; x = x.next) {
                if (o.equals(x.item))
                    return index;
                index++;
            }
        }
        return -1;
    }  
    Node<E> node(int index) {
        // assert isElementIndex(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;
        }
    }  

迭代器

与ArrayList类似
- LinkedList实现了两种迭代器,其中ListIterator除了实现基本的Iterator方法(hasNext,Next,remove),还包含更丰富的方法
- 迭代器作为LinkedList的内部类,可以直接访问修改ArrayList
- ListIterator额外实现的方法
- listIterator(index) 可以指定开始遍历的位置
- hasPrevious 有没有前驱
- previous 返回前驱
- add 实现添加元素
- set 更新上次访问的元素
- previousIndex() nextIndex() 返回下一个,和上一个位置
NOTE
remove,add,set方法都是改变上次被访问元素位置进行操作,连续调用两次以上就会出现问题

序列化

与ArrayList类似,同样代表列表元素的first,last,size不会被默认序列化函数序列化,列表元素通过逐个顺序写入,顺序读出的方式进行序列化

    private void writeObject(java.io.ObjectOutputStream s)
        throws java.io.IOException {
        // Write out any hidden serialization magic
        s.defaultWriteObject();
        // Write out size
        s.writeInt(size);
        // Write out all elements in the proper order.
        for (Node<E> x = first; x != null; x = x.next)
            s.writeObject(x.item);
    }
    @SuppressWarnings("unchecked")
    private void readObject(java.io.ObjectInputStream s)
        throws java.io.IOException, ClassNotFoundException {
        // Read in any hidden serialization magic
        s.defaultReadObject();
        // Read in size
        int size = s.readInt();
        // Read in all elements in the proper order.
        for (int i = 0; i < size; i++)
            linkLast((E)s.readObject());
    }  

fail-fast机制

与ArrayList类似同样有fail-fast机制
- 源码中凡是修改List结构(插入,删除,打乱顺序,调整容量,不包含set更新元素),都会涉及到modCount++
- 在LinkedList类创建迭代器之后,除非通过迭代器自身remove或add对列表结构进行修改,否则在其他线程中以任何形式对列表进行修改,迭代器马上会抛出异常,快速失败。
- 该机制通过检查modCount的值来确定是否迭代过程中有其他线程对列表进行修改

        final void checkForComodification() {
            if (modCount != expectedModCount)
                throw new ConcurrentModificationException();
        }  
目录
相关文章
|
6月前
|
算法 测试技术 C语言
深入理解HTTP/2:nghttp2库源码解析及客户端实现示例
通过解析nghttp2库的源码和实现一个简单的HTTP/2客户端示例,本文详细介绍了HTTP/2的关键特性和nghttp2的核心实现。了解这些内容可以帮助开发者更好地理解HTTP/2协议,提高Web应用的性能和用户体验。对于实际开发中的应用,可以根据需要进一步优化和扩展代码,以满足具体需求。
610 29
|
6月前
|
前端开发 数据安全/隐私保护 CDN
二次元聚合短视频解析去水印系统源码
二次元聚合短视频解析去水印系统源码
180 4
|
6月前
|
JavaScript 算法 前端开发
JS数组操作方法全景图,全网最全构建完整知识网络!js数组操作方法全集(实现筛选转换、随机排序洗牌算法、复杂数据处理统计等情景详解,附大量源码和易错点解析)
这些方法提供了对数组的全面操作,包括搜索、遍历、转换和聚合等。通过分为原地操作方法、非原地操作方法和其他方法便于您理解和记忆,并熟悉他们各自的使用方法与使用范围。详细的案例与进阶使用,方便您理解数组操作的底层原理。链式调用的几个案例,让您玩转数组操作。 只有锻炼思维才能可持续地解决问题,只有思维才是真正值得学习和分享的核心要素。如果这篇博客能给您带来一点帮助,麻烦您点个赞支持一下,还可以收藏起来以备不时之需,有疑问和错误欢迎在评论区指出~
|
6月前
|
移动开发 前端开发 JavaScript
从入门到精通:H5游戏源码开发技术全解析与未来趋势洞察
H5游戏凭借其跨平台、易传播和开发成本低的优势,近年来发展迅猛。接下来,让我们深入了解 H5 游戏源码开发的技术教程以及未来的发展趋势。
|
6月前
|
存储 前端开发 JavaScript
在线教育网课系统源码开发指南:功能设计与技术实现深度解析
在线教育网课系统是近年来发展迅猛的教育形式的核心载体,具备用户管理、课程管理、教学互动、学习评估等功能。本文从功能和技术两方面解析其源码开发,涵盖前端(HTML5、CSS3、JavaScript等)、后端(Java、Python等)、流媒体及云计算技术,并强调安全性、稳定性和用户体验的重要性。
|
6月前
|
负载均衡 JavaScript 前端开发
分片上传技术全解析:原理、优势与应用(含简单实现源码)
分片上传通过将大文件分割成多个小的片段或块,然后并行或顺序地上传这些片段,从而提高上传效率和可靠性,特别适用于大文件的上传场景,尤其是在网络环境不佳时,分片上传能有效提高上传体验。 博客不应该只有代码和解决方案,重点应该在于给出解决方案的同时分享思维模式,只有思维才能可持续地解决问题,只有思维才是真正值得学习和分享的核心要素。如果这篇博客能给您带来一点帮助,麻烦您点个赞支持一下,还可以收藏起来以备不时之需,有疑问和错误欢迎在评论区指出~
|
7月前
|
机器学习/深度学习 自然语言处理 算法
生成式 AI 大语言模型(LLMs)核心算法及源码解析:预训练篇
生成式 AI 大语言模型(LLMs)核心算法及源码解析:预训练篇
1203 0
|
10月前
|
监控 Java 应用服务中间件
高级java面试---spring.factories文件的解析源码API机制
【11月更文挑战第20天】Spring Boot是一个用于快速构建基于Spring框架的应用程序的开源框架。它通过自动配置、起步依赖和内嵌服务器等特性,极大地简化了Spring应用的开发和部署过程。本文将深入探讨Spring Boot的背景历史、业务场景、功能点以及底层原理,并通过Java代码手写模拟Spring Boot的启动过程,特别是spring.factories文件的解析源码API机制。
262 2
|
9月前
|
设计模式 存储 安全
【23种设计模式·全精解析 | 创建型模式篇】5种创建型模式的结构概述、实现、优缺点、扩展、使用场景、源码解析
创建型模式的主要关注点是“怎样创建对象?”,它的主要特点是"将对象的创建与使用分离”。这样可以降低系统的耦合度,使用者不需要关注对象的创建细节。创建型模式分为5种:单例模式、工厂方法模式抽象工厂式、原型模式、建造者模式。
【23种设计模式·全精解析 | 创建型模式篇】5种创建型模式的结构概述、实现、优缺点、扩展、使用场景、源码解析
|
9月前
|
存储 设计模式 算法
【23种设计模式·全精解析 | 行为型模式篇】11种行为型模式的结构概述、案例实现、优缺点、扩展对比、使用场景、源码解析
行为型模式用于描述程序在运行时复杂的流程控制,即描述多个类或对象之间怎样相互协作共同完成单个对象都无法单独完成的任务,它涉及算法与对象间职责的分配。行为型模式分为类行为模式和对象行为模式,前者采用继承机制来在类间分派行为,后者采用组合或聚合在对象间分配行为。由于组合关系或聚合关系比继承关系耦合度低,满足“合成复用原则”,所以对象行为模式比类行为模式具有更大的灵活性。 行为型模式分为: • 模板方法模式 • 策略模式 • 命令模式 • 职责链模式 • 状态模式 • 观察者模式 • 中介者模式 • 迭代器模式 • 访问者模式 • 备忘录模式 • 解释器模式
【23种设计模式·全精解析 | 行为型模式篇】11种行为型模式的结构概述、案例实现、优缺点、扩展对比、使用场景、源码解析

推荐镜像

更多
  • DNS