java數據結構(三)

public class ArrayDeque<E> extends AbstractCollection<E>
                           implements Deque<E>, Cloneable, Serializable
transient Object[] elements;
transient int head;
transient int tail;
-------、
public interface Deque<E> extends Queue<E> {

從定義可以看出,ArrayDeque類,實現了 Deque接口,而Deque繼承了Queue(隊列)接口;
而Queue接口,有以下功能:

boolean add(E e)
boolean offer(E e)
E remove();
E poll();
E element();
E peek();

Deque接口,定義了以下方法:

 void addFirst(E e);
 void addLast(E e);
 boolean offerFirst(E e);
 boolean offerLast(E e);
 E removeFirst();
 E removeLast();
 E pollFirst();
 E pollLast();
 E getFirst();
 E getLast();
 E peekFirst();
 E peekLast();

---------
 boolean removeFirstOccurrence(Object o);
 boolean removeLastOccurrence(Object o)
 boolean add(E e);
 boolean offer(E e);
 E remove();
 E poll();
 E element();
 E peek();

-----------
 boolean addAll(Collection<? extends E> c);
 void push(E e);
 E pop();
-----------
 boolean remove(Object o);
 boolean contains(Object o);
 int size();
 Iterator<E> iterator();
 Iterator<E> descendingIterator();

從源碼來看,ArrayDeque采用了類似與雙指針的雙端隊列結構,
其中Deque可以分為Queue 和 Stack相對應的接口;

Queue
Stack

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

ArrayDeque和LinkedList是Deque的兩個通用實現,由于官方更推薦使用AarryDeque用作棧和隊列,加之上一篇已經講解過LinkedList,本文將著重講解ArrayDeque的具體實現。

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

由于底層為 circular array,故其 head 和 tail 在數值上大小不一定,head代表頭部的第一個有效元素,tail代表尾部可以插入的位置;
其示意圖如下:

方法剖析
1、addLast()方法
像隊尾增加元素(tail),

 public void addLast(E e) {
        if (e == null)  //隊列中不允許放入null
            throw new NullPointerException();
        final Object[] es = elements; //新建了一個新的數組,來引用原數組
        es[tail] = e;   // 先插入,在判斷是否進行擴容
        if (head == (tail = inc(tail, es.length))) //若列表已滿,則進行擴容
            grow(1);
    }
 static final int inc(int i, int modulus) {
        if (++i >= modulus) i = 0; 
     //判斷隊尾是否已經在數組的尾部,若在則tail下一步為0
        return i;
    }
列表已滿的情況

此時,tail的下一個空位為 tail+1

列表已滿的情況

此時,tail的下一個空位為 0

2、addFirst() 方法
此時情況與addLast相似,只需江 tail+1 改為head-1 即可;

  public void addFirst(E e) {
        if (e == null) 
            throw new NullPointerException();
        final Object[] es = elements; 
        es[head = dec(head, es.length)] = e; // 先插入,在判斷是否進行擴容
        if (head == tail)
            grow(1);
    }

 static final int dec(int i, int modulus) { 
        if (--i < 0) i = modulus - 1;
        return i;
    }

上述代碼我們看到,空間問題是在插入之后解決的,因為tail總是指向下一個可插入的空位,也就意味著elements數組至少有一個空位,所以插入元素的時候不用考慮空間問題。

擴容的方法:


 private void grow(int needed) {
        // overflow-conscious code
        final int oldCapacity = elements.length;
        int newCapacity;
        // Double capacity if small; else grow by 50%
        int jump = (oldCapacity < 64) ? (oldCapacity + 2) : (oldCapacity >> 1);
       // 判斷原數組長度
        if (jump < needed
            || (newCapacity = (oldCapacity + jump)) - MAX_ARRAY_SIZE > 0)
            newCapacity = newCapacity(needed, jump);
        final Object[] es = elements = Arrays.copyOf(elements, newCapacity);
        // Exceptionally, here tail == head needs to be disambiguated
        if (tail < head || (tail == head && es[head] != null)) {
            // wrap around; slide first leg forward to end of array
            int newSpace = newCapacity - oldCapacity;
            System.arraycopy(es, head,
                             es, head + newSpace,
                             oldCapacity - head);
            for (int i = head, to = (head += newSpace); i < to; i++)
                es[i] = null;
        }
    }

 private int newCapacity(int needed, int jump) {
        final int oldCapacity = elements.length, minCapacity;
        if ((minCapacity = oldCapacity + needed) - MAX_ARRAY_SIZE > 0) {
            if (minCapacity < 0)
                throw new IllegalStateException("Sorry, deque too big");
            return Integer.MAX_VALUE;
        }
        if (needed > jump)
            return minCapacity;
        return (oldCapacity + jump - MAX_ARRAY_SIZE < 0)
            ? oldCapacity + jump
            : MAX_ARRAY_SIZE;
    }

擴容分區間進行擴容,在不超過最大限度情況下,若源列表的長度小于64,則擴容為2倍,否則為1.5倍;
擴容完后,要將原有元素復制到新的隊列中去;

3、pollFirst() 方法

    public E pollFirst() {
        final Object[] es;
        final int h;
        E e = elementAt(es = elements, h = head);
        if (e != null) {
            es[h] = null;
            head = inc(h, es.length);
        }
        return e;
    }
static final <E> E elementAt(Object[] es, int i) {
        return (E) es[i];
    }

pollFirst()的作用是刪除并返回Deque首端元素,也即是head位置處的元素。如果容器不空,只需要直接返回elements[head]即可,當然還需要處理下標的問題。由于ArrayDeque中不允許放入null,當elements[head] == null時,意味著容器為空。

4、 pollLast()方法


    public E pollLast() {
        final Object[] es;
        final int t;
        E e = elementAt(es = elements, t = dec(tail, es.length));
        if (e != null)
            es[tail = t] = null;
        return e;
    }

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

5、peekFirst() 和peekLast()
只返回,首端元素,并不刪除元素;

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容