谈谈集合.Queue
之前说到,Java中集合的主要作用就是装盛其他数据和实现常见的数据结构。所以当我们要用到“栈”、“队列”、“链表”和“数组”等常见的数据结构时就应该想到可以直接使用JDK给我们提供的集合框架。比如说当我们想用到队列时就应该想到使用LinkedList和ArrayDeque。本篇博客将介绍Collection框架中的Queue。

Queue接口继承了Collection接口,所以Collection的所有方法Queue的实现类中都包含,同时还有一个子接口Dqueue,表示双端队列。对于Queue我们主要掌握ArrayDeque和LinkedList,了解PriorityQueue(优先级队列)。
1. 接口介绍
Queue也是Java集合框架中定义的一种接口,直接继承自 Collection 接口。除了基本的 Collection 接口规定测操作外,Queue 接口还定义一组针对队列的特殊操作。通常来说,Queue是按照先进先出(FIFO)的方式来管理其中的元素的,但是优先队列是一个例外。
Deque接口继承自Queue接口,但Deque支持同时从两端添加或移除元素,因此又被成为双端队列。鉴于此,Deque接口的实现可以被当作FIFO队列使用,也可以当作LIFO队列(栈)来使用。官方也是推荐使用 Deque的实现来替代Stack。Deque的主要实现类有ArrayDeque和LinkedList。
ArrayDeque是Deque接口的一种具体实现,是依赖于可变数组来实现的。ArrayDeque没有容量限制,可根据需求自动进行扩容。ArrayDeque不支持值为null的元素。
LinkedList的具体特性已经在之前的博客中介绍过了,这边不再重新介绍了。
1.1 Queue接口概览
Queue可以被当做一个队列来使用,实现FIFO操作,主要提供下面的操作
public interface Queue<E> extends Collection<E> {
//向队列尾部插入一个元素,并返回true
//如果队列已满,抛出IllegalStateException异常
boolean add(E e);
//向队列尾部插入一个元素,并返回true
//如果队列已满,返回false
boolean offer(E e);
//取出队列头部的元素,并从队列中移除
//队列为空,抛出NoSuchElementException异常
E remove();
//取出队列头部的元素,并从队列中移除
//队列为空,返回null
E poll();
//取出队列头部的元素,但并不移除
//如果队列为空,抛出NoSuchElementException异常
E element();
//取出队列头部的元素,但并不移除
//队列为空,返回null
E peek();
}
1.2 Deque接口
Deque接口是一个双端队列,可以对队列的头尾进行操作,所以也可以当做栈来使用。
下面的表格列举了Queue和Deque接口的相对应方法
| Queue方法 | Deque方法 |
|---|---|
| add(e) | addLast(e) |
| offer(e) | offerLast(e) |
| remove() | removeFirst() |
| poll() | pollFirst() |
| element() | getFirst() |
| peek() | peekFirst() |
Deque还有一个重要的功能是可以当做栈来使用
| Stack方法 | Deque方法 |
|---|---|
| push(e) | addFirst(e) |
| pop() | removeFirst() |
| peek() | peekFirst() |
2. ArrayDeque
ArrayDeque是Deque基于数组的实现。
以下内容来源于网络博客
2.1 ArrayDeque的成员变量
//数组存储元素
transient Object[] elements;
//头部元素索引
transient int head;
//尾部元素索引
transient int tail;
//最小容量
private static final int MIN_INITIAL_CAPACITY = 8;
ArrayDeque底层使用数组存储元素,同时还使用head和tail来表示索引,但注意tail不是尾部元素的索引,而是尾部元素的下一位,即下一个将要被加入的元素的索引。
2.2 初始化
public ArrayDeque() {
elements = new Object[16];
}
public ArrayDeque(int numElements) {
allocateElements(numElements);
}
public ArrayDeque(Collection<? extends E> c) {
allocateElements(c.size());
addAll(c);
}
private void allocateElements(int numElements) {
int initialCapacity = MIN_INITIAL_CAPACITY;
// Find the best power of two to hold elements.
// Tests "<=" because arrays aren't kept full.
if (numElements >= initialCapacity) {
initialCapacity = numElements;
initialCapacity |= (initialCapacity >>> 1);
initialCapacity |= (initialCapacity >>> 2);
initialCapacity |= (initialCapacity >>> 4);
initialCapacity |= (initialCapacity >>> 8);
initialCapacity |= (initialCapacity >>> 16);
initialCapacity++;
if (initialCapacity < 0) // Too many elements, must back off
initialCapacity >>>= 1;// Good luck allocating 2 ^ 30 elements
}
elements = new Object[initialCapacity];
}
这边讲下private void allocateElements(int numElements)这个方法。ArrayDeque的初始化容量必须是2^n。所以你传的初始化容量如果是10,那么实际申请的数组容量是16,如果申请的容量是33,那么实际的容量是62。如果申请的容量是62,那么实际申请的容量是128。
2.3 add方法
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();
//tail中保存的是即将加入末尾的元素的索引
elements[tail] = e;
//tail向后移动一位
if ( (tail = (tail + 1) & (elements.length - 1)) == head)
//tail和head相遇,空间用尽,需要扩容
doubleCapacity();
}
在存储的过程中,这里有个有趣的算法,就是tail的计算公式(tail = (tail + 1) & (elements.length - 1)),注意这里的存储采用的是环形队列的形式,也就是当tail到达容量最后一个的时候,tail就为等于0,否则tail的值tail+1。
head也采用了类似的方式,每次在头部添加元素后,head都会指向最新被添加进去的那个元素所在的位置。当head小于0时也会采取环的形式存元素,比如head已经指向位置0,又向队列中头部添加一个元素后,head会变成length-1。

