java容器類4:Queue深入解讀


1594931-293dba23ebc504c2

Collection的其它兩大分支:List和Set在前面已近分析過,這篇來分析一下Queue的底層實現。

前三篇關於Java容器類的文章:

java容器類1:Collection,List,ArrayList,LinkedList深入解讀

java容器類2:Map及HashMap深入解讀

java容器類3:set/HastSet/MapSet深入解讀

Queue


public interface Queue<E> extends Collection<E> {
    boolean add(E var1);

    boolean offer(E var1);

    E remove();

    E poll();

    E element();

    E peek();
}
這就是Queue接口的代碼,相比於List或者Set簡潔明了很多。下面介紹一下它里面接口的含義:
在處理元素前用於保存元素的 collection。除了基本的 Collection 操作外,隊列還提供其他的

插入、提取和檢查

操作。每個方法都存在兩種形式:

一種拋出異常(操作失敗時),另一種返回一個特殊值(nullfalse,具體取決於操作)。

插入操作的后一種形式是用於專門為有容量限制的 Queue 實現設計的;在大多數實現中,插入操作不會失敗。
image
 
 
        
 
        
1182892-20171122100317930-842768608

AbstractQueue


AbstractQueue中實現了queue和Collection中部分函數,比較簡單,源碼如下:

public abstract class AbstractQueue<E> extends AbstractCollection<E>
    implements Queue<E> {
    protected AbstractQueue() {
    }
    public boolean add(E e) {
        if (offer(e))
            return true;
        else
            throw new IllegalStateException("Queue full");
    }


    public E remove() {
        E x = poll();
        if (x != null)
            return x;
        else
            throw new NoSuchElementException();
    }

    public E element() {
        E x = peek();
        if (x != null)
            return x;
        else
            throw new NoSuchElementException();
    }

    public void clear() {
        while (poll() != null)
            ;
    }

    public boolean addAll(Collection<? extends E> c) {
        if (c == null)
            throw new NullPointerException();
        if (c == this)
            throw new IllegalArgumentException();
        boolean modified = false;
        for (E e : c)
            if (add(e))
                modified = true;
        return modified;
    }
}

從上面的調用關系可以看出來,Queue的解釋中哪些是會拋出異常的調用,哪些是不會拋出異常的調用接口。

Deque


一個線性 collection,支持在兩端插入和移除元素。名稱 deque 是“double ended queue(雙端隊列)”的縮寫,通常讀為“deck”。大多數 Deque 實現對於它們能夠包含的元素數沒有固定限制,但此接口既支持有容量限制的雙端隊列,也支持沒有固定大小限制的雙端隊列。(java 1.6版本中的家扣,1.8中接口有變動,但是大概含義相似)

image

Java容器類1中介紹了LinkedList,鏈表類其實實現了 Deque的接口,所以鏈表支持從頭部和尾部添加和移除元素。

ArrayDeque


因為鏈表的存儲結構可能比較簡單,這里介紹一下ArrayDeque,它的里面存儲元素使用一個數組。 作為一個雙端隊列的數組,涉及到擴容和元素的拷貝的邏輯可能比較復雜些。

看一下里面的幾個構造函數:

public ArrayDeque() {
        elements = 
new Object[16];
    }


public ArrayDeque(int numElements) {
        
allocateElements(numElements);
    }

public ArrayDeque(Collection<? extends E> c) {
        allocateElements(c.size());
        addAll(c);
    }

從構造函數可以看出默認的會分配一個長度為16的數組。同時,也支持指定大小的隊列(這里的allocateElements函數之前在

java容器類2:Map及HashMap深入解讀  中已經深入分析過,是個非常精妙的函數)。下面看一下到底是如何實現插入?又是如何自動擴充數組的?

ArrayQueue中維護了兩個成員變量:head和tail分別代表 隊列的頭和尾在數組中的下標。

 /**
     * 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;

在隊列的首部添加元素:

public void addFirst(E e) {
        if (e == null)
            throw new NullPointerException();
        
elements[head = (head - 1) & (elements.length - 1)] = e;
        if (head == tail)
            
doubleCapacity();
    }
public void
addLast(E e) {
if
 (e == 
null
)
throw new
 NullPointerException();
elements[tail] = e;
if
 ( (tail = (tail + 1) & (elements.length - 1)) == head)
doubleCapacity();
}

由構造函數和數組分配的函數可以知道,數組的長度肯定是一個2的冪次方的一個整數。

當head為大於0的整數時,在頭部插入很簡單,將head前一個元素賦值為e就可以了。那么當head為0時,怎么計算的?由上面可以看出會插入到數組的尾部。所以ArrayDeque相當於在一個圓環上,規定一個頭一個尾作為隊列的前后(將數組的首位相連)。

在最后位置添加元素的原理和在首部添加相似。注意判斷是否已滿的 判斷,這里不再分析。

當隊列已經滿后,會將數組的長度double。由於數組是不能自由擴張的,所以doubleCapacity函數應該是分配一個更大的數組,並將原來的元素拷貝進去,這里不再分析。

總的來說雙端隊列ArrayDeque是在數組的基礎之上實現,原理和實現都不算復雜,但是很多邊界調節等細節可以斟酌。

BlockingQueue


BlockingQueue是concurrent包下面的,后續打算寫一個系列文章專門分析concurrent包下面的類,及一些多線程相關的東西。

PriorityQueue


優先級隊列是一個可以排序的隊列。內部是一個最大堆,大部分人應該了解堆排序,所以對最大堆應該不會陌生。

每次讀取元素都是讀取最大的元素(默認情況下)。

對外的接口有如下:

方法名 功能描述
add(E e) 添加元素
clear() 清空
contains(Object o) 檢查是否包含當前參數元素
offer(E e) 添加元素
peek() 讀取元素,(不刪除)
poll() 取出元素,(刪除)
remove(Object o) 刪除指定元素
size() 返回長度

PriorityQueue 默認是一個最大堆結構,如果想構造一個最小堆:

private static final int DEFAULT_INITIAL_CAPACITY = 11;
PriorityQueue<Integer> maxHeap=new PriorityQueue<Integer>(DEFAULT_INITIAL_CAPACITY, new Comparator<Integer>() {
        @Override
        public int compare(Integer o1, Integer o2) {
            return o2-o1;
        }
    });

關於堆的數據結構部分這里不再分析可以參考:https://www.cnblogs.com/tstd/p/5125949.html

c++版的優先級隊列分析:優先級隊列用法詳解(priority_queue)

由於是通過數組保存數據,所以優先級隊列也會涉及到容量的擴充等,和HashMap/Setting/Collection的擴容原理相同,甚至更簡單,不再分析。PriorityQueue內部的操作都是在最大堆的基礎上展開的,閱讀堆的數據結構相關資料便可了解。

參考:

http://tool.oschina.net/uploads/apidocs/jdk-zh/java/util/Deque.html

http://www.cnblogs.com/NeilZhang/p/5650226.html


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM