Java集合系列 LinkedList底层源码 万字细致解读(超通俗易懂)

一、概述

Java中LinkedList是一个双向链表实现的集合类,它其中每一个元素都是一个节点,每个节点都包含一个数据元素和一个指向下一个节点的引用,因此LinkedList的元素是通过节点之间的链接来组织的。

双向链表

双向链表是一种常见的数据结构,它由一系列节点组成,每个节点包含两个指针,分别指向前一个节点和后一个节点。双向链表中的节点通常包含三个字段:数据元素、指向前一个节点的指针、指向后一个节点的指针。

在这里插入图片描述

在双向链表中,插入和删除元素的操作效率非常高,因为只需要修改相对应节点的指针即可。因此双向链表非常适合在需要频繁插入、删除的场景下使用。

双向链表中插入节点时:只需要将其前一个节点的指针和后一个节点的指针都指向新节点。
双向链表中删除节点时:只需要修改前一个节点和后一个节点的指针,将其指向链表的下一个节点即可。

类图

在这里插入图片描述

看向上图:LinkedList实现了4个接口和继承1个抽象类

  • List:主要提供添加、删除、修改、遍历等操作
  • Cloneable:支持克隆
  • Serializable:支持序列化
  • Deque:提供双端队列的功能,支持快速在头尾添加和读取元素

LinkedList所实现的接口相比于ArrayList是少一个RandomAccess接口,说明LinkedList是不支持随机访问的,但同时多了一个Deque接口,新增了双端队列的能力。

源码如下:

public class LinkedList<E>
    extends AbstractSequentialList<E>
    implements List<E>, Deque<E>, Cloneable, java.io.Serializable
{
	// .......
}

二、源码解读

注:不同jdk版本的源码会有一定差异,不过大致上是相同的,我使用的是 openjdk version “1.8.0_342”。

成员变量

	// 头节点
	transient Node<E> first;
	
	// 尾节点
	transient Node<E> last;
	
	// 数量
	transient int size = 0;
	
	// 底层实现是 双向链表 
	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;
	   }
	}

看向上述源码:LinkedList的节点实现完全符合双向链表的数据结构要求。

构造函数

	/**
	 * 空参数的构造由于生成一个空链表 first = last = null
	 */
	 public LinkedList() {
	 }
	
	/**
	 * 传入一个集合类,来构造一个包含此集合所有元素的 LinkedList 集合
	 * Collection c 其内部的元素将按顺序作为 LinkedList 节点
	 */
	public LinkedList(Collection<? extends E> c) {
	   this();
	   addAll(c);
	}

相对于ArrayList来说,LinkedList没有类似于指定初始化容量的构造方法。造成这个区别的原因是它们底层的实现方式不一样,LinkedList是基于节点实现的双向链表,每一个添加元素的时候,只需要new一个节点即可

注:addAll(); 后文新增元素会详细介绍

新增元素

新增元素的方法主要有如下4个:

  • public boolean add(E e):在链表节点后顺序添加一个元素(默认是在末尾)
  • public void add(int index, E element):在指定下标位置添加元素
  • public boolean addAll(Collection<? extends E> c):在链表节点后顺序添加此集合 c 的所有元素(默认是在末尾)
  • public boolean addAll(int index, Collection<? extends E> c):在指定下标位置添加此集合 c 的所有元素

第一种public boolean add(E e)

在链表节点后顺序添加一个元素(默认是在末尾)

    public boolean add(E e) {
    	// 在链表的末尾插入指定元素
        linkLast(e);
        // 添加成功 返回true
        return true;
    }