关于head和tail,需要主要的是head永远指向第一个元素的索引位置,tail永远指向尾部位置(这个位置上暂时还没有元素,如果在尾部插入元素,则在这个位置上插入)
2.4 扩容机制
private void doubleCapacity() {
assert head == tail; //扩容时头部索引和尾部索引肯定相等
int p = head;
int n = elements.length;
//头部索引到数组末端(length-1处)共有多少元素
int r = n - p; // number of elements to the right of p
//容量翻倍
int newCapacity = n << 1;
//容量过大,溢出了
if (newCapacity < 0)
throw new IllegalStateException("Sorry, deque too big");
//分配新空间
Object[] a = new Object[newCapacity];
//复制头部索引到数组末端的元素到新数组的头部
System.arraycopy(elements, p, a, 0, r);
//复制其余元素
System.arraycopy(elements, 0, a, r, p);
elements = a;
//重置头尾索引
head = 0;
tail = n;
}
下图是扩容的示意图

3. 总结
ArrayDeque是Deque 接口的一种具体实现,是依赖于可变数组来实现的。ArrayDeque 没有容量限制,可根据需求自动进行扩容。ArrayDeque 可以作为栈来使用,效率要高于Stack;ArrayDeque 也可以作为队列来使用,效率相较于基于双向链表的LinkedList也要更好一些。
所以我们程序中如果要使用到“队列”和“栈”这种数据结构,我们要首先想到LinkedList和ArrayDeque。个人认为作为队列和栈来使用的话,两者性能相差不大,但是ArrayDeque需要扩容,还需要申请连续的内存空间,所以个人更推荐使用LinkedList,不知道我的理解对不对。
公众号推荐
欢迎大家关注我的微信公众号「程序员自由之路」

