<rt id="bn8ez"></rt>
<label id="bn8ez"></label>

  • <span id="bn8ez"></span>

    <label id="bn8ez"><meter id="bn8ez"></meter></label>

    隨筆-13  評論-22  文章-0  trackbacks-0

    ArrayDeque

    本文github地址

    前言

    Java里有一個叫做Stack的類,卻沒有叫做Queue的類(它是個接口名字)。當需要使用棧時,Java已不推薦使用Stack,而是推薦使用更高效的ArrayDeque;既然Queue只是一個接口,當需要使用隊列時也就首選ArrayDeque了(次選是LinkedList)。

    總體介紹

    要講棧和隊列,首先要講Deque接口。Deque的含義是“double ended queue”,即雙端隊列,它既可以當作棧使用,也可以當作隊列使用。下表列出了DequeQueue相對應的接口:

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

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

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

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

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

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

    ArrayDeque_base.png

    上圖中我們看到,head指向首端第一個有效元素,tail指向尾端第一個可以插入元素的空位。因為是循環數組,所以head不一定總等于0,tail也不一定總是比head大。

    方法剖析

    addFirst()

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

    ArrayDeque_addFirst.png

    實際需要考慮:1.空間是否夠用,以及2.下標是否越界的問題。上圖中,如果head0之后接著調用addFirst(),雖然空余空間還夠用,但head-1,下標越界了。下列代碼很好的解決了這兩個問題。

    //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的補碼。

    下面再說說擴容函數doubleCapacity(),其邏輯是申請一個更大的數組(原數組的兩倍),然后將原數組復制過去。過程如下圖所示:

    ArrayDeque_doubleCapacity.png

    圖中我們看到,復制分兩次進行,第一次復制head右邊的元素,第二次復制head左邊的元素。

    //doubleCapacity()
    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;
    }


    addLast()

    addLast(E e)的作用是在Deque的尾端插入元素,也就是在tail的位置插入元素,由于tail總是指向下一個可以插入的空位,因此只需要elements[tail] = e;即可。插入完成后再檢查空間,如果空間已經用光,則調用doubleCapacity()進行擴容。

    ArrayDeque_addLast.png

    public void addLast(E e) {
        if (e == null)//不允許放入null
            throw new NullPointerException();
        elements[tail] = e;//賦值
        if ( (tail = (tail + 1) & (elements.length - 1)) == head)//下標越界處理
            doubleCapacity();//擴容
    }
    下標越界處理方式addFirt()中已經講過,不再贅述。

    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)];
    }
    posted on 2016-05-07 18:30 CarpenterLee 閱讀(1368) 評論(2)  編輯  收藏

    評論:
    # re: Java ArrayDeque源碼剖析 2016-05-08 22:09 | journey_fan
    寫得真好,畫圖超有耐心,佩服  回復  更多評論
      
    # re: Java ArrayDeque源碼剖析 2016-05-09 08:44 | CarpenterLee
    @journey_fan
    多謝學姐夸獎~  回復  更多評論
      

    只有注冊用戶登錄后才能發表評論。


    網站導航:
     
    主站蜘蛛池模板: 亚洲精品美女久久777777| 国产成人精品免费直播| 亚洲国产精品无码久久一区二区| 精品在线观看免费| 亚洲av午夜精品一区二区三区| 色天使色婷婷在线影院亚洲| 国产一区二区三区在线免费| 婷婷国产偷v国产偷v亚洲| 免费在线黄色网址| 国产日韩久久免费影院| 亚洲AV永久精品爱情岛论坛| 免费无码又爽又刺激高潮视频 | 国产免费啪嗒啪嗒视频看看| 亚洲AV网一区二区三区| 亚洲高清免费视频| 中文字幕免费在线看| 亚洲国产精品久久| 99在线精品免费视频九九视| 亚洲精品无码专区久久| 免费a级毛片永久免费| 国产无遮挡又黄又爽免费网站| 亚洲一区二区三区日本久久九| h视频在线观看免费网站| 久久久久久亚洲精品影院| 色www永久免费视频| free哆拍拍免费永久视频| 亚洲AV无码成人精品区天堂| 91精品免费国产高清在线| 亚洲AV成人无码久久WWW| 色噜噜亚洲精品中文字幕| 99久久99久久精品免费观看| 亚洲精品色播一区二区| 亚洲中文字幕无码日韩| 亚洲免费视频观看| 免费无码AV一区二区| 无码乱人伦一区二区亚洲| 在线免费视频一区二区| 任你躁在线精品免费| 亚洲中文无码永久免| 国产AV无码专区亚洲精品| 好吊妞在线新免费视频|