队列是一个有序列表, 可以使用数组实现, 也可以使用链表实现
队列遵守先进先出的原则
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类似,不再赘述.
原文地址:https://www.cnblogs.com/z-qinfeng/p/12128026.html
时间: 2024-10-06 19:46:26