谈谈集合.Queue的更多相关文章
- 先进先出集合queue
先进先出集合queue Enqueue添加到集合最后 Dequeue移除集合第一个对象并返回
- 【由浅入深理解java集合】(四)——集合 Queue
今天我们来介绍下集合Queue中的几个重要的实现类.关于集合Queue中的内容就比较少了.主要是针对队列这种数据结构的使用来介绍Queue中的实现类. Queue用于模拟队列这种数据结构,队列通常是指 ...
- Java集合—Queue(转载)
Queue用于模拟队列这种数据结构,队列通常是指“先进先出”(FIFO)的容器.新元素插入(offer)到队列的尾部,访问元素(poll)操作会返回队列头部的元素.通常,队列不允许随机访问队列中的元素 ...
- Stack集合 Queue队列集合 Hashtable哈希表
Stack集合 干草堆集合 栈集合 栈;stack,先进后出,一个一个赋值,一个一个取值,安装顺序来. 属性和方法 实例化 初始化 Stack st = new Stack(); 添加元素 个数 Co ...
- 【Java 基础】谈谈集合.List
目录 1. ArrayList 1.1 ArrayList的构造 1.2 add方法 1.3 remove方法 1.4 查询方法 1.5 一些其他常用方法 1.6 ArrayList小结 2. Vec ...
- Java中的集合Queue、LinkedList、PriorityQueue(四)
Queue接口 Queue用于模拟了队列这种数据结构,队列通常是指“先进先出”(FIFO)的容器.队列的头部保存在队列中时间最长的元素,队列的尾部保存在队列中时间最短的元素.新元素插入(offer)到 ...
- 【Java基础】谈谈集合.List
摘自:https://www.cnblogs.com/54chensongxia/p/11722828.html 目录 1. ArrayList 1.1 ArrayList的构造 1.2 add方法 ...
- 谈谈集合.Map
本文来谈谈我们平时使用最多的HashMap. 1. 简介 HashMap是我们在开发过程中用的最多的一个集合结构,没有之一.HashMap实现了Map接口,内部存放Key-Value键值对,支持泛型. ...
- java集合--Queue用法
队列是一种特殊的线性表,它只允许在表的前端(front)进行删除操作,而在表的后端(rear)进行插入操作.进行插入操作的端称为队尾,进行删除操作的端称为队头.队列中没有元素时,称为空队列. 在队列这 ...
随机推荐
- linux查看并发连接数
1.查看TCP的并发请求数及其TCP连接状态: netstat -n | awk '/^tcp/ {++S[$NF]} END {for(a in S) print a, S[a]}' netstat ...
- numpy矩阵运算--矩阵乘法
1)元素对应相乘,使用 multiply 函数或 * 运算符来实现 a = np.array([2,2,2])b = np.array([3,3,3]) c1 = a*a c1 array([4, 4 ...
- C#阶段小结
一.数据类型: (一)内建类型: 整型(int ,short, long ,byte ,uint ,ushort, ulong ,sbyte): 浮点型(double float decimal): ...
- mysql中造3千条数据(3种方法)
方法一:存储过程 1.存储过程如下: delimiter $$ DROP PROCEDURE IF EXISTS data CREATE PROCEDURE data(in i int) BEGIN ...
- 企业框架-Spring
1.什么是Spring Spring是最受欢迎的企业级Java应用程序开发框架,数以百万的来自世界各地的开发人员使用Spring框架来创建性能好.易于测试.可重用的代码. Spring框架是一个开源的 ...
- abstract class
在面向对象(OOP)语言中,一个类可以有一个或多个子类,而每个类都有至少一个公有方法作为外部代码访问的接口.而抽象方法就是为了方便继承而引入的,现在来看一下抽象类和抽象方法分别是如何定义以及他们的特点 ...
- sshd启动故障“Failed to start OpenSSH Server daemon ”解决方法
- [LC] 74. Search a 2D Matrix
Write an efficient algorithm that searches for a value in an m x n matrix. This matrix has the follo ...
- Log4j输出的日志乱码问题
设置日志输出编码: log4j.appender.stdout.Encoding=UTF-8
- 制作MACOSX 10.9Mavericks安装启动U盘教程
苹果OS X Mavericks发布了,不少朋友又开始制作系统安装U盘了.其实下面介绍的方法与之前介绍的方法可能有些不同,不过原理还是一样的.下面看看具体的操作步骤吧! 1. 首先你需要开启显示隐藏文 ...