LinkedList源码详解

LinkedList概述

LinkedList是一个双向链表,链表数据结构的特点是每个元素分配的空间不必连续、插入和删除元素时速度非常快、但访问元素的速度较慢。本源码版本:1.8.0_172

LinkedList结构

LinkedList.png
  • Deque接口:双端队列(double ended queue)接口,支持从两个端点检索和插入数据,即可支持LIFO也可支持FIFO
  • Serializable接口:标记接口,java提供的序列化接口,为对象提供标准的序列化和反序列化操作
  • Cloneable接口:标记接口,只有实现了这个接口,然后才能调用类的clone()方法,否则抛出CloneNotSupportedException异常

LinkedList的源码分析

LinkedList属性

/**
 * 链表长度
 */
transient int size = 0;

/**
 * 链表的头节点
 * Invariant: (first == null && last == null) ||
 *            (first.prev == null && first.item != null)
 */
transient Node first;

/**
 * 链表的尾部节点
 * Invariant: (first == null && last == null) ||
 *            (last.next == null && last.item != null)
 */
transient Node last;

LinkedList的节点Node介绍

Node节点主要包括:

  • item:当前节点元素信息
  • next:后继节点,即下一个节点Node的信息,节点是尾部节点时,next == null
  • prev:前驱节点,即上一个节点Node的信息,节点是头节点时,prev == null

LinkedList的节点Node代码如下:

private static class Node {
    E item;
    Node next;
    Node prev;

    Node(Node prev, E element, Node next) {
        this.item = element;
        this.next = next;
        this.prev = prev;
    }
}

LinkedList的主要实现方法

LinkedList的所有操作主要是围绕如下七个方法实现的:
linkFirst(链表头部插入节点)


linkFirst.jpg

linkLast(链表尾部插入)


LinkLast.jpg

linkBefore(某一个节点前插入)


LinkedBefore.jpg

unlinkFirst(删除链表头部节点),unlinkLast(删除链表尾部节点),unlink(删除指定节点),
node(查找指定索引节点),掌握上面几个方法的实现,基本就掌握了LinkedList。

/**
 * 链表头部插入节点
 * 
 * 1. 将头节点first缓存到临时节点f
 * 2. 创建需要插入的元素的节点信息,同时将新增节点的后驱指向原头节点
 * 3. 将新增节点设置为头节点
 * 4. 判断原头节点是否为空
 *      如果为空,链表为空,将新增节点设置为尾部节点
 *      如果不为空,则将原头节点的前驱指向新增节点
 * 5. 链表长度加1
 * 6. 修改记录数加1
 */
private void linkFirst(E e) {
    final Node f = first;
    final Node newNode = new Node<>(null, e, f);
    first = newNode;
    if (f == null)
        last = newNode;
    else
        f.prev = newNode;
    size++;
    modCount++;
}

/**
 * 链表尾部插入节点
 * 
 * 1. 将尾部节点last缓存到临时节点f
 * 2. 创建需要插入的元素的节点信息,同时将新增节点的前驱指向原尾部节点
 * 3. 将新增节点设置为尾部节点
 * 4. 判断原尾部节点是否为空
 *       如果为空,链表为空,需要将头节点设置为新增节点
 *       如果不为空,则将原尾部节点的后继指向新增节点
 * 5. 链表长度加1
 * 6. 修改记录数加1
 */
void linkLast(E e) {
    final Node l = last;
    final Node newNode = new Node<>(l, e, null);
    last = newNode;
    if (l == null)
        first = newNode;
    else
        l.next = newNode;
    size++;
    modCount++;
}

/**
 * 在指定节点前添加节点
 *
 * 1. 缓存指定节点succ的前驱节点pred
 * 2. 创建需要插入元素的节点信息newNode,同时将前驱设置为指定节点的前驱pred, 后继设置为指定节点succ
 * 3. 将指定节点的前驱设置为新增节点newNode
 * 4. 判断指定节点的前驱节点pred是否为空
 *        如果为空,说明指定节点是头节点,也是尾部节点,需要将新增节点设置为尾部节点
 *        如果不为空,需要将指定节点的前驱节点的pred的后继节点执行新增节点newNode
 * 5. 链表长度加1
 * 6. 修改记录数加1
 */
