LinkedList源码解析

本文涉及的产品
云解析 DNS,旗舰版 1个月
全局流量管理 GTM,标准版 1个月
公共DNS(含HTTPDNS解析),每月1000万次HTTP解析
简介: 本解析源码来自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();
        }  
目录
相关文章
|
7天前
|
监控 Java 应用服务中间件
高级java面试---spring.factories文件的解析源码API机制
【11月更文挑战第20天】Spring Boot是一个用于快速构建基于Spring框架的应用程序的开源框架。它通过自动配置、起步依赖和内嵌服务器等特性,极大地简化了Spring应用的开发和部署过程。本文将深入探讨Spring Boot的背景历史、业务场景、功能点以及底层原理,并通过Java代码手写模拟Spring Boot的启动过程,特别是spring.factories文件的解析源码API机制。
28 2
|
8天前
|
存储 安全 Linux
Golang的GMP调度模型与源码解析
【11月更文挑战第11天】GMP 调度模型是 Go 语言运行时系统的核心部分,用于高效管理和调度大量协程(goroutine)。它通过少量的操作系统线程(M)和逻辑处理器(P)来调度大量的轻量级协程(G),从而实现高性能的并发处理。GMP 模型通过本地队列和全局队列来减少锁竞争,提高调度效率。在 Go 源码中,`runtime.h` 文件定义了关键数据结构,`schedule()` 和 `findrunnable()` 函数实现了核心调度逻辑。通过深入研究 GMP 模型,可以更好地理解 Go 语言的并发机制。
|
20天前
|
消息中间件 缓存 安全
Future与FutureTask源码解析,接口阻塞问题及解决方案
【11月更文挑战第5天】在Java开发中,多线程编程是提高系统并发性能和资源利用率的重要手段。然而,多线程编程也带来了诸如线程安全、死锁、接口阻塞等一系列复杂问题。本文将深度剖析多线程优化技巧、Future与FutureTask的源码、接口阻塞问题及解决方案,并通过具体业务场景和Java代码示例进行实战演示。
39 3
|
1月前
|
存储
让星星⭐月亮告诉你,HashMap的put方法源码解析及其中两种会触发扩容的场景(足够详尽,有问题欢迎指正~)
`HashMap`的`put`方法通过调用`putVal`实现,主要涉及两个场景下的扩容操作:1. 初始化时,链表数组的初始容量设为16,阈值设为12;2. 当存储的元素个数超过阈值时,链表数组的容量和阈值均翻倍。`putVal`方法处理键值对的插入,包括链表和红黑树的转换,确保高效的数据存取。
56 5
|
1月前
|
Java Spring
Spring底层架构源码解析(三)
Spring底层架构源码解析(三)
113 5
|
1月前
|
XML Java 数据格式
Spring底层架构源码解析(二)
Spring底层架构源码解析(二)
|
1月前
|
算法 Java 程序员
Map - TreeSet & TreeMap 源码解析
Map - TreeSet & TreeMap 源码解析
34 0
|
1月前
|
缓存 Java 程序员
Map - LinkedHashSet&Map源码解析
Map - LinkedHashSet&Map源码解析
68 0
|
1月前
|
算法 Java 容器
Map - HashSet & HashMap 源码解析
Map - HashSet & HashMap 源码解析
57 0
|
1月前
|
存储 Java C++
Collection-PriorityQueue源码解析
Collection-PriorityQueue源码解析
61 0

推荐镜像

更多
下一篇
无影云桌面