【源碼閱讀】Java集合之三 - ArrayDeque源碼深度解讀

Java 源碼閱讀的第一步是Collection框架源碼,這也是面試基礎中的基礎; 針對Collection的源碼閱讀寫一個系列的文章,本文是第三篇ArrayDeque。 ---@pdaijava

JDK版本

JDK 1.8.0_110程序員

概述總結

  • ArrayDeque是可變長Array, 實現了Deque接口;Deque是"double ended queue", 表示雙向的隊列,英文讀做"deck";
  • Deque 繼承自 Queue接口,除了支持Queue的方法以外,還支持insert, removeexamine操做,因爲Deque是雙向的,因此能夠對隊列的頭和尾都進行操做,它同時也支持兩組格式,一組是拋出異常的實現;另一組是返回值的實現(沒有則返回null);
  • Java裏有一個叫作Stack的類,卻沒有叫作Queue的類(它是個接口名字)。當須要使用棧時,Java已不推薦使用Stack,而是推薦使用更高效的ArrayDeque;既然Queue只是一個接口,當須要使用隊列時也就首選ArrayDeque了(次選是LinkedList)。
  • ArrayDeque底層經過數組實現,爲了知足能夠同時在數組兩端插入或刪除元素的需求,該數組還必須是循環的,即循環數組(circular array),也就是說數組的任何一點均可能被看做起點或者終點;
  • ArrayDeque是非線程安全的(not thread-safe),當多個線程同時使用的時候,須要程序員手動同步;另外,該容器不容許放入null元素。
  • ArrayDeque也採用了快速失敗的機制,經過記錄modCount參數來實現。在面對併發的修改時,迭代器很快就會徹底失敗,而不是冒着在未來某個不肯定時間發生任意不肯定行爲的風險;
  • ArrayDeque底層的數據類型是Object[], Java泛型只是編譯器提供的語法糖,因此這裏的數組是一個Object數組,以便可以容納任何類型的對象;

類關係圖

ArrayDeque實現的接口和繼承的類以下:面試

public class ArrayDeque<E> extends AbstractCollection<E>
                           implements Deque<E>, Cloneable, Serializable
{
}

Collection接口

Collection接口操做分爲以下幾類:數組

  • Query Operations
    • int size();
    • boolean isEmpty();
    • boolean contains(Object o);
    • Iterator iterator();
    • Object[] toArray();
    • T[] toArray(T[] a);
  • Modification Operations
    • boolean add(E e);
    • boolean remove(Object o);
  • Bulk Operations
    • boolean containsAll(Collection<?> c);
    • boolean addAll(Collection<? extends E> c);
    • boolean removeAll(Collection<?> c);
    • default boolean removeIf(Predicate<? super E> filter)
    • boolean retainAll(Collection<?> c);
    • void clear();
  • Comparison and hashing
    • boolean equals(Object o);
    • int hashCode();
  • others
    • default Spliterator spliterator() {}
    • default Stream stream() {}
    • default Stream parallelStream() {}

Queue

Queue接口繼承自Collection接口,除了最基本的Collection的方法以外,它還支持額外的insertion, extractioninspection操做。這裏有兩組格式,共6個方法,一組是拋出異常的實現;另一組是返回值的實現(沒有則返回null)。安全

Throws exception Returns special value
Insert add(e) offer(e)
Remove remove() poll()
Examine element() peek()

Deque

Deque 繼承自 Queue接口,除了支持Queue的方法以外,還支持insert, removeexamine操做,因爲Deque是雙向的,因此能夠對隊列的頭和尾都進行操做,它同時也支持兩組格式,一組是拋出異常的實現;另一組是返回值的實現(沒有則返回null)。共12個方法以下:數據結構

First Element (Head) Last Element (Tail)
Throws exception Special value Throws exception
Insert addFirst(e) offerFirst(e) addLast(e)
Remove removeFirst() pollFirst() removeLast()
Examine getFirst() peekFirst() getLast()

當把Deque當作FIFO的queue來使用時,元素是從deque的尾部添加,從頭部進行刪除的; 因此deque的部分方法是和queue是等同的。具體以下:併發

Queue Method Equivalent Deque Method
add(e) addLast(e)
offer(e) offerLast(e)
remove() removeFirst()
poll() pollFirst()
element() getFirst()
peek() peekFirst()

Deque的含義是「double ended queue」,即雙端隊列,它既能夠看成棧使用,也能夠看成隊列使用。app