void linkBefore(E e, Node succ) {
    // assert succ != null;
    final Node pred = succ.prev;
    final Node newNode = new Node<>(pred, e, succ);
    succ.prev = newNode;
    if (pred == null)
        first = newNode;
    else
        pred.next = newNode;
    size++;
    modCount++;
}

/**
 * 移除首节点(首节点不为空)
 * 
 * 1. 缓存首节点f元素信息element
 * 2. 缓存首节点f的后继节点next
 * 3. 将首节点的元素信息设置为空(为了gc释放资源)
 * 4. 将首节点的后继节点设置为空(为了gc释放资源)
 * 5. 将首节点重新设置为原首节点的后继节点next
 * 6. 判断后继节点next是否为空,
 *        如果为空,则原首节点无后继节点,将尾部节点设置为null
 *        如果不为空,则将后继节点next(也就是现在的首节点)的前驱节点设置为null
 * 7. 链表长度减一
 * 8. 修改记录数加一
 * 9. 返回移除的首节点元素
 */
private E unlinkFirst(Node f) {
    // assert f == first && f != null;
    final E element = f.item;
    final Node 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;
}

/**
 * 移除尾部节点(尾部节点不为空)
 * 
 * 1. 缓存尾部节点f元素信息element
 * 2. 缓存尾部节点f的前驱节点prev
 * 3. 将尾部节点的元素信息设置为空(为了gc释放资源)
 * 4. 将尾部节点的前驱节点设置为空(为了gc释放资源)
 * 5. 将尾部节点重新设置为原尾部节点的前驱节点prev
 * 6. 判断前驱节点prev是否为空,
 *        如果为空,则原尾部节点无前驱节点,将首节点设置为null
 *        如果不为空,则将前驱节点prev(也就是现在的尾部节点)的后继节点设置为null
 * 7. 链表长度减一
 * 8. 修改记录数加一
 * 9. 返回移除的首节点元素
 */
private E unlinkLast(Node l) {
    // assert l == last && l != null;
    final E element = l.item;
    final Node 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;
}

/**
 * 移除指定节点(节点不允许为空)
 * 
 * 1. 缓存指定节点x的元素信息element
 * 2. 缓存指定节点的后继节点next
 * 3. 缓存指定节点的前驱节点prev
 * 4. 判断前驱节点prev是否为空
 *       如果为空,说明指定节点为首节点,重新将首节点设置为指定节点的后继节点next
 *       如果不为空,将前驱节点prev的后继节点重新设置为指定节点的后继节点next,指定节点的前驱节点prev设置为空(释放引用)
 * 5. 判断后继节点next是否为空
 *       如果为空,说明指定节点为尾部节点,重新将尾部节点设置为指定节点的前驱节点prev
 *       如果不为空,将后继节点next的前驱节点重新设置为指定节点的前驱节点prev,指定节点的后继节点next设置为空(释放引用)
 * 6. 将指定节点的元素设置为空
 * 7. 链表长度减一
 * 8. 修改记录数加一
 * 9. 返回移除的首节点元素
 */
E unlink(Node x) {
    // assert x != null;
    final E element = x.item;
    final Node next = x.next;
    final Node 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;
}

/**
 * 通过索引index获取链表中节点信息
 * 
 * 思考:为什么需要判断 index < (size >> 1)呢?
 * 答:因为LinkedList是一个双向链表结构,可以通过从头节点向后遍历到尾部节点
 *    也可以从最后节点遍历到头部节点, index < (size >> 1) 实际的作用是判断
 *    需要从头节点开始遍历还是需要从尾部节点开始遍历,size >> 1,相当于size除以2,
 *    index < (size >> 1) 则离头部节点更近,从头部节点遍历更快获取索引index位置节点信息,
 *    否则从尾部节点遍历更快获取到索引index位置的节点信息
 *
 */
Node node(int index) {
    // assert isElementIndex(index);

    if (index < (size >> 1)) {
        Node x = first;
        for (int i = 0; i < index; i++)
            x = x.next;
        return x;
    } else {
        Node x = last;
        for (int i = size - 1; i > index; i--)
            x = x.prev;
        return x;
    }
}

LinkedList构造器

LinkedList主要有两个构造器,一个是空构造器,一个是将集合中所有元素加入到LinkedList里面,具体实现将在LinkedList添加元素中讲解