继续向下解读:

	// 在链表的末尾插入指定元素
	void linkLast(E e) {
		// 保存之前的last节点
	   final Node<E> l = last;
	   // 创建一个新的节点,并将新节点prev指针指向之前的last节点
	   final Node<E> newNode = new Node<>(l, e, null);
	   // 将last索引指向新的last节点
	   last = newNode;
	   // 如果之前的last节点为空,则表示此链表为空链表。那么将first索引指向新节点
	   // 此时 first last 指针都会指向此新节点
	   if (l == null)
	       first = newNode;
	   // 否则,将之前的last节点的next指针指向新节点
	   else
	       l.next = newNode;
	   // 数量+1
	   size++;
	   // 这是一个自增操作,增加了列表的修改计数。这个计数是用来跟踪列表被修改了多少次,这对于一些并发控制是非常有用的。(可以暂时不用关注)
	   modCount++;
	}

一句话概括:首先是根据此元素创建一个新节点;其次将链表的last指针指向此新节点(此处两种情况)。情况1、如果之前的last节点为空,则表示此链表为空链表。那么将first索引指向新节点,此时 first last 指针都会指向此新节点。情况2、之前的last节点不为空,,则表示此链表有元素且不为空,那么将之前的last节点的next指针指向新节点。

第二种public void add(int index, E element)

在指定下标位置添加元素

    public void add(int index, E element) {
    	// 检查index的合法性 
    	// index必须在此范围内(index >= 0 && index <= size)
        checkPositionIndex(index);
		// 如果指定下标位置index == size,则表示在末尾添加此元素
        if (index == size)
        	// linkLast上文已解读,不赘述
            linkLast(element)
		// 在index处节点插入node
        else
        	// node(index) 获取index位置的node节点
            linkBefore(element, node(index));
    }

继续向下解读

	// 找到并返回指定index处的节点
    Node<E> node(int index) {
        // assert isElementIndex(index);

		// 如果 index < size的一半 则从头开始遍历(类似于折半查找)
        if (index < (size >> 1)) {
        	// 先拿到first 也就是第一个节点
            Node<E> x = first;
            // 再通过循环去 一个一个向后拿节点 最后遍历到index的位置 找到index位置的节点
            for (int i = 0; i < index; i++)
                x = x.next;
            return x;
        } 
		// 如果 index > size的一半 则从末尾开始遍历
		else {
			// 先拿到last也就是最后节点
            Node<E> x = last;
            // 再通过循环去 一个一个向前拿节点 最后遍历到index的位置 找到index位置的节点
            for (int i = size - 1; i > index; i--)
                x = x.prev;
            return x;
        }
    }

继续向下解读

    void linkBefore(E e, Node<E> succ) {
        // assert succ != null;

		// 通过node(index)方法找到了index处的节点,在此方法中也就是succ
		// 拿到succ的前一个节点
        final Node<E> pred = succ.prev;
        // 创建一个新节点 也就是我们想要插入的节点,并关联前后节点(前节点:succ的前一个节点;后节点:也就是succ)
        final Node<E> newNode = new Node<>(pred, e, succ);
        // 再将succ的前节点关联上此新节点
        succ.prev = newNode;
        // 如果succ前节点为null,则代表succ则是first头结点,也就是第一个节点
        if (pred == null)
        	// first指向newNode作为新的首节点
            first = newNode;
        else
        	// 如果不为空,将succ前节点的后节点关联上此新节点
            pred.next = newNode;
        size++;
        modCount++;
    }

在这里插入图片描述

一句话概括:首先校验index的合法性是否在index >= 0 && index <= size范围内,否则抛出异常。其次判断此index是否等于size如果是则表示是在末尾插入,直接调用linkLast方法;如果不是则找到index下标处的节点,将新节点插入进去。

第三种public boolean addAll(Collection<? extends E> c)

在链表节点后顺序添加此集合 c 的所有元素(默认是在末尾)

此方法是直接调用第四种public boolean addAll(int index, Collection<? extends E> c)方法,此处不解读,请继续向下阅读。

    public boolean addAll(Collection<? extends E> c) {
    	// 此处是调用`public boolean addAll(int index, Collection<? extends E> c)`方法
    	// 默认是在末尾,因此index为size
        return addAll(size, c);
    }

