LinkedList源码详解

1. LinkedList概述

上大学那会,链表是我们接触最多的数组结构之一,那会我们常用的是单向链表。Java的LinkedList实现了链表这一数据结构,并且实现的是双向链表,LinkedList还提供了双端队列的操作。LinkedList可以存储任何元素,包括null。

和ArrayList不同的是,LinkedList是一个”sequential list”,即LinkedList是一个”顺序存取”的数据结构,而ArrayList是一个”random list”,即ArrayList是一个”随机存取”的数据结构。所谓”seqnential list“,表示该集合只能从前往后或者从后往前遍历存取元素,而不能根据索引立马定位到元素,表现了定位元素的顺序性。相反,”random list”,表示该集合不需要从前往后或者从后往前遍历存取元素,而是可以根据索引立马定位到元素,表现了定位元素的”随机性“。LinkedList继承了AbstractSequentialList正是对”随机存取的体现“。

先来看下LinkedList的类图:

这里写图片描述

LinkedList实现了List、Deque、Cloneable和Serializable接口,其中Deque是双端队列,因此我们可以将LinkedList当作双端队列使用。

2. 链表节点Node

LinkedList作为一个双端链表,链表节点是通过内部静态类Node实现的,看下Node的源码:

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

Node的代码很简单,它就是一个双向链表的节点表示。

3. 构造函数

LinkedList有两个构造函数,一个默认构造函数,另一个带有Collection参数的构造函数。

//元素个数
transient int size = 0;

//指向链表第一个节点
transient Node<E> first;

//指向链表最后一个节点
transient Node<E> last;

//默认构造函数
public LinkedList() {
}

//通过另一个集合c来初始化该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;
    //succ是索引index处的节点,pred是该节点的前一节点
    Node<E> pred, succ;
    if (index == size) {
      succ = null;
      pred = last;
    } else {
      succ = node(index);
      pred = succ.prev;
    }
    //将集合c的元素一个一个插入到链表中
    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;
    }
    //链接succ和pred
    if (succ == null) {
      last = pred;
    } else {
      pred.next = succ;
      succ.prev = pred;
    }
    //更新链表元素个数
    size += numNew;
    modCount++;
    return true;
}

该方法将集合c的元素插入到链表位置index开始的位置,很简单,直接看源码注释就可以了。

定位指定索引index处节点的方法是node()方法,看下该方法的实现:

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

该方法其实是做了一个优化,如果index小于元素个数的一半,从链表的头往尾方向查找,否则从链表的尾往头方向查找。其它的都很简单,遍历指定个数的节点即可。

接着看下其它常用的方法:

4. 常用方法

先看下add方法:

public boolean add(E e) {
    linkLast(e);
    return true;
}

该方法将指定元素加到链表尾部,调用linkLast将元素链接到链表尾部结点。

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

linkLast将指定的元素链接到链表的尾部。

继续看下在指定的位置插入元素的方法:

//在指定位置index们插入元素element
public void add(int index, E element) {
    //检查插入的位置
    checkPositionIndex(index);
    //如果在末尾插入,调用linkLast将元素链接到末尾即可
    if (index == size)
      linkLast(element);
    else
      //如果不在末尾插入,在index索引之前插入元素element
      linkBefore(element, node(index));
}

继续看下linkBefore方法:

//在节点succ之前插入元素e
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++;
}

linkBefore方法也很简单,在节点succ前面插入元素e。

继续看下Dequeue相关方法:

//该方法取出队列中第一个元素
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 E removeFirst() {
    final Node<E> f = first;
    if (f == null)
      throw new NoSuchElementException();
    return unlinkFirst(f);
}

//删除并返回队列中最后一个元素
public E removeLast() {
    final Node<E> l = last;
    if (l == null)
      throw new NoSuchElementException();
    return unlinkLast(l);
}

//往队列头添加元素e
public void addFirst(E e) {
    linkFirst(e);
}

//往队列尾添加元素e
public void addLast(E e) {
    linkLast(e);
}

//返回队头的元素,但是不删除该元素
public E peek() {
    final Node<E> f = first;
    return (f == null) ? null : f.item;
}

//返回队头的元素,并删除该元素
public E poll() {
    final Node<E> f = first;
    return (f == null) ? null : unlinkFirst(f);
}

这几个方法都很简单,都是链表指针的链接操作,关于队列的操作,以后会专门作介绍,队列也是java一个重要的部分。

总体来说,LinkedList的代码比较简单,本文抽出一些比较有代表性的方法进行了介绍,其他很多方法都是类似的操作,不再说明。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值