● 队列的定义
队列(Queue):也是运算受限的线性表。是一种先进先出(First In First Out ,简称FIFO)的线性表。只允许在表的一端front进行插入,而在另一端rear进行删除。
队首(front) :允许进行删除的一端称为队首。
队尾(rear) :允许进行插入的一端称为队尾。
例如:排队购物。操作系统中的作业排队。先进入队列的成员总是先离开队列。
队列中没有元素时称为空队列。在空队列中依次加入元素a1, a2, …, an之后,a1是队首元素,an是队尾元素。显然退出队列的次序也只能是a1, a2, …, an ,即队列的修改是依先进先出的原则进行的,如图所示。
●顺序队列
顺序队列存储结构称为顺序队列,顺序队列实际上是运算受限的顺序表
顺序队列的表示
和顺序表一样,顺序队列利用内存中一段连续的存储空间来存放当前队列中的元素。由于队列的队头和队尾是变化的,设置两个指针front和real分别指示队头和队尾元素,它们的初值在队列初始化时应置为0
顺序队列的基本操作
入队时:将新元素插入rear所指的位置的后一位
出队时:删去front所指的元素,然后将front加1并返回被删元素
顺序表的溢出现象
- 下溢: 当队列为空时,做出队运算产生的溢出现象。下溢是正常现象,常用作程序控制转移的条件。
- 真上溢:当队列满时,做进栈运算产生空间溢出的现象。真上溢是一种出错状态,应设法避免。
- 假上溢:由于入队和出对操作中,头尾指针只增加不减小,致使被删元素的空间永远无法被重新利用。当队列中的实际元素的个数远远小于内存中分配的空间时,也可能由于尾指针已超越向量空间的上界而不能做入队操作。
• 顺序队列的实现
package lang;
import java.io.Serializable;
import java.util.Arrays;
/**
* @ClassName: ArrayQueue
* @Description: 顺序队列
* @param <T>
*/
public class ArrayQueue<T> implements Serializable{
/**
* @Fields serialVersionUID
*/
private static final long serialVersionUID = 7333344126529379197L;
private int DEFAULT_SIZE = 10;
private int capacity;//保存数组的长度
private Object[] elementData;//定义一个数组用于保存顺序队列的元素
private int front = 0;//队头
private int rear = 0;//队尾
//以默认数组长度创建空顺序队列
public ArrayQueue() {
capacity = DEFAULT_SIZE;
elementData = new Object[capacity];
}
//以一个初始化元素来创建顺序队列
public ArrayQueue(T element) {
this();
elementData[0] = element;
rear++;
}
public ArrayQueue(int initSize) {
elementData = new Object[initSize];
}
/**
* 以指定长度的数组来创建顺序队列
* @param element 指定顺序队列中第一个元素
* @param initSize 指定顺序队列底层数组的长度
*/
public ArrayQueue(T element, int initSize) {
this.capacity = initSize;
elementData = new Object[capacity];
elementData[0] = element;
rear++;
}
/**
* @Title: size
* @Description: 获取顺序队列的大小
* @return
*/
public int size() {
return rear - front;
}
/**
* @Title: offer
* @Description: 入队
* @param element
*/
public void offer(T element) {
ensureCapacity(rear + 1);
elementData[rear++] = element;
}
private void ensureCapacity(int minCapacity) {
//如果数组的原有长度小于目前所需的长度
int oldCapacity = elementData.length;
if (minCapacity > oldCapacity) {
int newCapacity = (oldCapacity * 3) / 2 + 1;
if (newCapacity < minCapacity)
newCapacity = minCapacity;
// minCapacity is usually close to size, so this is a win:
elementData = Arrays.copyOf(elementData, newCapacity);
}
}
/**
* @Title: poll
* @Description: 出队
* @return
*/
public T poll() {
if (isEmpty()) {
throw new IndexOutOfBoundsException("空队列异常");
}
//保留队列的front端的元素的值
T oldValue = (T) elementData[front];
//释放队列的front端的元素
elementData[front++] = null;
return oldValue;
}
/**
* @Title: peek
* @Description: 返回队列顶元素,但不删除队列顶元素
* @return
*/
public T peek() {
if (isEmpty()) {
throw new IndexOutOfBoundsException("空队列异常");
}
return (T) elementData[front];
}
/**
* @Title: isEmpty
* @Description: 判断顺序队列是否为空队列
* @return
*/
public boolean isEmpty() {
return rear == front;
}
/**
* @Title: clear
* @Description: 清空顺序队列
*/
public void clear() {
//将底层数组所有元素赋为null
Arrays.fill(elementData, null);
front = 0;
rear = 0;
}
public String toString() {
if (isEmpty()) {
return "[]";
} else {
StringBuilder sb = new StringBuilder("[");
for (int i = front; i < rear; i++) {
sb.append(elementData[i].toString() + ", ");
}
int len = sb.length();
return sb.delete(len - 2, len).append("]").toString();
}
}
}
● 循环队列
如下图所示,这种头尾相连的顺序存储结构称为循环队列(circular queue)
循环队列中需要注意的几个重要问题
- 队空的判定条件,队空的判定条件是front=rear
- 堆满的判定条件是(rear+1)%QueueSize=front
- 队列长度计算公式 (rear-front+QueueSize)%QueueSize
• 循环队列的实现1
package lang;
import java.io.Serializable;
import java.util.Arrays;
/**
* @ClassName: LoopQueue
* @Description: 循环队列
*/
public class LoopQueue<T> implements Serializable{
/**
* @Fields serialVersionUID
*/
private static final long serialVersionUID = -3670496550272478781L;
private int DEFAULT_SIZE = 10;
private int capacity;//保存数组的长度
private Object[] elementData;//定义一个数组用于保存循环队列的元素
private int front = 0;//队头
private int rear = 0;//队尾
//以默认数组长度创建空循环队列
public LoopQueue() {
capacity = DEFAULT_SIZE;
elementData = new Object[capacity];
}
//以一个初始化元素来创建循环队列
public LoopQueue(T element) {
this();
elementData[0] = element;
rear++;
}
/**
* 以指定长度的数组来创建循环队列
* @param element 指定循环队列中第一个元素
* @param initSize 指定循环队列底层数组的长度
*/
public LoopQueue(T element, int initSize) {
this.capacity = initSize;
elementData = new Object[capacity];
elementData[0] = element;
rear++;
}
//获取循环队列的大小
public int size() {
if (isEmpty()) {
return 0;
}
return rear > front ? rear - front : capacity - (front - rear);
}
//插入队列
public void add(T element) {
if (rear == front && elementData[front] != null) {
throw new IndexOutOfBoundsException("队列已满的异常");
}
elementData[rear++] = element;
//如果rear已经到头,那就转头
rear = rear == capacity ? 0 : rear;
}
//移除队列
public T remove() {
if (isEmpty()) {
throw new IndexOutOfBoundsException("空队列异常");
}
//保留队列的rear端的元素的值
T oldValue = (T) elementData[front];
//释放队列的rear端的元素
elementData[front++] = null;
//如果front已经到头,那就转头
front = front == capacity ? 0 : front;
return oldValue;
}
//返回队列顶元素,但不删除队列顶元素
public T element() {
if (isEmpty()) {
throw new IndexOutOfBoundsException("空队列异常");
}
return (T) elementData[front];
}
//判断循环队列是否为空队列
public boolean isEmpty() {
//rear==front且rear处的元素为null
return rear == front && elementData[rear] == null;
}
//清空循环队列
public void clear() {
//将底层数组所有元素赋为null
Arrays.fill(elementData, null);
front = 0;
rear = 0;
}
public String toString() {
if (isEmpty()) {
return "[]";
} else {
//如果front < rear,有效元素就是front到rear之间的元素
if (front < rear) {
StringBuilder sb = new StringBuilder("[");
for (int i = front; i < rear; i++) {
sb.append(elementData[i].toString() + ", ");
}
int len = sb.length();
return sb.delete(len - 2, len).append("]").toString();
}
//如果front >= rear,有效元素为front->capacity之间、0->front之间的
else {
StringBuilder sb = new StringBuilder("[");
for (int i = front; i < capacity; i++) {
sb.append(elementData[i].toString() + ", ");
}
for (int i = 0; i < rear; i++) {
sb.append(elementData[i].toString() + ", ");
}
int len = sb.length();
return sb.delete(len - 2, len).append("]").toString();
}
}
}
public static void main(String[] args) {
LoopQueue<String> queue = new LoopQueue<String>("aaaa", 3);
//添加两个元素
queue.add("bbbb");
queue.add("cccc");
//此时队列已满
System.out.println(queue);
//删除一个元素后,队列可以再多加一个元素
queue.remove();
System.out.println("删除一个元素后的队列:" + queue);
//再次添加一个元素,此时队列又满
queue.add("dddd");
System.out.println(queue);
System.out.println("队列满时的长度:" + queue.size());
//删除一个元素后,队列可以再多加一个元素
queue.remove();
//再次加入一个元素,此时队列又满
queue.add("eeee");
System.out.println(queue);
}
}
• 循环队列的实现2
"使用Java 来模拟循环队列的实现原理"
package org.moxiu.StackandQueue;
/**
* 模拟循环队列的实现
*/
public class CircularQueue<E> {
//对象数组,队列最多存储a.length-1个对象
private Object[] element;
//默认初始化大小
private static final int DEFAULT_SIZE=10;
//队首下标
int front;
//队尾下标
int rear;
// 定义无参构造函数,默认长度为10
public CircularQueue(){
this(DEFAULT_SIZE);
}
//定义有参构造函数
public CircularQueue(int len){
element = new Object[len];
front = 0;
rear=0;
}
/**
*将一个对象追加到队列尾部
*@param obj
* return 队列满时返回false,否则返回true
*/
public boolean push(E obj){
if((rear+1)%element.length==front){
return false;
}else{
element[rear]=obj;
rear=(rear+1)%element.length;
return true;
}
}
/**
*将一个队列的头部元素出队。
* @return
* @author gyg
*/
public Object pop(){
if(rear == front){
return null;
}else{
Object obj = element[front];
element[front] = null;
front=(front+1)%element.length;
return obj;
}
}
/**
* 获取队列长度
* @param
*/
public int size(){
if(rear>front)
{
return rear-front;
}
return front-rear;
}
/**
* 判断队列是否为空
* @param
*/
public boolean isEmpty(){
return rear==front;
}
public static void main(String[] args){
CircularQueue queue = new CircularQueue(5);
queue.push("3");
queue.push("5");
System.out.println(queue.size()); //打印队列的长度
System.out.print(queue.isEmpty()); //判断队列是否为空
queue.pop(); //队列的头元素出队
int size = queue.size(); //使用变量定义队列的长度
for(int i=0;i<size;i++)
{
System.out.print(queue.pop()); //输出队列中所有的元素。
}
}
● 链式存储队列
队列的链式存储结构,其实就是线性表的单链表,只不过它只能尾进头出而已,我们把它简称为链队列。
链队列的存储形式
• 链式存储队列的实现 1
package lang;
import java.io.Serializable;
/**
* @ClassName: LinkQueue
* @Description: 链式队列
* @param <T>
*/
public class LinkQueue<T> implements Serializable{
/**
* @Fields serialVersionUID
*/
private static final long serialVersionUID = -6726728595616312615L;
//定义一个内部类Node,Node实例代表链队列的节点。
private class Node {
private T data;//保存节点的数据
private Node next;//指向下个节点的引用
//无参数的构造器
public Node() {
}
//初始化全部属性的构造器
public Node(T data, Node next) {
this.data = data;
this.next = next;
}
}
private Node front;//保存该链队列的头节点
private Node rear;//保存该链队列的尾节点
private int size;//保存该链队列中已包含的节点数
/**
* <p>Title: LinkQueue </p>
* <p>Description: 创建空链队列 </p>
*/
public LinkQueue() {
//空链队列,front和rear都是null
front = null;
rear = null;
}
/**
* <p>Title: LinkQueue </p>
* <p>Description: 以指定数据元素来创建链队列,该链队列只有一个元素</p>
*/
public LinkQueue(T element) {
front = new Node(element, null);
//只有一个节点,front、rear都指向该节点
rear = front;
size++;
}
/**
* @Title: size
* @Description: 获取顺序队列的大小
* @return
*/
public int size() {
return size;
}
/**
* @Title: offer
* @Description: 入队
* @param element
*/
public void offer(T element) {
//如果该链队列还是空链队列
if (front == null) {
front = new Node(element, null);
rear = front;//只有一个节点,front、rear都指向该节点
} else {
Node newNode = new Node(element, null);//创建新节点
rear.next = newNode;//让尾节点的next指向新增的节点
rear = newNode;//以新节点作为新的尾节点
}
size++;
}
/**
* @Title: poll
* @Description: 出队
* @return
*/
public T poll() {
Node oldFront = front;
front = front.next;
oldFront.next = null;
size--;
return oldFront.data;
}
/**
* @Title: peek
* @Description: 返回队列顶元素,但不删除队列顶元素
* @return
*/
public T peek() {
return rear.data;
}
/**
* @Title: isEmpty
* @Description: 判断顺序队列是否为空队列
* @return
*/
public boolean isEmpty() {
return size == 0;
}
/**
* @Title: clear
* @Description: 清空顺序队列
*/
public void clear() {
//将front、rear两个节点赋为null
front = null;
rear = null;
size = 0;
}
public String toString() {
//链队列为空链队列时
if (isEmpty()) {
return "[]";
} else {
StringBuilder sb = new StringBuilder("[");
for (Node current = front; current != null; current = current.next) {
sb.append(current.data.toString() + ", ");
}
int len = sb.length();
return sb.delete(len - 2, len).append("]").toString();
}
}
public static void main(String[] args) {
LinkQueue<String> queue = new LinkQueue<String>("aaaa");
//添加两个元素
queue.offer("bbbb");
queue.offer("cccc");
System.out.println(queue);
//删除一个元素后
queue.poll();
System.out.println("删除一个元素后的队列:" + queue);
//再次添加一个元素
queue.offer("dddd");
System.out.println("再次添加元素后的队列:" + queue);
//删除一个元素后,队列可以再多加一个元素
queue.poll();
//再次加入一个元素
queue.offer("eeee");
System.out.println(queue);
}
}
• 链式存储队列的实现 2
/**
* 被类用于模拟链队列用java的实现
*/
public class LinkQueue<T> {
//链的数据结构
private class Node{
public T data;
public Node next;
//无参构造函数
public Node(){}
//有参构造函数
public Node(T data,Node next){
this.data=data;
this.next=next;
}
}
//队列头指针
private Node front;
//队列尾指针
private Node rear;
//队列长度
private int size=0;
public LinkQueue(){
Node n = new Node(null,null);
n.next=null;
front=rear=n;
}
/**
* 队列入队算法
*/
public void push(T data){
//创建一个节点
Node s = new Node(data,null);
//将尾指针指向新加入的节点,将s节点插入队尾
rear.next=s;
rear=s;
size++;
}
/**
* 队列出队算法
*/
public Object pop(){
if(rear==front){
try{
throw new Exception("堆栈为空");
}catch(Exception e){
e.printStackTrace();
}
return null;
}else{
//定义变量存储队头元素值
Node p = front.next;
T x = p.data;
//将队头元素所在的节点摘链
front.next=p.next;
//判断出队列长度是否为1
if(p.next==null)
rear=front;
//删除节点
p=null;
size--;
return x;
}
}
/**
* 求出队列长度
*/
public int size(){
return size;
}
/**
* 判断队列是否为空
*/
public boolean isEmpty(){
return size==0;
}
/**
* 重写toString方法
*/
public String toString(){
if(isEmpty()){
return "[]";
}else{
StringBuilder sb = new StringBuilder("[");
for(Node current=front.next;current!=null;current=current.next){
sb.append(current.data.toString()+",");
}
int len = sb.length();
return sb.delete(len - 2,len).append("]").toString();
}
}
//测试
public static void main(String[] args){
LinkQueue<Integer> queue=new LinkQueue<Integer>();
queue.push(1);
queue.push(2);
queue.push(3);
queue.push(4);
queue.push(5);
queue.push(6);
System.out.println(queue);
System.out.println("出队:"+queue.pop());
System.out.println("队列长度="+queue.size());
System.out.println(queue);
System.out.println("出队:"+queue.pop());
System.out.println("队列长度="+queue.size());
System.out.println(queue);
System.out.println("出队:"+queue.pop());
System.out.println("队列长度="+queue.size());
System.out.println(queue);
}
}
●队列(Queue)接口层应用
Queue接口与List、Set同一级别,都是继承了Collection接口。LinkedList实现了Queue接 口,因此我们可以把LinkedList当成Queue来用。Queue接口窄化了对LinkedList的方法的访问权限(即在方法中的参数类型如果是Queue时,就完全只能访问Queue接口所定义的方法 了,而不能直接访问 LinkedList的非Queue的方法),以使得只有恰当的方法才可以使用。BlockingQueue 继承了Queue接口。
队列是一种数据结构.它有两个基本操作:在队列尾部加人一个元素,和从队列头部移除一个元素就是说,队列以一种先进先出的方式管理数据,如果你试图向一个 已经满了的阻塞队列中添加一个元素或者是从一个空的阻塞队列中移除一个元索,将导致线程阻塞.在多线程进行合作时,阻塞队列是很有用的工具。工作者线程可 以定期地把中间结果存到阻塞队列中而其他工作者线线程把中间结果取出并在将来修改它们。队列会自动平衡负载。如果第一个线程集运行得比第二个慢,则第二个 线程集在等待结果时就会阻塞。如果第一个线程集运行得快,那么它将等待第二个线程集赶上来。下表显示了jdk1.5中的阻塞队列的操作:
add 增加一个元索 如果队列已满,则抛出一个IIIegaISlabEepeplian异常
remove 移除并返回队列头部的元素 如果队列为空,则抛出一个NoSuchElementException异常
element 返回队列头部的元素 如果队列为空,则抛出一个NoSuchElementException异常
offer 添加一个元素并返回true 如果队列已满,则返回false
poll 移除并返问队列头部的元素 如果队列为空,则返回null
peek 返回队列头部的元素 如果队列为空,则返回null
put 添加一个元素 如果队列满,则阻塞
take 移除并返回队列头部的元素 如果队列为空,则阻塞
remove、element、offer 、poll、peek 其实是属于Queue接口。
阻塞队列的操作可以根据它们的响应方式分为以下三类:aad、removee和element操作在你试图为一个已满的队列增加元素或从空队列取得元素时 抛出异常。当然,在多线程程序中,队列在任何时间都可能变成满的或空的,所以你可能想使用offer、poll、peek方法。这些方法在无法完成任务时 只是给出一个出错示而不会抛出异常。
注意:poll和peek方法出错进返回null。因此,向队列中插入null值是不合法的。
还有带超时的offer和poll方法变种,例如,下面的调用:
boolean success = q.offer(x,100,TimeUnit.MILLISECONDS);
尝试在100毫秒内向队列尾部插入一个元素。如果成功,立即返回true;否则,当到达超时进,返回false。同样地,调用:
Object head = q.poll(100, TimeUnit.MILLISECONDS);
如果在100毫秒内成功地移除了队列头元素,则立即返回头元素;否则在到达超时时,返回null。
最后,我们有阻塞操作put和take。put方法在队列满时阻塞,take方法在队列空时阻塞。
java.ulil.concurrent包提供了阻塞队列的4个变种。默认情况下,LinkedBlockingQueue的容量是没有上限的(说的不准确,在不指定时容量为Integer.MAX_VALUE,不要然的话在put时怎么会受阻呢),但是也可以选择指定其最大容量,它是基于链表的队列,此队列按 FIFO(先进先出)排序元素。
• ArrayBlockingQueue在构造时需要指定容量, 并可以选择是否需要公平性,如果公平参数被设置true,等待时间最长的线程会优先得到处理(其实就是通过将ReentrantLock设置为true来 达到这种公平性的:即等待时间最长的线程会先操作)。通常,公平性会使你在性能上付出代价,只有在的确非常需要的时候再使用它。它是基于数组的阻塞循环队 列,此队列按 FIFO(先进先出)原则对元素进行排序。
• PriorityBlockingQueue是一个带优先级的 队列,而不是先进先出队列。元素按优先级顺序被移除,该队列也没有上限(看了一下源码,PriorityBlockingQueue是对 PriorityQueue的再次包装,是基于堆数据结构的,而PriorityQueue是没有容量限制的,与ArrayList一样,所以在优先阻塞 队列上put时是不会受阻的。虽然此队列逻辑上是无界的,但是由于资源被耗尽,所以试图执行添加操作可能会导致 OutOfMemoryError),但是如果队列为空,那么取元素的操作take就会阻塞,所以它的检索操作take是受阻的。另外,往入该队列中的元 素要具有比较能力。
最后,DelayQueue(基于PriorityQueue来实现的)是一个存放Delayed 元素的无界阻塞队列,只有在延迟期满时才能从中提取元素。该队列的头部是延迟期满后保存时间最长的 Delayed 元素。如果延迟都还没有期满,则队列没有头部,并且poll将返回null。当一个元素的 getDelay(TimeUnit.NANOSECONDS) 方法返回一个小于或等于零的值时,则出现期满,poll就以移除这个元素了。此队列不允许使用 null 元素。 下面是延迟接口:
Java代码
public interface Delayed extends Comparable<Delayed> {
long getDelay(TimeUnit unit);
}
放入DelayQueue的元素还将要实现compareTo方法,DelayQueue使用这个来为元素排序。
下面的实例展示了如何使用阻塞队列来控制线程集。程序在一个目录及它的所有子目录下搜索所有文件,打印出包含指定关键字的文件列表。从下面实例可以看出,使用阻塞队列两个显著的好处就是:多线程操作共同的队列时不需要额外的同步,另外就是队列会自动平衡负载,即那边(生产与消费两边)处理快了就会被阻塞掉,从而减少两边的处理速度差距。下面是具体实现:
public class BlockingQueueTest {
public static void main(String[] args) {
Scanner in = new Scanner(System.in);
System.out.print("Enter base directory (e.g. /usr/local/jdk5.0/src): ");
String directory = in.nextLine();
System.out.print("Enter keyword (e.g. volatile): ");
String keyword = in.nextLine();
final int FILE_QUEUE_SIZE = 10;// 阻塞队列大小
final int SEARCH_THREADS = 100;// 关键字搜索线程个数
// 基于ArrayBlockingQueue的阻塞队列
BlockingQueue<File> queue = new ArrayBlockingQueue<File>(
FILE_QUEUE_SIZE);
//只启动一个线程来搜索目录
FileEnumerationTask enumerator = new FileEnumerationTask(queue,
new File(directory));
new Thread(enumerator).start();
//启动100个线程用来在文件中搜索指定的关键字
for (int i = 1; i <= SEARCH_THREADS; i++)
new Thread(new SearchTask(queue, keyword)).start();
}
}
class FileEnumerationTask implements Runnable {
//哑元文件对象,放在阻塞队列最后,用来标示文件已被遍历完
public static File DUMMY = new File("");
private BlockingQueue<File> queue;
private File startingDirectory;
public FileEnumerationTask(BlockingQueue<File> queue, File startingDirectory) {
this.queue = queue;
this.startingDirectory = startingDirectory;
}
public void run() {
try {
enumerate(startingDirectory);
queue.put(DUMMY);//执行到这里说明指定的目录下文件已被遍历完
} catch (InterruptedException e) {
}
}
// 将指定目录下的所有文件以File对象的形式放入阻塞队列中
public void enumerate(File directory) throws InterruptedException {
File[] files = directory.listFiles();
for (File file : files) {
if (file.isDirectory())
enumerate(file);
else
//将元素放入队尾,如果队列满,则阻塞
queue.put(file);
}
}
}
class SearchTask implements Runnable {
private BlockingQueue<File> queue;
private String keyword;
public SearchTask(BlockingQueue<File> queue, String keyword) {
this.queue = queue;
this.keyword = keyword;
}
public void run() {
try {
boolean done = false;
while (!done) {
//取出队首元素,如果队列为空,则阻塞
File file = queue.take();
if (file == FileEnumerationTask.DUMMY) {
//取出来后重新放入,好让其他线程读到它时也很快的结束
queue.put(file);
done = true;
} else
search(file);
}
} catch (IOException e) {
e.printStackTrace();
} catch (InterruptedException e) {
}
}
public void search(File file) throws IOException {
Scanner in = new Scanner(new FileInputStream(file));
int lineNumber = 0;
while (in.hasNextLine()) {
lineNumber++;
String line = in.nextLine();
if (line.contains(keyword))
System.out.printf("%s:%d:%s%n", file.getPath(), lineNumber,
line);
}
in.close();
}
}
● 队列的多线程、线程安全
未学习
详见这个博客:
http://hellosure.iteye.com/blog/1126541