队列篇之使用数组模拟一个队列

队列是一个有序列表, 可以使用数组实现, 也可以使用链表实现 

队列遵守先进先出的原则

1. 下面使用数组模拟一个队列

public class ArrayQueueDemo {
    public static void main(String[] args) {
        ArrayQueue queue = new ArrayQueue(3);
        queue.add(1);
        queue.show();
        System.out.println("-----------------");
        queue.add(2);
        queue.show();
        System.out.println("-----------------");
        queue.add(3);
        queue.show();
        System.out.println("-----------------");
//        System.out.println(queue.pop());
        System.out.println(queue.pop());
////        System.out.println(queue.pop());
//        queue.show();
        System.out.println("-----------------");
        System.out.println(queue.peek());
        System.out.println(queue.peek());
        System.out.println(queue.peek());
        queue.show();
    }
}

/**
 * 使用数组来模拟队列
 * 该代码存在着一个明显的bug,就是数组只能使用一次, 不能复用. 原因很明显,rear指针只有++操作.
 */
class ArrayQueue {
    /**
     * 队列最大容量
     */
    private int maxSize;

    /**
     * 队列头指针
     */
    private int front;

    /**
     * 队列尾指针
     */
    private int rear;


    /**
     * 存放数据的数组
     */
    private int[] arr;


    // 创建队列的构造器
    public ArrayQueue(int maxSize) {
        this.maxSize = maxSize;
        arr = new int[maxSize];
        front = -1;  // 指向队列头部前一个位置
        rear = -1; // 指向队列尾部指针(指队列最后一个数据)
    }

    /**
     * 判断队列是否已满
     *
     * @return
     */
    public boolean isFull() {
        return rear == maxSize - 1;
    }

    public boolean isEmpty() {
        return rear == front;
    }


    public void add(int data) {
        if (isFull()) {
            System.out.println("队列已满");
            return;
        }

        rear++;  // rear指针后移一位
        arr[rear] = data;
    }

    /**
     * 弹出队列头部的数据,
     * 其实该数据还是存在于数组中,只是front指针+1了, 不能再次获取了而已
     *
     * @return 队列头部的数据
     */
    public int pop() {
        if (isEmpty()) {
            throw new RuntimeException("队列为空, 不能取数据");
        }
        front++; // front后移一位
        return arr[front];
    }

    /**
     * 只是获取队列头部的数据,但并不会移除该数据(即是说front指针不会发生变化)
     * @return 队列头部的数据
     */
    public int peek() {
        if (isEmpty()) {
            throw new RuntimeException("队列为空, 不能peek数据");
        }
        return arr[front + 1]; // 为什么加1? 是因为我们默认front指向数组数据的前一位
    }


    public void show() {
        if (isEmpty()) {
            System.out.println("队列空,无数据");
            return;
        }
        for (int i = 0; i < arr.length; i++) {
            System.out.printf("arr[%d] = %s\n", i, arr[i]);
        }
    }
}

2. jdk中ArrayBlockingQueue实现方式

2.1 构造器

  public ArrayBlockingQueue(int capacity) {
        this(capacity, false);
    }
    public ArrayBlockingQueue(int capacity, boolean fair) {
        if (capacity <= 0)
            throw new IllegalArgumentException();
        this.items = new Object[capacity];
        lock = new ReentrantLock(fair);
        notEmpty = lock.newCondition();
        notFull =  lock.newCondition();
    }

很明显, 通过构造器我们可以知道 , ArrayBlockingQueue是一个有界队列 ,其底层使用了一个Object类型的数组items来存储数据.

2.2 添加元素

因为添加元素到队列的底层方法都是使用offer方法,所以我们直接看offer方法源码即可

public boolean offer(E e) {
    checkNotNull(e);
    final ReentrantLock lock = this.lock;
    lock.lock();
    try {
        // 如果队列中的元素个数count等于数据组items的长度,说明队列已满,直接返回false,添加元素失败
        if (count == items.length)
            return false;
        else {
            enqueue(e); // 添加元素
            return true;
        }
    } finally {
        lock.unlock();
    }
}
private void enqueue(E x) {
    final Object[] items = this.items;
    items[putIndex] = x;  // 将元素x放到数组items的putIndex位置
    if (++putIndex == items.length) // 表示items已经满了
        putIndex = 0;  // putIndex重新置为0
    count++; // 队列元素加1 
    notEmpty.signal();
}

源码很简单,锁不是本篇的关注点. 值得一提的是 if (++putIndex == items.length) { putIndex = 0; }这行代码挺有意思的, 它解决了数组重复使用问题.

2.3 获取元素

    private E dequeue() {
        final Object[] items = this.items;
        E x = (E) items[takeIndex];// 获取takeIndex位置的元素,从0开始,就是队列的头部
        items[takeIndex] = null;  // 将数组中takeIndex置空
        if (++takeIndex == items.length)  // 如果队列中的元素取完了,takeIndex重置为0,下次继续从头部开始获取元素
            takeIndex = 0;
        count--;  // 队列中的总元素个数减1
        if (itrs != null)   // 没用到, 没细看
            itrs.elementDequeued();
        notFull.signal();  
        return x; // 返回取出的元素
    }

2.4 总结

ArrayBlockingQueue底层使用了一个Object类型的数组存储数据, 同时它维护了两个指针putIndex和takeIndex,它们的默认值都是0, 其中putIndex与offer操作相关,即是每添加一个元素,putIndex的值加1, 如果++putIndex等于数组的length,说明队列已满(其实是数组满了), 这时将putIndex重置为0. takeIndex与putIndex类似,不再赘述.

猜你喜欢

转载自www.cnblogs.com/z-qinfeng/p/12128026.html
今日推荐