1. 程式人生 > >教你如何使用Java手寫一個基於陣列實現的佇列

教你如何使用Java手寫一個基於陣列實現的佇列

  一、概述

  佇列,又稱為佇列(queue),是先進先出(FIFO, First-In-First-Out)的線性表。在具體應用中通常用連結串列或者陣列來實現。佇列只允許在後端(稱為rear)進行插入操作,在前端(稱為front)進行刪除操作。佇列的操作方式和堆疊類似,唯一的區別在於佇列只允許新資料在後端進行新增。

  在Java中佇列又可以分為兩個大類,一種是阻塞佇列和非阻塞佇列。

  1、沒有實現阻塞介面:

  1)實現java.util.Queue的LinkList,

  2)實現java.util.AbstractQueue介面內建的不阻塞佇列: PriorityQueue 和 ConcurrentLinkedQueue

  2、實現阻塞介面的

  java.util.concurrent 中加入了 BlockingQueue 介面和五個阻塞佇列類。它實質上就是一種帶有一點扭曲的 FIFO 資料結構。不是立即從佇列中新增或者刪除元素,執行緒執行操作阻塞,直到有空間或者元素可用。
  五個佇列所提供的各有不同:
  * ArrayBlockingQueue :一個由陣列支援的有界佇列。
  * LinkedBlockingQueue :一個由連結節點支援的可選有界佇列。
  * PriorityBlockingQueue :一個由優先順序堆支援的無界優先順序佇列。
  * DelayQueue :一個由優先順序堆支援的、基於時間的排程佇列。
  * SynchronousQueue :一個利用 BlockingQueue 介面的簡單聚集(rendezvous)機制。
  佇列是Java中常用的資料結構,比如線上程池中就是用到了佇列,比如訊息佇列等。

  由佇列先入先出的特性,我們知道佇列資料的儲存結構可以兩種,一種是基於陣列實現的,另一種則是基於單鏈實現。前者在建立的時候就已經確定了陣列的長度,所以佇列的長度是固定的,但是可以迴圈使用陣列,所以這種佇列也可以稱之為迴圈佇列。後者實現的佇列內部通過指標指向形成一個佇列,這種佇列是單向且長度不固定,所以也稱之為非迴圈佇列。下面我將使用兩種方式分別實現佇列。

  二、基於陣列實現迴圈佇列

  由於在往佇列中放資料或拉取資料的時候需要移動陣列對應的下標,所以需要記錄一下隊尾和隊頭的位置。說一下幾個核心的屬性吧:

  1、queue:佇列,object型別的陣列,用於儲存資料,長度固定,當儲存的資料數量大於陣列當度則丟擲異常;

  2、head:隊頭指標,int型別,用於記錄佇列頭部的位置資訊。

  3、tail:隊尾指標,int型別,用於記錄佇列尾部的位置資訊。

  4、size:佇列長度,佇列長度大於等於0或者小於等於陣列長度。

 /**
     * 佇列管道,當管道中存放的資料大於佇列的長度時將不會再offer資料,直至從佇列中poll資料後
     */
    private Object[] queue;
    //佇列的頭部,獲取資料時總是從頭部獲取
    private int head;
    //佇列尾部,push資料時總是從尾部新增
    private int tail;
    //佇列長度
    private int size;
    //陣列中能存放資料的最大容量
    private final static int MAX_CAPACITY = 1<<30;
    //陣列長度
    private int capacity;
    //最大下標
    private int maxIndex;

  三、資料結構

 

  圖中,紅色部分即為佇列的長度,陣列的長度為16。因為這個佇列是迴圈佇列,所以佇列的頭部不一定要在佇列尾部前面,只要佇列的長度不大於陣列的長度就可以了。

  四、構造方法

  1、MyQueue(int initialCapacity):建立一個最大長度為 initialCapacity的佇列。

  2、MyQueue():建立一個預設最大長度的佇列,預設長度為16;

    public MyQueue(int initialCapacity){
        if (initialCapacity > MAX_CAPACITY)
            throw new OutOfMemoryError("initialCapacity too large");
        if (initialCapacity <= 0)
            throw new IndexOutOfBoundsException("initialCapacity must be more than zero");
        queue = new Object[initialCapacity];
        capacity = initialCapacity;
        maxIndex = initialCapacity - 1;
        head = tail = -1;
        size = 0;
    }
    public MyQueue(){
        queue = new Object[16];
        capacity = 16;
        head = tail = -1;
        size = 0;
        maxIndex = 15;
    }

  五、往佇列新增資料

  新增資料時,首先判斷佇列的長度是否超出了陣列的長度,如果超出了就新增失敗(也可以設定成等待,等到有人消費了佇列裡的資料,然後再新增進去)。都是從佇列的尾部新增資料的,新增完資料後tail指標也會相應自增1。具體實現如一下程式碼:

/**
     * 往佇列尾部新增資料
     * @param object 資料
     */
    public void offer(Object object){
        if (size >= capacity){
            System.out.println("queue's size more than or equal to array's capacity");
            return;
        }
        if (++tail > maxIndex){
            tail = 0;
        }
        queue[tail] = object;
        size++;
    }

  六、向佇列中拉取資料

  拉取資料是從佇列頭部拉取的,拉取完之後將該元素刪除,佇列的長度減一,head自增1。程式碼如下:

    /**
     * 從佇列頭部拉出資料
     * @return 返回佇列的第一個資料
     */
    public Object poll(){
        if (size <= 0){
            System.out.println("the queue is null");
            return null;
        }
        if (++head > maxIndex){
            head = 0;
        }
        size--;
        Object old = queue[head];
        queue[head] = null;
        return old;
    }

  七、其他方法

  1、檢視資料:這個方法跟拉取資料一樣都是獲取到佇列頭部的資料,區別是該方法不會將對頭資料刪除:

/**
     * 檢視第一個資料
     * @return
     */
    public Object peek(){
        return queue[head];
    }

  2、清空佇列:

/**
     * 清空佇列
     */
    public void clear(){
        for (int i = 0; i < queue.length; i++) {
            queue[i] = null;
        }
        tail = head = -1;
        size = 0;
    }

  完整的程式碼如下:

/**
 * 佇列
 */
public class MyQueue {

    /**
     * 佇列管道,當管道中存放的資料大於佇列的長度時將不會再offer資料,直至從佇列中poll資料後
     */
    private Object[] queue;
    //佇列的頭部,獲取資料時總是從頭部獲取
    private int head;
    //佇列尾部,push資料時總是從尾部新增
    private int tail;
    //佇列長度
    private int size;
    //陣列中能存放資料的最大容量
    private final static int MAX_CAPACITY = 1<<30;
    //陣列長度
    private int capacity;
    //最大下標
    private int maxIndex;

    public MyQueue(int initialCapacity){
        if (initialCapacity > MAX_CAPACITY)
            throw new OutOfMemoryError("initialCapacity too large");
        if (initialCapacity <= 0)
            throw new IndexOutOfBoundsException("initialCapacity must be more than zero");
        queue = new Object[initialCapacity];
        capacity = initialCapacity;
        maxIndex = initialCapacity - 1;
        head = tail = -1;
        size = 0;
    }
    public MyQueue(){
        queue = new Object[16];
        capacity = 16;
        head = tail = -1;
        size = 0;
        maxIndex = 15;
    }

    /**
     * 往佇列尾部新增資料
     * @param object 資料
     */
    public void offer(Object object){
        if (size >= capacity){
            System.out.println("queue's size more than or equal to array's capacity");
            return;
        }
        if (++tail > maxIndex){
            tail = 0;
        }
        queue[tail] = object;
        size++;
    }

    /**
     * 從佇列頭部拉出資料
     * @return 返回佇列的第一個資料
     */
    public Object poll(){
        if (size <= 0){
            System.out.println("the queue is null");
            return null;
        }
        if (++head > maxIndex){
            head = 0;
        }
        size--;
        Object old = queue[head];
        queue[head] = null;
        return old;
    }

    /**
     * 檢視第一個資料
     * @return
     */
    public Object peek(){
        return queue[head];
    }

    /**
     * 佇列中儲存的資料量
     * @return
     */
    public int size(){
        return size;
    }

    /**
     * 佇列是否為空
     * @return
     */
    public boolean isEmpty(){
        return size == 0;
    }

    /**
     * 清空佇列
     */
    public void clear(){
        for (int i = 0; i < queue.length; i++) {
            queue[i] = null;
        }
        tail = head = -1;
        size = 0;
    }

    @Override
    public String toString() {
        if (size <= 0) return "{}";
        StringBuilder builder = new StringBuilder(size + 8);
        builder.append("{");
        int h = head;
        int count = 0;
        while (count < size){
            if (++h > maxIndex) h = 0;
            builder.append(queue[h]);
            builder.append(", ");
            count++;
        }
        return builder.substring(0,builder.length()-2) + "}";
    }
}

  八、總結:

  1、該佇列為非執行緒安全的,在多執行緒環境中可能會發生資料丟失等問題。

  2、佇列通過移動指標來確定陣列下標的位置,因為是基於陣列實現的,所以佇列的長度不能夠超過陣列的長度。

  3、該佇列是迴圈佇列,這就意味著陣列可以重複被使用,避免了重複建立物件帶來的效能的開銷。

  4、新增資料時總是從佇列尾部新增,拉取資料時總是從佇列頭部拉取,拉取完將物件元素刪除。