下表列出了DequeQueue相對應的接口:框架

Queue Method Equivalent Deque Method 說明
add(e) addLast(e) 向隊尾插入元素,失敗則拋出異常
offer(e) offerLast(e) 向隊尾插入元素,失敗則返回false
remove() removeFirst() 獲取並刪除隊首元素,失敗則拋出異常
poll() pollFirst() 獲取並刪除隊首元素,失敗則返回null
element() getFirst() 獲取但不刪除隊首元素,失敗則拋出異常
peek() peekFirst() 獲取但不刪除隊首元素,失敗則返回null

下表列出了DequeStack對應的接口:ide

Stack Method Equivalent Deque Method 說明
push(e) addFirst(e) 向棧頂插入元素,失敗則拋出異常
offerFirst(e) 向棧頂插入元素,失敗則返回false
pop() removeFirst() 獲取並刪除棧頂元素,失敗則拋出異常
pollFirst() 獲取並刪除棧頂元素,失敗則返回null
peek() peekFirst() 獲取但不刪除棧頂元素,失敗則拋出異常
peekFirst() 獲取但不刪除棧頂元素,失敗則返回null

上面兩個表共定義了Deque的12個接口。添加,刪除,取值都有兩套接口,它們功能相同,區別是對失敗狀況的處理不一樣。一套接口遇到失敗就會拋出異常,另外一套遇到失敗會返回特殊值(falsenull。除非某種實現對容量有限制,大多數狀況下,添加操做是不會失敗的。雖然Deque的接口有12個之多,但無非就是對容器的兩端進行操做,或添加,或刪除,或查看。明白了這一點講解起來就會很是簡單。

類的實現

底層數據結構

從名字能夠看出ArrayDeque底層經過數組實現,爲了知足能夠同時在數組兩端插入或刪除元素的需求,該數組還必須是循環的,即循環數組(circular array),也就是說數組的任何一點均可能被看做起點或者終點。ArrayDeque是非線程安全的(not thread-safe),當多個線程同時使用的時候,須要程序員手動同步;另外,該容器不容許放入null元素。

/**
     * The array in which the elements of the deque are stored.
     * The capacity of the deque is the length of this array, which is
     * always a power of two. The array is never allowed to become
     * full, except transiently within an addX method where it is
     * resized (see doubleCapacity) immediately upon becoming full,
     * thus avoiding head and tail wrapping around to equal each
     * other.  We also guarantee that all array cells not holding
     * deque elements are always null.
     */
    transient Object[] elements; // non-private to simplify nested class access

    /**
     * The index of the element at the head of the deque (which is the
     * element that would be removed by remove() or pop()); or an
     * arbitrary number equal to tail if the deque is empty.
     */
    transient int head;

    /**
     * The index at which the next element would be added to the tail
     * of the deque (via addLast(E), add(E), or push(E)).
     */
    transient int tail;

    /**
     * The minimum capacity that we'll use for a newly created deque.
     * Must be a power of 2.
     */
    private static final int MIN_INITIAL_CAPACITY = 8;

head指向首端第一個有效元素,tail指向尾端第一個能夠插入元素的空位。由於是循環數組,因此head不必定總等於0,tail也不必定老是比head大。

基礎數據結構方法

下面再說說擴容函數doubleCapacity(),其邏輯是申請一個更大的數組(原數組的兩倍),而後將原數組複製過去。

/**
     * Allocates empty array to hold the given number of elements.
     *
     * @param numElements  the number of elements to hold
     */
    private void allocateElements(int numElements) {
        int initialCapacity = MIN_INITIAL_CAPACITY;
        // Find the best power of two to hold elements.
        // Tests "<=" because arrays aren't kept full.
        if (numElements >= initialCapacity) {
            initialCapacity = numElements;
            initialCapacity |= (initialCapacity >>>  1);
            initialCapacity |= (initialCapacity >>>  2);
            initialCapacity |= (initialCapacity >>>  4);
            initialCapacity |= (initialCapacity >>>  8);
            initialCapacity |= (initialCapacity >>> 16);
            initialCapacity++;

            if (initialCapacity < 0)   // Too many elements, must back off
                initialCapacity >>>= 1;// Good luck allocating 2 ^ 30 elements
        }
        elements = new Object[initialCapacity];
    }

    /**
     * Doubles the capacity of this deque.  Call only when full, i.e.,
     * when head and tail have wrapped around to become equal.
     */
    private void doubleCapacity() {
        assert head == tail;
        int p = head;
        int n = elements.length;
        int r = n - p; // head右邊元素的個數
        int newCapacity = n << 1;//原空間的2倍
        if (newCapacity < 0)
            throw new IllegalStateException("Sorry, deque too big");
        Object[] a = new Object[newCapacity];
        System.arraycopy(elements, p, a, 0, r);//複製右半部分,對應上圖中綠色部分
        System.arraycopy(elements, 0, a, r, p);//複製左半部分,對應上圖中灰色部分
        elements = (E[])a;
        head = 0;
        tail = n;
    }

    /**
     * Copies the elements from our element array into the specified array,
     * in order (from first to last element in the deque).  It is assumed
     * that the array is large enough to hold all elements in the deque.
     *
     * @return its argument
     */
    private <T> T[] copyElements(T[] a) {
        if (head < tail) {
            System.arraycopy(elements, head, a, 0, size());
        } else if (head > tail) {
            int headPortionLen = elements.length - head;
            System.arraycopy(elements, head, a, 0, headPortionLen);
            System.arraycopy(elements, 0, a, headPortionLen, tail);
        }
        return a;
    }

構造函數

/**
     * Constructs an empty array deque with an initial capacity
     * sufficient to hold 16 elements.
     */
    public ArrayDeque() {
        elements = new Object[16];
    }

    /**
     * Constructs an empty array deque with an initial capacity
     * sufficient to hold the specified number of elements.
     *
     * @param numElements  lower bound on initial capacity of the deque
     */
    public ArrayDeque(int numElements) {
        allocateElements(numElements);
    }

    /**
     * Constructs a deque containing the elements of the specified
     * collection, in the order they are returned by the collection's
     * iterator.  (The first element returned by the collection's
     * iterator becomes the first element, or <i>front</i> of the
     * deque.)
     *
     * @param c the collection whose elements are to be placed into the deque
     * @throws NullPointerException if the specified collection is null
     */
    public ArrayDeque(Collection<? extends E> c) {
        allocateElements(c.size());
        addAll(c);
    }

基本的插入和取出方法

主要都是基於以下四個方法addFirst(), addLast(), pollFirst(), pollLast()

addFirst()

addFirst(E e)的做用是在Deque的首端插入元素,也就是在head的前面插入元素,在空間足夠且下標沒有越界的狀況下,只須要將elements[--head] = e便可。

實際須要考慮:1.空間是否夠用,以及2.下標是否越界的問題。

//addFirst(E e)
public void addFirst(E e) {
    if (e == null)//不容許放入null
        throw new NullPointerException();
    elements[head = (head - 1) & (elements.length - 1)] = e;//2.下標是否越界
    if (head == tail)//1.空間是否夠用
        doubleCapacity();//擴容
}

上述代碼咱們看到,空間問題是在插入以後解決的,由於tail老是指向下一個可插入的空位,也就意味着elements數組至少有一個空位,因此插入元素的時候不用考慮空間問題。

下標越界的處理解決起來很是簡單,head = (head - 1) & (elements.length - 1)就能夠了,這段代碼至關於取餘,同時解決了head爲負值的狀況。由於elements.length必需是2的指數倍,elements - 1就是二進制低位全1,跟head - 1相與以後就起到了取模的做用,若是head - 1爲負數(其實只多是-1),則至關於對其取相對於elements.length的補碼。

addLast()

addLast(E e)的做用是在Deque的尾端插入元素,也就是在tail的位置插入元素,因爲tail老是指向下一個能夠插入的空位,所以只須要elements[tail] = e;便可。插入完成後再檢查空間,若是空間已經用光,則調用doubleCapacity()進行擴容。

public void addLast(E e) {
    if (e == null)//不容許放入null
        throw new NullPointerException();
    elements[tail] = e;//賦值
    if ( (tail = (tail + 1) & (elements.length - 1)) == head)//下標越界處理
        doubleCapacity();//擴容
}

pollFirst()

pollFirst()的做用是刪除並返回Deque首端元素,也便是head位置處的元素。若是容器不空,只須要直接返回elements[head]便可,固然還須要處理下標的問題。因爲ArrayDeque中不容許放入null,當elements[head] == null時,意味着容器爲空。

public E pollFirst() {
    E result = elements[head];
    if (result == null)//null值意味着deque爲空
        return null;
    elements[h] = null;//let GC work
    head = (head + 1) & (elements.length - 1);//下標越界處理
    return result;
}

pollLast()

pollLast()的做用是刪除並返回Deque尾端元素,也便是tail位置前面的那個元素。

public E pollLast() {
    int t = (tail - 1) & (elements.length - 1);//tail的上一個位置是最後一個元素
    E result = elements[t];
    if (result == null)//null值意味着deque爲空
        return null;
    elements[t] = null;//let GC work
    tail = t;
    return result;
}

peekFirst()

peekFirst()的做用是返回但不刪除Deque首端元素,也便是head位置處的元素,直接返回elements[head]便可。

public E peekFirst() {
    return elements[head]; // elements[head] is null if deque empty
}

peekLast()

peekLast()的做用是返回但不刪除Deque尾端元素,也便是tail位置前面的那個元素。

public E peekLast() {
    return elements[(tail - 1) & (elements.length - 1)];
}

隊列(Queue)方法

add(E e)

/**
     * Inserts the specified element at the end of this deque.
     *
     * <p>This method is equivalent to {@link #addLast}.
     *
     * @param e the element to add
     * @return {@code true} (as specified by {@link Collection#add})
     * @throws NullPointerException if the specified element is null
     */
    public boolean add(E e) {
        addLast(e);
        return true;
    }

offer(E e)

/**
     * Inserts the specified element at the end of this deque.
     *
     * <p>This method is equivalent to {@link #offerLast}.
     *
     * @param e the element to add
     * @return {@code true} (as specified by {@link Queue#offer})
     * @throws NullPointerException if the specified element is null
     */
    public boolean offer(E e) {
        return offerLast(e);
    }

remove()

/**
     * Retrieves and removes the head of the queue represented by this deque.
     *
     * This method differs from {@link #poll poll} only in that it throws an
     * exception if this deque is empty.
     *
     * <p>This method is equivalent to {@link #removeFirst}.
     *
     * @return the head of the queue represented by this deque
     * @throws NoSuchElementException {@inheritDoc}
     */
    public E remove() {
        return removeFirst();
    }

poll()

/**
     * Retrieves and removes the head of the queue represented by this deque
     * (in other words, the first element of this deque), or returns
     * {@code null} if this deque is empty.
     *
     * <p>This method is equivalent to {@link #pollFirst}.
     *
     * @return the head of the queue represented by this deque, or
     *         {@code null} if this deque is empty
     */
    public E poll() {
        return pollFirst();
    }

element()

/**
     * Retrieves, but does not remove, the head of the queue represented by
     * this deque.  This method differs from {@link #peek peek} only in
     * that it throws an exception if this deque is empty.
     *
     * <p>This method is equivalent to {@link #getFirst}.
     *
     * @return the head of the queue represented by this deque
     * @throws NoSuchElementException {@inheritDoc}
     */
    public E element() {
        return getFirst();
    }

peek()

/**
     * Retrieves, but does not remove, the head of the queue represented by
     * this deque, or returns {@code null} if this deque is empty.
     *
     * <p>This method is equivalent to {@link #peekFirst}.
     *
     * @return the head of the queue represented by this deque, or
     *         {@code null} if this deque is empty
     */
    public E peek() {
        return peekFirst();
    }

棧(Stack)方法

push(E e)

/**
     * Pushes an element onto the stack represented by this deque.  In other
     * words, inserts the element at the front of this deque.
     *
     * <p>This method is equivalent to {@link #addFirst}.
     *
     * @param e the element to push
     * @throws NullPointerException if the specified element is null
     */
    public void push(E e) {
        addFirst(e);
    }

pop()

/**
     * Pops an element from the stack represented by this deque.  In other
     * words, removes and returns the first element of this deque.
     *
     * <p>This method is equivalent to {@link #removeFirst()}.
     *
     * @return the element at the front of this deque (which is the top
     *         of the stack represented by this deque)
     * @throws NoSuchElementException {@inheritDoc}
     */
    public E pop() {
        return removeFirst();
    }

集合(Collection)方法

size()

/**
     * Returns the number of elements in this deque.
     *
     * @return the number of elements in this deque
     */
    public int size() {
        return (tail - head) & (elements.length - 1);
    }

isEmpty()

/**
     * Returns {@code true} if this deque contains no elements.
     *
     * @return {@code true} if this deque contains no elements
     */
    public boolean isEmpty() {
        return head == tail;
    }

iterator()

/**
     * Returns an iterator over the elements in this deque.  The elements
     * will be ordered from first (head) to last (tail).  This is the same
     * order that elements would be dequeued (via successive calls to
     * {@link #remove} or popped (via successive calls to {@link #pop}).
     *
     * @return an iterator over the elements in this deque
     */
    public Iterator<E> iterator() {
        return new DeqIterator();
    }

    public Iterator<E> descendingIterator() {
        return new DescendingIterator();
    }

contains(Object o)

/**
     * Returns {@code true} if this deque contains the specified element.
     * More formally, returns {@code true} if and only if this deque contains
     * at least one element {@code e} such that {@code o.equals(e)}.
     *
     * @param o object to be checked for containment in this deque
     * @return {@code true} if this deque contains the specified element
     */
    public boolean contains(Object o) {
        if (o == null)
            return false;
        int mask = elements.length - 1;
        int i = head;
        Object x;
        while ( (x = elements[i]) != null) {
            if (o.equals(x))
                return true;
            i = (i + 1) & mask;
        }
        return false;
    }

remove(Object o)

/**
     * Removes a single instance of the specified element from this deque.
     * If the deque does not contain the element, it is unchanged.
     * More formally, removes the first element {@code e} such that
     * {@code o.equals(e)} (if such an element exists).
     * Returns {@code true} if this deque contained the specified element
     * (or equivalently, if this deque changed as a result of the call).
     *
     * <p>This method is equivalent to {@link #removeFirstOccurrence(Object)}.
     *
     * @param o element to be removed from this deque, if present
     * @return {@code true} if this deque contained the specified element
     */
    public boolean remove(Object o) {
        return removeFirstOccurrence(o);
    }

clear()

/**
     * Removes all of the elements from this deque.
     * The deque will be empty after this call returns.
     */
    public void clear() {
        int h = head;
        int t = tail;
        if (h != t) { // clear all cells
            head = tail = 0;
            int i = h;
            int mask = elements.length - 1;
            do {
                elements[i] = null;
                i = (i + 1) & mask;
            } while (i != t);
        }
    }

toArray()

/**
     * Returns an array containing all of the elements in this deque
     * in proper sequence (from first to last element).
     *
     * <p>The returned array will be "safe" in that no references to it are
     * maintained by this deque.  (In other words, this method must allocate
     * a new array).  The caller is thus free to modify the returned array.
     *
     * <p>This method acts as bridge between array-based and collection-based
     * APIs.
     *
     * @return an array containing all of the elements in this deque
     */
    public Object[] toArray() {
        return copyElements(new Object[size()]);
    }

toArray(T[] a)

/**
     * Returns an array containing all of the elements in this deque in
     * proper sequence (from first to last element); the runtime type of the
     * returned array is that of the specified array.  If the deque fits in
     * the specified array, it is returned therein.  Otherwise, a new array
     * is allocated with the runtime type of the specified array and the
     * size of this deque.
     *
     * <p>If this deque fits in the specified array with room to spare
     * (i.e., the array has more elements than this deque), the element in
     * the array immediately following the end of the deque is set to
     * {@code null}.
     *
     * <p>Like the {@link #toArray()} method, this method acts as bridge between
     * array-based and collection-based APIs.  Further, this method allows
     * precise control over the runtime type of the output array, and may,
     * under certain circumstances, be used to save allocation costs.
     *
     * <p>Suppose {@code x} is a deque known to contain only strings.
     * The following code can be used to dump the deque into a newly
     * allocated array of {@code String}:
     *
     *  <pre> {@code String[] y = x.toArray(new String[0]);}</pre>
     *
     * Note that {@code toArray(new Object[0])} is identical in function to
     * {@code toArray()}.
     *
     * @param a the array into which the elements of the deque are to
     *          be stored, if it is big enough; otherwise, a new array of the
     *          same runtime type is allocated for this purpose
     * @return an array containing all of the elements in this deque
     * @throws ArrayStoreException if the runtime type of the specified array
     *         is not a supertype of the runtime type of every element in
     *         this deque
     * @throws NullPointerException if the specified array is null
     */
    @SuppressWarnings("unchecked")
    public <T> T[] toArray(T[] a) {
        int size = size();
        if (a.length < size)
            a = (T[])java.lang.reflect.Array.newInstance(
                    a.getClass().getComponentType(), size);
        copyElements(a);
        if (a.length > size)
            a[size] = null;
        return a;
    }
相關文章
相關標籤/搜索