JDK类库源码分析系列3--集合类分析(4) List集合3-LinkedList

86 阅读6分钟

一、LinkedList的结构

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

​ 我们知道LinkedList是使用链表实现的线性表,其与ArrayList一样也继承了AbstractList,但其没有实现RandomAccess接口。

二、成员变量

1、size

transient int size = 0;

​ 这个就是用来表明有多少个元素的。

2、first

transient Node<E> first;

​ 这个是表明第一个元素。

3、last

transient Node<E> last;

​ 表明最后一个元素。

​ 整体来说其的成员变量较少。

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

​ 可以看到其是一个静态内部类,其有三个变量。item:表明该节点本身的数据值、next:表示该节点的下一个节点、prev:表明该节点的前一个节点。通过这些信息就像一个链条一样建立了元素之间的关系,同时我们也可以看到LinkedList是一个双向链表。

四、AbstractSequentialList

​ 下面我们来看下AbstractSequentialList这个抽象类中的方法,这个类是用来定义一些按顺序存储的方法。

1、get(int index)

public abstract class AbstractSequentialList<E> extends AbstractList<E> {
public E get(int index) {
    try {
        return listIterator(index).next();
    } catch (NoSuchElementException exc) {
        throw new IndexOutOfBoundsException("Index: "+index);
    }
}

​ 这个方法就是获取这个列表的第index个元素,可以看到其是通过迭代器去遍历获取元素的,ArrayList是直接获取数组对应的index位置的值。

2、set(int index, E element)

public E set(int index, E element) {
    try {
        ListIterator<E> e = listIterator(index);
        E oldVal = e.next();
        e.set(element);
        return oldVal;
    } catch (NoSuchElementException exc) {
        throw new IndexOutOfBoundsException("Index: "+index);
    }
}

​ 这里也是通过迭代器先获取对应位置的值,再将原值返回,其他的add(int index, E element)、remove(int index)都是使用迭代器去操作的。

五、构造函数

1、LinkedList()

public LinkedList() {
}

​ 这个构造方法可以看到其没有进行一些数据的初始化。

2、LinkedList(Collection<? extends E> c)

public LinkedList(Collection<? extends E> c) {
  this();
  addAll(c);
}

​ 这个其的入参是一个集合类,再通过addAll方法将这些数据添加到LinkedList中。

六、方法

1、linkFirst(E e)

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

​ 将参数e链接为第一个元素(添加到最前面)。可以看到这里是先通过元素值e去创建一个Node节点,节点的parent为null,下一个节点是原来的first元素,如果还没有first元素,则在设置这个newNode为first的时候也将其设置为last元素,如果first元素,则将原来的first节点的parent(prev)设置为newNode,再size++&modCount++。

2、linkLast(E e)

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

​ 这个是将元素添加在last,与前面的操作类似。

3、linkBefore(E e, Node<E> succ)

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

​ 这个是将元素添加在另一个节点succ的前面。其的操作时,先获取succ的prev前一个节点pred,再将pred设置为新节点parent节点,而succ设置为newNode的next节。可以看到Linked在进行first、last、明确前置节点或后值节点等插入操作的时候是很方便的,因为不想ArrayList那样,要将目前的这个后面(前面)的内容进行copy移动。

4、unlinkFirst(Node<E> f) & unlinkLast(Node<E> l)

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

​ 这个就是不链接现在first节点(删除现在的first节点)。其是获取现在这个first的next节点,将该节点设置为新的first,再将现在节点的对应值置为null。unlinkLast与其类似

5、unlink(Node<E> x)

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

​ 这个就是不链接(删除)一个具体的节点,可以看到这个也是相对ArrayList要好的(看具体数据的操作情况,老人如是查询,遍历多,还是节点的插入设置多)。其是将目前节点前置&后置节点删除,再将前置节点&后置节点通过next&prev将这两个节点关联。不过可以看到该方法的作用域是default。

6、getFirst() | getLast()

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

​ 获取first( | last)节点。

7、removeFirst() | removeLast()

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

​ 删除目前的first节点,可以看到其是调用的unlinkFirst方法去操作。removeLast方法是remove last节点。

8、addFirst(E e) addLast(E e)

public void addFirst(E e) {
    linkFirst(e);
}

​ 添加到first,也是包装的linkFirst(e)方法。addLast添加到last。

9、indexOf(Object o) | lastIndexOf(Object o)

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

​ 这里就是查找对应节点元素所在的位置。这个就是通过next一直往后获取再通过equals方法判断,但这里需要注意的是,可以将节点的item元素数据设为null,不过这里是查询出第一个。而lastIndexOf方法就是获取最后一个,其是通过prev往前移动。

10、peek() (Deque 队列接口方法)

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

​ 这里是获取first元素的值,但其不会删除第一个元素。

11、poll() (Deque 队列接口方法)

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

​ 这个方法也是获取first元素值,但其会调用unlinkFirst方法去删除first节点。

12、element() (Deque 队列接口方法)

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

​ 获取第一个元素,但整个方法与peek() 方法不同点是这个方法由于调用的是getFirst方法,所以如果first为null其会抛出NoSuchElementException异常,而不是像peek方法直接返回的null。

13、remove() (Deque 队列接口方法)

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

​ 移除first节点,调用removeFirst方法,如果为null抛出NoSuchElementException。

14、offer(E e) (Deque 队列接口方法)

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

​ 将元素添加在last。

15、offerFirst(E e) (Deque 队列接口方法)

public boolean offerFirst(E e) {
    addFirst(e);
    return true;
}

​ 将元素添加在first。

​ ....还有一些其他的Deque接口的方法实现就不再一一表述了,主要是最link相关的方法在包装丰富其功能。

16、toArray()

public Object[] toArray() {
    Object[] result = new Object[size];
    int i = 0;
    for (Node<E> x = first; x != null; x = x.next)
        result[i++] = x.item;
    return result;
}

​ 这个是将List变为数组。根据size创建对应数组,然后在next遍历。

17、add(int index, E element)

public void add(int index, E element) {
    checkPositionIndex(index);

    if (index == size)
        linkLast(element);
    else
        linkBefore(element, node(index));
}

​ 这个方法就是将元素添加在指定位置,可以看到这里判断,如果是添加在last,则直接调用linkLast方法,如果不是,则调用前面梳理的linkBefore方法,这里的关键是获取对应index位置的Node,可以看到其是通过node(index)方法获取。

18、node(int index)

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

​ 可以看到这里获取对应index的node去主要操作也是next遍历,但这里有个点,就是通过右移,如果大于则从后面往前推。如果小于则前面往后推。

六、LinkedList关于迭代器的实现

​ 下面我们来看下在AbstractList中关于Iterator接口的实现(能直接访问到AbstractList的成员变量的值)。

1、ListItr

1)、成员变量

private class ListItr implements ListIterator<E> {
    private Node<E> lastReturned;
    private Node<E> next;
    private int nextIndex;
    private int expectedModCount = modCount;
    ListItr(int index) {
            // assert isPositionIndex(index);
      next = (index == size) ? null : node(index);
      nextIndex = index;
   }

​ 该内部类有4个成员变量。lastReturned:表上次获取时返回的节点、next:表示下次获取的时候返回的节点、nextIndex:表示下个元素的index位置、expectedModCount:一起修改次数,上篇有提过。

2)、一些方法

public boolean hasNext() {
    return nextIndex < size;
}

​ 是否还有下一个元素。

public E next() {
    checkForComodification();
    if (!hasNext())
        throw new NoSuchElementException();

    lastReturned = next;
    next = next.next;
    nextIndex++;
    return lastReturned.item;
}

​ 获取下一个元素,通过这里对这些成员变量的修改操作也明白上面对其的解释。

​ 然后关于这个ListItr的一些其他方法也不再展开了,关于LinkedList的方法就是围着其的Node节点的三个变量的各种操作。