/**
    * Constructs an empty list.
    */
public LinkedList() {
}

/**
 * Constructs a list containing the elements of the specified
 * collection, in the order they are returned by the collection's
 * iterator.
 *
 * @param  c the collection whose elements are to be placed into this list
 * @throws NullPointerException if the specified collection is null
 */
public LinkedList(Collection c) {
    this();
    addAll(c);
}

LinkedList新增相关详解

/**
 * LinkedList的新增方法,实际上调用的是linkLast方法
 *
 */
public boolean add(E e) {
    linkLast(e);
    return true;
}

/**
 * 在链表尾部添加元素e,实际调用add.
 * 
 * @param e the element to add
 * @return {@code true} (as specified by {@link Queue#offer})
 * @since 1.5
 */
public boolean offer(E e) {
    return add(e);
}

/**
 * 在指定索引位置插入元素element
 *  
 * 1、校验索引位置是否越界 index > size 则抛出IndexOutOfBoundsException异常
 * 2、判断索引是否等于LinkedList长度
 *    等于则表示元素element需要插入到LinkedList的尾部,直接调用linkedLast方法
 *    不等于,则通过node(index)方法找到索引index的节点信息,然后通过linkBefore方法
 *    将元素element插入到当前索引index节点之前
 * @param index 指定索引位置
 * @param element 需要插入的元素
 * @throws IndexOutOfBoundsException {@inheritDoc}
 */
public void add(int index, E element) {
    checkPositionIndex(index);

    if (index == size)
        linkLast(element);
    else
        linkBefore(element, node(index));
}
/**
 * 将集合c中所有元素添加到LinkedList尾部
 *
 * @param c 集合
 * @return {@code true} if this list changed as a result of the call
 * @throws NullPointerException if the specified collection is null
 */
public boolean addAll(Collection c) {
    return addAll(size, c);
}

/**
 * 将集合c中所有元素添加到LinkedList指定索引index位置前,如果index == size,则添加到尾部
 * 
 * @param index 索引位置
 * @param c 指定集合
 * @return {@code true} if this list changed as a result of the call
 * @throws IndexOutOfBoundsException {@inheritDoc}
 * @throws NullPointerException if the specified collection is null
 */
public boolean addAll(int index, Collection c) {
    //校验索引位置是否越界 index > size 则抛出IndexOutOfBoundsException异常
    checkPositionIndex(index);

    //将集合转换成数组
    Object[] a = c.toArray();
    //获取数组长度,用于判断数组元素个数
    int numNew = a.length;
    //如果数组长度等于0,返回false,没有元素添加
    if (numNew == 0)
        return false;

    //succ-索引index位置的节点信息
    //pred-索引index位置的前驱节点信息
    Node pred, succ;
    //判断是否添加到链表尾部,即 index == size
    if (index == size) { //添加到尾部
        //当前节点信息为空
        succ = null;
        //前驱节点为最后尾部节点last
        pred = last;
    } else { //添加到索引index位置
        succ = node(index);
        pred = succ.prev;
    }

    //循环遍历数组a,通过Node构造器,建立链表前驱后继关系
    for (Object o : a) {
        @SuppressWarnings("unchecked") E e = (E) o;
        Node newNode = new Node<>(pred, e, null);
        //如果前驱节点为空,说明没有前驱节点,需要将新节点设置为头部节点
        if (pred == null)
            first = newNode;
        else
            pred.next = newNode;
        
        //更新前驱节点为新节点
        pred = newNode;
    }

    //对数组a中最后一个元素节点pred建立后继管理
    //如果索引index节点succ节点为空,需要将数组a最后一个节点pred设置为尾部节点
    if (succ == null) {
        last = pred;
    } else {
        //将数组a最后一个节点pred后继执行索引index位置的节点
        pred.next = succ;
        //加索引index位置的节点前驱节点指向数组a最后一个节点pred
        succ.prev = pred;
    }

    //修改LinkedList长度为原来长度size + 数组长度 numNew
    size += numNew;
    modCount++;
    return true;
}

LinkedList移除相关操作

/**
 * 移除指定索引位置的节点
 *
 * @param index the index of the element to be removed
 * @return the element previously at the specified position
 * @throws IndexOutOfBoundsException {@inheritDoc}
 */