第四种public boolean addAll(int index, Collection<? extends E> c)

在指定下标位置添加此集合 c 的所有元素

    public boolean addAll(int index, Collection<? extends E> c) {
    	// 校验下标合法性 上述已解读 此处不再赘述
        checkPositionIndex(index);
        
		// 将集合c转为数组 以便遍历集合中的元素
        Object[] a = c.toArray();
        // 拿到此集合的长度并校验是否为0,如果为0表示集合中没有元素,直接返回false
        int numNew = a.length;
        if (numNew == 0)
            return false;

		// 定义变量:index的前一个节点pred、后一个节点的succ
        Node<E> pred, succ;
        // 如果指定下标位置index == size,则表示在末尾添加此元素
        if (index == size) {
        	// succ 直接赋为null
            succ = null;
            // pred 则是应该是当前此链表的最后一个节点
            pred = last;
        } 
		// 在index处节点插入node
		else {
	        // 拿到当前下标的节点
            succ = node(index);
            pred = succ.prev;
        }
		// 遍历数组 依次插入
        for (Object o : a) {
            @SuppressWarnings("unchecked") 
            E e = (E) o;
            // 此处同void linkBefore(E e, Node<E> succ)方法大同小异
            // 创建新节点 并关联上一个节点
            Node<E> newNode = new Node<>(pred, e, null);
            // 如果succ前节点为null,则代表succ则是first头结点,也就是第一个节点 if (pred == null)
            if (pred == null)
            	// first指向newNode作为新的首节点
                first = newNode;
            else
            	// 如果不为空,将succ前节点的后节点关联上此新节点
                pred.next = newNode;
            // 最后将新节点赋值给上一个节点,进行下一次循环
            pred = newNode;
        }

		// succ为index后的一个节点,如果succ为空,说明插入的位置就是在最后面,则直接设置last为pred
        if (succ == null) {
            last = pred;
        } else {
            pred.next = succ;
            succ.prev = pred;
        }

        size += numNew;
        modCount++;
        return true;
    }

一句话概括:首先校验index的合法性是否在index >= 0 && index <= size范围内,否则抛出异常。其次遍历集合依次插入节点。

删除元素

删除元素的方法主要有如下4个:

  • public E remove(int index):根据下标删除指定元素并返回此元素
  • public boolean remove(Object o):删除首个出现的指定元素
  • public E removeFirst():删除列表中第一个元素并返回此元素
  • public E removeLast():删除列表中最后一个元素并返回此元素

第一种public E remove(int index)

根据下标删除指定元素并返回此元素

    public E remove(int index) {
    	// 检查index的合法性 index必须在此范围内(index >= 0 && index <= size)
        checkElementIndex(index);
        // node(index) 拿到当前下标的节点 上文有解读 此处不赘述
        return unlink(node(index));
    }

继续向下解读:

	// 解除关联
    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;

		// 如果前一个节点是null 表示此节点x是头结点
        if (prev == null) {
        	// 那么将first 指向后一个节点
            first = next;
        } else {
        	// 将前一个节点的next 指向此节点x的下一个节点
            prev.next = next;
            // 将此节点的前一个节点置为null
            x.prev = null;
        }
		
		// 如果下一个节点是null 表示此节点x是最后一个节点
        if (next == null) {
        	// 那么将last 指向此节点x的上一个节点
            last = prev;
        } else {
        	// 将下一个节点的prev 指向此节点x的上一个节点
            next.prev = prev;
            // 将此节点x的next置为空(为了垃圾回收)
            x.next = null;
        }
		// 将此节点x的元素置为空(为了垃圾回收)
        x.item = null;
        size--;
        modCount++;
        return element;
    }

第二种public boolean remove(Object o)

