2061 2017-09-20 2020-06-25

前言:在看完常用的ArrayList后,我们来看下LinkedList的实现。

一、类结构

1、概述

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

    transient int size = 0;
    // 头节点
    transient Node<E> first;
    // 尾节点
    transient Node<E> last;
}

常规的类结构声明,与ArrayList相比,LinkedList在实现上有所不同。其中需要注意的

  • AbstractSequentialList:是一个链式列表存储的规范,尽大程度的减少链式存储结构实现List接口所需的工作。
  • Deque:double ended queue的缩写,Deque(英['dek] )是一个双端队列,父类是Queue(英[kju:])。这个需要重点看下。

2、内部类

既然是链式存储,那么链的节点是必不可少的,如下

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

二、Deque

1、定义

一个线性 collection,支持在两端插入和移除元素。名称 deque 是“double ended queue(双端队列)”的缩写,通常读为“deck”。大多数 Deque 实现对于它们能够包含的元素数没有固定限制,但此接口既支持有容量限制的双端队列,也支持没有固定大小限制的双端队列。

public interface Deque<E> extends Queue<E> {
    // 失败时抛出异常,添加元素
    /**
     * Inserts the specified element at the front of this deque if it is
     * possible to do so immediately without violating capacity restrictions,
     * throwing an {@code IllegalStateException} if no space is currently
     * available.  When using a capacity-restricted deque, it is generally
     * preferable to use method {@link #offerFirst}.
     *
     * @param e the element to add
     * @throws IllegalStateException if the element cannot be added at this
     *         time due to capacity restrictions
     * @throws ClassCastException if the class of the specified element
     *         prevents it from being added to this deque
     * @throws NullPointerException if the specified element is null and this
     *         deque does not permit null elements
     * @throws IllegalArgumentException if some property of the specified
     *         element prevents it from being added to this deque
     */    
    void addFirst(E e);
    void addLast(E e);
    // 失败时返回特殊值,添加元素,下同
    /**
     * Inserts the specified element at the front of this deque unless it would
     * violate capacity restrictions.  When using a capacity-restricted deque,
     * this method is generally preferable to the {@link #addFirst} method,
     * which can fail to insert an element only by throwing an exception.
     *
     * @param e the element to add
     * @return {@code true} if the element was added to this deque, else
     *         {@code false}
     * @throws ClassCastException if the class of the specified element
     *         prevents it from being added to this deque
     * @throws NullPointerException if the specified element is null and this
     *         deque does not permit null elements
     * @throws IllegalArgumentException if some property of the specified
     *         element prevents it from being added to this deque
     */    
    boolean offerFirst(E e);
    boolean offerLast(E e);
    E removeFirst();
    E removeLast();
    E pollFirst();
    E pollLast();
    E getFirst();
    E getLast();
    E peekFirst();
    E peekLast();
    // 移除第一次出现的元素,从first遍历
    boolean removeFirstOccurrence(Object o);
    // 移除最后一次出现的元素,从last遍历
    boolean removeLastOccurrence(Object o);

    // *** Queue methods ***
    // 失败时抛出异常,添加元素
    boolean add(E e);
    // 返回特殊值
    boolean offer(E e);
    // 失败时抛出异常,删除元素并返回
    E remove();
    E poll();
    // 失败时抛出异常,返回首元素,不删除
    E element();
    E peek();

    // *** Stack methods ***
    //入栈
    void push(E e);
    // 出栈
    E pop();
    
    // *** Collection methods ***
    boolean remove(Object o);
    boolean contains(Object o);
    public int size();
    Iterator<E> iterator();
    Iterator<E> descendingIterator();
}

// Queue接口与List、Set接口是平级
public interface Queue<E> extends Collection<E> {
    //方法略
}

此接口定义在双端队列两端访问元素的方法。提供插入、移除和检查元素的方法。每种方法都存在两种形式:

  • 一种形式在操作失败时抛出异常(如因容量限制插入失败,size为0去取值)。
  • 另一种形式返回一个特殊值(null 或 false,具体取决于操作)。

插入操作的后一种形式是专为使用有容量限制的 Deque 实现设计的;在大多数实现中,插入操作不能失败。

2、可用作于队列

此接口扩展了 Queue 接口。在将双端队列用作队列时,将得到 FIFO(先进先出)行为。将元素添加到双端队列的末尾,从双端队列的开头移除元素。从 Queue 接口继承的方法完全等效于 Deque 方法,如下表所示:

Queue 方法等效 Deque 方法
add(e)addLast(e)
offer(e)offerLast(e)
remove()removeFirst()
poll()pollFirst()
element()getFirst()
peek()peekFirst()

3、可用作于栈

Deque实现了Queue,自然而然是一个队列。但双端队列也可用作 LIFO(后进先出)堆栈应优先使用此接口而不是遗留 Stack 类。在将双端队列用作堆栈时,元素被推入双端队列的开头并从双端队列开头弹出。堆栈方法完全等效于 Deque 方法,如下表所示

堆栈方法等效 Deque 方法
push(e)addFirst(e)
pop()removeFirst()
peek()peekFirst()

补充:Queue 实现通常不允许插入 null 元素,尽管某些实现(如 LinkedList)并不禁止插入 null。即使在允许 null 的实现中,也不应该将 null 插入到 Queue 中,因为 null 也用作 poll 方法的一个特殊返回值,表明队列不包含元素

4、可用作于列表

LinkedList实现了List接口,自然拥有List的特性。关于List的特性,可参考ArrayList,这篇更多讨论的是除List接口之外的更多特性。

三、方法说明

结合前文所述,我们看下LinkedList对于方法addFirst(e)addLast(e)removeFirst()、**peekFirst()**的具体实现(抽取队列和栈的各自特点)。不过在此之前,我们先来看下构造方法。

1、构造方法

两个构造方法如下

public LinkedList() {
}

public LinkedList(Collection<? extends E> c) {
    this();
    // 遍历Collection,将其添加进LinkedList
    addAll(c);
}

2、addFirst(e)

都是链表的常规操作。

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

private void linkFirst(E e) {
    // final表示不可更改,这里的final可有可无
    final Node<E> f = first;
    // 构造方法为 Node(Node<E> prev, E element, Node<E> next)
    final Node<E> newNode = new Node<>(null, e, f);
    first = newNode;
    if (f == null)
        last = newNode;
    else
        f.prev = newNode;
    size++;
    // 修改次数增1(继承自AbstractList),标记并发
    modCount++;
}
假设1:链表为空,即first = null, last = null,f = null
那么方法执行过后,first = newNode, last = newNode

假设2:链表先前存在1->2->3,现插入0
那么方法执行过后,first = 0, first.pre = 0

3、addLast(e)

public void addLast(E e) {
    linkLast(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++;
}

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

5、peekFirst()

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

其他方法不再赘述。

四、面试题

LinkedList设计的很巧妙,这里不拍马屁了。下面针对网上的面试题,再次深入理解下队列和栈。

1、俩栈实现队列

先看图

俩队列实现栈

这里的难点在于,是否两个栈是否分工明确。即把一个栈当作输入栈、一个栈当作输出栈时,题目迎刃而解。实现代码如下

public class MyQueue<E> {

    private Deque<E> input;

    private Deque<E> output;

    public MyQueue() {
        input = new LinkedList<>();
        output = new LinkedList<>();
    }

    public void offer(E e) {
        input.push(e);
    }

    public E poll() {
        if (output.size() != 0) {
            return output.pop();
        }
        while (input.size() != 0) {
            E e = input.pop();
            output.push(e);
        }
        return output.pop();
    }


    public void pollAll() {
        System.out.print("队列输出顺序为:");
        while (input.size() != 0 || output.size() != 0) {
            System.out.print(poll() + " ");
        }
        System.out.println();
    }

    public static void main(String[] args) {
        MyQueue<Integer> queue = new MyQueue<>();
        queue.offer(1);
        queue.offer(2);
        queue.offer(3);
        System.out.println(queue.poll());
        queue.pollAll();

        queue.offer(4);
        queue.offer(5);
        queue.offer(6);
        queue.pollAll();
    }
}
输出结果如下
1
队列输出顺序为:2 3 
队列输出顺序为:4 5 6 

2、俩队列实现栈

先看图

俩队列实现栈

这里的难点在于理解队列最后一个元素,通过落下队列的最后一个元素从而实现栈结构。实现代码如下

public class MyStack<E> {

    private Queue<E> queue1;

    private Queue<E> queue2;

    public MyStack() {
        queue1 = new LinkedList<>();
        queue2 = new LinkedList<>();
    }

    public void push(E e) {
        if (queue2.size() != 0) {
            queue2.offer(e);
        } else {
            queue1.offer(e);
        }
    }

    public E pop() {
        if (queue1.size() > 0) {
            while (queue1.size() > 1) {
                E e = queue1.poll();
                queue2.offer(e);
            }
            return queue1.poll();
        }
        while (queue2.size() > 1) {
            E e = queue2.poll();
            queue1.offer(e);
        }
        return queue2.poll();
    }

    public void popAll() {
        System.out.print("栈输出顺序为:");
        while (queue1.size() != 0 || queue2.size() != 0) {
            System.out.print(pop() + " ");
        }
        System.out.println();
    }

    public static void main(String[] args) {
        MyStack<Integer> stack = new MyStack<>();
        stack.push(1);
        stack.push(2);
        stack.push(3);
        stack.push(4);
        stack.popAll();

        stack.push(4);
        stack.push(5);
        stack.push(6);
        stack.popAll();
    }
}
输出结果如下
栈输出顺序为:4 3 2 1 
栈输出顺序为:6 5 4 ;
总访问次数: 362次, 一般般帅 创建于 2017-09-20, 最后更新于 2020-06-25

进大厂! 欢迎关注微信公众号,第一时间掌握最新动态!