public E remove(int index) {
    //校验索引位置是否越界 index > size 则抛出IndexOutOfBoundsException异常
    checkElementIndex(index);
    //先通过node方法查找节点信息,然后通过unlink方法移除节点信息
    return unlink(node(index));
}

/**
 * 移除第一个匹配元素o的节点信息
 * @param o element to be removed from this list, if present
 * @return {@code true} if this list contained the specified element
 */
public boolean remove(Object o) {
    //判断元素o是否为空
    if (o == null) {
        //如果元素为空,从头节点开始遍历节点
        for (Node x = first; x != null; x = x.next) {
            //获取节点元素信息判断是否等于null
            if (x.item == null) {
                //移除节点
                unlink(x);
                //返回链表改动标识
                return true;
            }
        }
    } else { //元素不为空
        //从头节点开始遍历节点
        for (Node x = first; x != null; x = x.next) {
            //获取节点元素信息判断是否等于元素o
            if (o.equals(x.item)) {
                //移除节点
                unlink(x);
                //返回链表改动标识
                return true;
            }
        }
    }
    //返回链表没有改动标识
    return false;
}

LinkedList修改相关操作

/**
 *  将索引index位置元素换成element
 *
 * @param index index of the element to replace
 * @param element element to be stored at the specified position
 * @return the element previously at the specified position
 * @throws IndexOutOfBoundsException {@inheritDoc}
*/
public E set(int index, E element) {
    //校验索引位置是否越界 index > size 则抛出IndexOutOfBoundsException异常
    checkElementIndex(index);
    //通过node方法查询节点信息
    Node x = node(index);
    //换成节点元素信息
    E oldVal = x.item;
    //将节点元素信息换成element
    x.item = element;
    //返回被替换元素信息
    return oldVal;
}

/**
 * 获取第一个匹配元素o的节点索引,没有匹配返回-1
 * 基本思想和上续remove一样,不重复了
 * @param o element to search for
 * @return the index of the first occurrence of the specified element in
 *         this list, or -1 if this list does not contain the element
 */
public int indexOf(Object o) {
    int index = 0;
    if (o == null) {
        for (Node x = first; x != null; x = x.next) {
            if (x.item == null)
                return index;
            index++;
        }
    } else {
        for (Node x = first; x != null; x = x.next) {
            if (o.equals(x.item))
                return index;
            index++;
        }
    }
    return -1;
}

/**
 * 获取最后一个匹配元素o的节点索引,没有匹配返回-1
 * 基本思想和上续remove一样,只不过遍历是从尾部向头部遍历,不重复了
 *
 * @param o element to search for
 * @return the index of the last occurrence of the specified element in
 *         this list, or -1 if this list does not contain the element
 */
public int lastIndexOf(Object o) {
    int index = size;
    if (o == null) {
        for (Node x = last; x != null; x = x.prev) {
            index--;
            if (x.item == null)
                return index;
        }
    } else {
        for (Node x = last; x != null; x = x.prev) {
            index--;
            if (o.equals(x.item))
                return index;
        }
    }
    return -1;
}

LinkedList对双向队列的支持

/**
 * 在链表头部添加元素e,实际调用linkFirst.
 *
 * @param e the element to add
 */
public void addFirst(E e) {
    linkFirst(e);
}

/**
 * 在链表头部添加元素e,实际调用addFirst.
 * 
 * 与addFirst不同是返回true
 *
 * @param e the element to insert
 * @return {@code true} (as specified by {@link Deque#offerFirst})
 * @since 1.6
 */
public boolean offerFirst(E e) {
    addFirst(e);
    return true;
}

/**
 * 在链表尾部添加元素e,实际调用linkLast.
 *
 * @param e the element to add
 */
public void addLast(E e) {
    linkLast(e);
}

/**
 * 在链表尾部添加元素e,实际调用addLast.
 * 
 * 与addLast不同是返回true
 * @param e the element to insert
 * @return {@code true} (as specified by {@link Deque#offerLast})
 * @since 1.6
 */
public boolean offerLast(E e) {
    addLast(e);
    return true;
}

/**
 * 如果头节点不存在,返回null,
 * 思考:比较removeFirst()不同
 * 否则移除链表头节点,并返回头节点元素信息,
 * 核心实现unlinkFirst
 */
public E pollFirst() {
    final Node f = first;
    return (f == null) ? null : unlinkFirst(f);
}