删除首个出现的指定元素

    public boolean remove(Object o) {
    	// 说明 LinkedList是支持存null元素的
        if (o == null) {
        	// 找到为 null 的元素 并调用unlink解除关联。
            for (Node<E> x = first; x != null; x = x.next) {
                if (x.item == null) {
                    unlink(x);
                    return true;
                }
            }
        } else {
            for (Node<E> x = first; x != null; x = x.next) {
            	// 找到为 x.item 的元素 并调用unlink解除关联。
                if (o.equals(x.item)) {
                    unlink(x);
                    return true;
                }
            }
        }
        return false;
    }

第三种public E removeFirst()

删除列表中第一个元素并返回此元素

源码如下 非常简单 不多赘述 自行阅读

    public E removeFirst() {
        final Node<E> f = first;
        if (f == null)
            throw new NoSuchElementException();
        return unlinkFirst(f);
    }

继续向下:

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

第四种public E removeLast()

删除列表中最后一个元素并返回此元素

源码如下 非常简单 不多赘述 自行阅读

    public E removeLast() {
        final Node<E> l = last;
        if (l == null)
            throw new NoSuchElementException();
        return unlinkLast(l);
    }

继续向下:

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

查找元素

查找元素的方法主要有如下2个:

  • public int indexOf(Object o):查找首个为指定元素的位置(如不包含,返回-1;如包含,返回此元素下标)
  • public int lastIndexOf(Object o):删除首个出现的指定元素(如不包含,返回-1;如包含,返回此元素下标)

两个方法的源码都很简单,两者的差别在于一个是从头遍历,一个是从尾遍历(因为LinkedList底层实现是双向链表,所以很容易实现这点)。

第一种public int indexOf(Object o)

查找首个为指定元素的位置(如不包含,返回-1;如包含,返回此元素下标)

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

第二种public int lastIndexOf(Object o)

删除首个出现的指定元素(如不包含,返回-1;如包含,返回此元素下标)

    public int lastIndexOf(Object o) {
        int index = size;
        if (o == null) {
            for (Node<E> x = last; x != null; x = x.prev) {
                index--;
                if (x.item == null)
                    return index;
            }
        } else {
            for (Node<E> x = last; x != null; x = x.prev) {
                index--;
                if (o.equals(x.item))
                    return index;
            }
        }
        return -1;
    }

其他方法此处不再一一解读,如以上源码都理解清楚的同学,其他方法的代码理解起来很简单。

三、Deque双端队列

从上文类图中所能看出,LinkedList实现了Deque双端队列的接口。

Deque接口继承Queue接口,因此LinkedList拥有队列以及双端队列的特性。并且我们知道Java中的Stack是一个栈结构,不过栈并没有单独的Stack接口,但是相关栈的方法都是放在了Deque中,因此LinkedList同时也拥有栈的特性。

换句话说:实现了这个Deque双端队列的类,既可以作为栈使用也可以作为队列使用还可以作为双端队列使用。

双端队列相关方法

/**
  * =======================================双端队列==============================
  */

/**
  * 往双端队列 前端添加元素
  */
@Override
public void addFirst(E e) {
    linkFirst(e);
}

/**
  * 双端队列 末尾添加元素
  */
@Override
public void addLast(E e) {
    linkLast(e);
}

/**
  * 双端队列前端添加元素
  */
@Override
public boolean offerFirst(E e) {
    addFirst(e);
    return true;
}

/**
  * 双端队列 末尾端添加元素
  */
@Override
public boolean offerLast(E e) {
    addLast(e);
    return true;
}

/**
  * 双端队列 前端移除首个元素
  */
@Override
public E removeFirst() {
    final Node<E> f = first;
    if (f == null)
        throw new NoSuchElementException();
    return unlinkFirst(f);
}

/**
  * 双端队列 末尾端 移除首个元素
  */
@Override
public E removeLast() {
    final Node<E> l = last;
    if (l == null)
        throw new NoSuchElementException();
    return unlinkLast(l);
}

/**
  * 双端队列  前端 弹出首个元素
  */
