关注公众号 MageByte,设置星标获取最新干货。 “加群” 进入技术交流群获更多技术成长。java
向固定大小的线程池投放请求任务时,若果线程池中没有空闲资源了,这时候还有新的请求进来,线程池如何处理这个请求?拒绝请求仍是排队?使用怎样的处理机制git
通常两种策略:github
那如何存储排队的请求呢?这就是今天要讲的话题。算法
其底层的数据结构就是今天咱们要讲的内容,「队列」Queue
。数组
完整代码详见 GitHub:https://github.com/UniqueDong/algorithms.git浏览器
用一个生活例子,能够想象成超市排队结帐,先来的先结帐,后面的人只能站在末尾,不容许插队。先进先出,这就是所谓的「队列」缓存
队列是一种线性数据结构,队列的出口端叫「队头」,队列的入口端叫「队尾」。数据结构
与栈相似队列的数据结构能够使用数组实现也能够使用链表实现。关于栈的内容同窗们能够翻阅历史文章学习「栈:实现浏览器前进后退」,队列最基本的操做也是两个:入队 (enqueue) ,将新元素放到队尾;出队 (dequeue),从队头移除元素,出队元素的下一个元素变成新的队头。并发
做为基础的数据结构,队列的应用也很普遍,尤为是一些特定场景下的队列。好比循环队列、阻塞队列、并发队列。它们在不少偏底层系统、框架、中间件的开发中,起着关键性的做用。好比高性能队列 Disruptor、Linux 环形缓存,都用到了循环并发队列;Java concurrent 并发包利用 ArrayBlockingQueue 来实现公平锁等。框架
队列也是一种操做受限的线性表数据结构。
队列是跟栈同样,是一种抽象的数据结构。 具备先进先出的特性,在队头删除数据,在队尾插入数据。
能够使用数组实现,也能够使用链表实现。使用数组实现的叫 顺序队列,用链表实现的 叫 链式队列。
一块儿先来看数组实现的队列:
随着不停地进行入队、出队操做,head 和 tail 都会持续日后移动。当 tail 移动到最右边,即便数组中还有空闲空间,也没法继续往队列中添加数据了。这个问题该如何解决呢?
当出现这种状况的时候咱们就须要作数据迁移。如图所示:当 abcd 入队后,对应的指针位置。
如今咱们执行出队操做
当咱们调用两次出队操做以后,队列中 head 指针指向下标为 2 的位置,tail 指针仍然指向下标为 4 的位置。
迁移操做其实就是把整段数据移动到数组 0 开始的位置。
具体代码以下
/** * 数组实现队列 */ public class ArrayQueue<E> extends AbstractQueue<E> { /** * The queued items */ final E[] items; /** * 队头指针 */ private int front; /** * 队尾指针 */ private int rear; /** * Creates an ArrayQueue with the given capacity * * @param capacity the capacity of this queue */ public ArrayQueue(Class<E> type, int capacity) { if (capacity <= 0) { throw new IllegalArgumentException(); } this.items = (E[]) Array.newInstance(type, capacity); } public int capacity() { return items.length; } @Override public E dequeue() { if (front == rear) { throw new IllegalStateException("Queue empty"); } return items[front++]; } @Override public boolean enqueue(E e) { if (isFull()) { throw new IllegalStateException("Queue empty"); } // 队尾没有空间了,须要执行数据迁移 if (rear == capacity()) { // 数据迁移 if (rear - front >= 0) { System.arraycopy(items, front, items, 0, rear - front); } // 调整 front 与 rear rear -= front; front = 0; } items[rear++] = e; return true; } @Override public boolean isFull() { return rear == capacity() && front == 0; } @Override public boolean isEmpty() { return front == rear; } }
咱们能够经过以前学习过的链表来实现队列,具体详见单向链表篇 。其实主要就是利用了 出队就是链表头删除数据,入队就是尾节点添加数据
public class LinkedQueue<E> extends AbstractQueue<E> implements Queue<E> { private final SingleLinkedList<E> linkedList; public LinkedQueue() { this.linkedList = new SingleLinkedList<>(); } @Override public E dequeue() { if (linkedList.isEmpty()) { throw new IllegalStateException("Queue empty"); } return linkedList.remove(); } @Override public boolean enqueue(E e) { return linkedList.add(e); } @Override public boolean isFull() { return false; } @Override public boolean isEmpty() { return linkedList.isEmpty(); } }
刚刚的例子,当 rear == capacity
的时候,会出现数据迁移操做,这样性能受到影响,那如何避免呢?
本来数组是有头有尾的,是一条直线。如今咱们把首尾相连,扳成了一个环。
咱们能够看到,图中这个队列的大小为 8,当前 head=4,tail=7。当有一个新的元素 a 入队时,咱们放入下标为 7 的位置。但这个时候,咱们并不把 tail 更新为 8,而是将其在环中后移一位,到下标为 0 的位置。当再有一个元素 b 入队时,咱们将 b 放入下标为 0 的位置,而后 tail 加 1 更新为 1。因此,在 a,b 依次入队以后,循环队列中的元素就变成了下面的样子:
队列为空的判断依然是 front == rear,队列满的条件则是 (rear + 1) % capacity = front
你有没有发现,当队列满时,图中的 tail 指向的位置其实是没有存储数据的。因此,循环队列会浪费一个数组的存储空间。
/** * 数组实现环形队列 * * @param <E> */ public class ArrayCircleQueue<E> extends AbstractQueue<E> { /** * The queued items */ final E[] items; /** * 队头指针 */ private int front; /** * 队尾指针 */ private int rear; public int capacity() { return items.length; } /** * Creates an ArrayQueue with the given capacity * * @param capacity the capacity of this queue */ public ArrayCircleQueue(Class<E> type, int capacity) { if (capacity <= 0) { throw new IllegalArgumentException(); } this.items = (E[]) Array.newInstance(type, capacity); } @Override public E dequeue() { if (front == rear) { throw new IllegalStateException("Queue empty"); } E item = items[front]; front = (front + 1) % items.length; return item; } @Override public boolean enqueue(E e) { checkNotNull(e); int newRear = (rear + 1) % items.length; if (newRear == front) { throw new IllegalStateException("Queue full"); } items[rear] = e; this.rear = newRear; return true; } @Override public boolean isFull() { return (rear + 1) % items.length == front; } @Override public boolean isEmpty() { return rear == front; } }
4.线性表之数组
5.链表导论-心法篇
7.双向链表正确实现
原创不易,以为有用但愿随手「在看」「收藏」「转发」三连。