/**
 * 如果尾部节点不存在,返回null,
 * 思考:比较removeLast()不同
 * 否则移除尾部节点,并返回节点元素信息
 * 核心实现unlinkFirst
 */
public E pollLast() {
    final Node l = last;
    return (l == null) ? null : unlinkLast(l);
}

/**
 * 如果头节点不存在,抛出异常NoSuchElementException,
 * 思考:比较pollFirst()不同
 * 否则移除链表头节点,并返回头节点元素信息,
 * 核心实现unlinkFirst
 */
public E removeFirst() {
    final Node f = first;
    if (f == null)
        throw new NoSuchElementException();
    return unlinkFirst(f);
}

/**
 * 如果尾部节点不存在,抛出异常NoSuchElementException,
 * 思考:比较pollLast()不同
 * 否则移除尾部节点,并返回节点元素信息
 * 核心实现unlinkFirst
 */
public E removeLast() {
    final Node l = last;
    if (l == null)
        throw new NoSuchElementException();
    return unlinkLast(l);
}

/**
 * 获取链表头节点信息
 * 如果链表头节点不存在,返回null
 * 否则,返回头节点元素信息
 * 思考:peekFirst和getFirst的不同点
 */
public E peekFirst() {
    final Node f = first;
    return (f == null) ? null : f.item;
}

/**
 * 获取链表尾部节点信息
 * 如果链表尾部节点不存在,返回null
 * 否则,返回头节点元素信息
 * 思考:peekLast和getLast的不同点
 */
public E peekLast() {
    final Node l = last;
    return (l == null) ? null : l.item;
}

/**
 * 获取链表头节点信息
 * 如果链表头节点不存在,抛出异常NoSuchElementException
 * 否则,返回头节点元素信息
 * 思考:peekFirst和getFirst的不同点
 */
public E getFirst() {
    final Node f = first;
    if (f == null)
        throw new NoSuchElementException();
    return f.item;
}

/**
 * 获取链表尾部节点信息
 * 如果链表尾部节点不存在,抛出异常NoSuchElementException
 * 否则,返回头节点元素信息
 * 思考:peekLast和getLast的不同点
 */
public E getLast() {
    final Node l = last;
    if (l == null)
        throw new NoSuchElementException();
    return l.item;
}

/**
 * 移除第一个匹配指定元素o的节点
 * 如果没有元素匹配,返回false
 */
public boolean removeFirstOccurrence(Object o) {
    return remove(o);
}

/**
 * 移除最后一个匹配指定元素o的节点
 * 
 * 思考:第一个和最后一个匹配指定元素o的算法不同点
 * 答:移除第一个匹配是从链表头节点开始遍历匹配的第一个节点
 *    移除最后一个匹配是从链表尾部节点开始遍历匹配的第一个节点
 * 
 * 如果没有元素匹配,返回false
 */
public boolean removeLastOccurrence(Object o) {
    if (o == null) {
        for (Node x = last; x != null; x = x.prev) {
            if (x.item == null) {
                unlink(x);
                return true;
            }
        }
    } else {
        for (Node x = last; x != null; x = x.prev) {
            if (o.equals(x.item)) {
                unlink(x);
                return true;
            }
        }
    }
    return false;
}

LinkedList对FIFO (First-In-First-Out)的支持

FIFO先入先出队列

  • add(e),addLast(e),offer(e), offerLast(e)
  • remove(), removeFirst(),poll(), pollFirst()
  • element(), getFirst(),peek(), peekFirst()
public boolean add(E e) {
    linkLast(e);
    return true;
}

public boolean offer(E e) {
    return add(e);
}

public E remove() {
    return removeFirst();
}

public E poll() {
    final Node f = first;
    return (f == null) ? null : unlinkFirst(f);
}

public E element() {
    return getFirst();
}

public E peek() {
    final Node f = first;
    return (f == null) ? null : f.item;
}

LinkedList对LIFO (Last-In-First-Out)的支持

LIFO后入先出栈操作

  • push(e),addFirst(e)
  • pop(),removeFirst()
  • peek(),peekFirst()
public void push(E e) {
    addFirst(e);
}
public E pop() {
    return removeFirst();
}

感谢大家的支持!!!

你可能感兴趣的:(LinkedList源码详解)