@Override
public E pollFirst() {
    final Node<E> f = first;
    return (f == null) ? null : unlinkFirst(f);
}

/**
  * 双端队列  末尾端 弹出首个元素
  */
@Override
public E pollLast() {
    final Node<E> l = last;
    return (l == null) ? null : unlinkLast(l);
}

/**
  * 双端队列 获取首端 首个元素
  */
@Override
public E getFirst() {
    final Node<E> f = first;
    if (f == null)
        throw new NoSuchElementException();
    return f.item;
}

/**
  * 双端队列 获取末尾端 首个元素
  */
@Override
public E getLast() {
    final Node<E> l = last;
    if (l == null)
        throw new NoSuchElementException();
    return l.item;
}

@Override
public E peekFirst() {
    final Node<E> f = first;
    return (f == null) ? null : f.item;
}

@Override
public E peekLast() {
    final Node<E> l = last;
    return (l == null) ? null : l.item;
}


@Override
public boolean removeFirstOccurrence(Object o) {
    return remove(o);
}

@Override
public boolean removeLastOccurrence(Object o) {
    if (o == null) {
        for (Node<E> x = last; x != null; x = x.prev) {
            if (x.item == null) {
                unlink(x);
                return true;
            }
        }
    } else {
        for (Node<E> x = last; x != null; x = x.prev) {
            if (o.equals(x.item)) {
                unlink(x);
                return true;
            }
        }
    }
    return false;
}

队列相关方法

/**
  * ==================================队列方法========================================
  */

@Override
public boolean add(E e) {
    // 顺序添加单个元素到链表的末尾
    linkLast(e);
    return true;
}

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

@Override
public E remove() {
    // 移除第一个元素,Deque接口的方法
    return removeFirst();
}

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

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

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

栈相关方法

/**
  * ==================================队列方法========================================
  */

@Override
public boolean add(E e) {
    // 顺序添加单个元素到链表的末尾
    linkLast(e);
    return true;
}

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

@Override
public E remove() {
    // 移除第一个元素,Deque接口的方法
    return removeFirst();
}

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

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

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

以上源码大致浏览一遍即可,都比较简单。

四、遍历方式

第一种遍历方式:for循环

    LinkedList<Integer> list = new LinkedList<>();
    list.add(1);
    list.add(2);
    list.add(3);
    list.add(4);
    list.add(5);

    for (int i = 0; i < list.size(); ++i) {
        System.out.print(list.get(i));
    }
    public E get(int index) {
        checkElementIndex(index);
        return node(index).item;
    }

底层是调用node(index),上文对此方法有解读,采用类似于折半查找的方式。

第二种遍历方式:foreach循环在这里插入代码片

    for (Integer x : list) {
        System.out.print(x + " ");
    }

第三种遍历方式: 迭代器遍历

    //迭代器遍历--正向输出
    ListIterator<Integer> it = list.listIterator();
    while (it.hasNext()) {
        System.out.print(it.next() + " ");
    }

    //迭代器遍历--反向输出
    ListIterator<Integer> rit = list.listIterator(list.size());
    while (rit.hasPrevious()) {
        System.out.print(rit.previous() + " ");
    }

五、总结

本文主要解读了LinkedList属性、构造函数、新增元素、删除元素的源码。其他方法的源码大同小异,感兴趣的同学自行阅读。

属性

LinkedList只有 3 个属性,分别是代表头节点的 first、代表尾节点的 last以及表示数量的 size

构造方法

LinkedList有2个构造方法

  • 无参构造public LinkedList()
  • 构造包含此集合c所有元素的 LinkedList 集合public LinkedList(Collection<? extends E> c)

相对于ArrayList来说,LinkedList没有类似于制定初始化容量的构造方法,因为底层是基于双向链表,每添加一个元素的时候只需要new一个节点即可。

添加、删除元素方法

添加、删除元素方法都是一些对于链表操作,且注意LinkedList是支持存储 null的。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值