7.5 Queue集合


Queue用于模拟队列这种数据结构,队列通常是指“先入先出”(FIFO)的容器。队列头部保存在队列中存放时间最长的元素,队列尾部通常保留在队列中存放时间最短的元素。新元素插入(offer)到队列的尾部,访问(poll)操作将会返回队列头部的元素。通常队列不允许随机访问队列中的元素。
Queue接口中定义的方法:
★void add(Object e):指定元素添加到队列的尾部
★Object element():获取队列的头部元素,不删除该元素
★boolean offer(Object e):将指定元素加入此队列的尾部。当使用容量限制的队列时,此方法通常比add()方法更好。
★Object peek():获取队列的头部元素,但不删除该元素。如果此队列为空,则返回null。
★Object poll():获取队列的头部元素,并删除该元素。如果此队列为空,则返回null。
★Object remove():获取队列头部元素,并删除该元素。

一、PriorityQueue实现类

1、基本方法使用

PriorityQueue是一个比较标准的队列实现类:PriorityQueue保存队列元素的顺序,并不是按加入队列的顺序,而是按队列元素的大小顺序重新排序。因此调用peek()或poll()方法取出队列中的元素时,并不是取出最先进入队列的元素,而是取出队列中最小的元素。从这个层面上分析,Priority已经违反了队列的基本规则:先入先出(FIFO)。

import java.util.PriorityQueue;
public class PriorityQueueTest 
{
	public static void main(String[] args) 
	{
		var pq=new PriorityQueue();
		//插入元素offer()比add()方法更好
                //Java常用包装类已经实现了Comparable接口中的抽象方法compareTo(Object obj)
		pq.offer(6);
		pq.offer(-9);
		pq.offer(20);
		pq.add(15);
		//返回队列得元素并不是按元素加入顺序排列的
		System.out.println(pq);//[-9, 6, 20, 15]
		//访问队列的第一个元素
		System.out.println(pq.peek());//此时不删除队列的第一个元素
		System.out.println(pq.element());//-9
		System.out.println(pq.poll());//-9
		System.out.println(pq);//[6, 15, 20]
	}
}

2、排序方式

PriorityQueue不允许插入null元素,它需要对队列元素排序,PriorityQueue的元素排序有两种用法:

2.1 自然排序:采用自然顺序的PriorityQueue集合中的元素必须实现Coparable接口,而且是同一个类的多个实例,否则引发ClassCastException异常

import java.util.PriorityQueue;
class  Z implements Comparable
{
	String str;
	public Z(String str)
	{
		this.str=str;
	}
	@Override
	public boolean equals(Object obj)
	{
		if(this==obj)
			return true;
		else if(obj!=null&&obj.getClass()==Z.class)
		{
			var r=(Z)obj;
			return r.str==this.str;
		}
		else
			return false;
	}
	@Override
	public int compareTo(Object obj)
	{
		var r=(Z)obj;
		return this.str.length()>r.str.length()?1:this.str.length()==r.str.length()?0:-1;
	}
	@Override
	public String toString()
	{
		return "Z[str="+str+"]";
	}
}
public class PriorityQueueSort1
{
	public static void main(String[] args)
	{
		var pq=new PriorityQueue();
		pq.offer(new Z("疯狂Java讲义"));
		pq.offer(new Z("疯狂Android讲义"));
		pq.offer(new Z("轻量级Java EE企业应用实战"));
		System.out.println(pq);

		//改变队列的第一个元素,排序不会改变
		var first=(Z)(pq.peek());
		first.str="相关学习网站:www.fkjava.com";
		System.out.println(pq);
	}
}
---------- 运行Java捕获输出窗 ----------
[Z[str=疯狂Java讲义], Z[str=疯狂Android讲义], Z[str=轻量级Java EE企业应用实战]]
[Z[str=相关学习网站:www.fkjava.com], Z[str=疯狂Android讲义], Z[str=轻量级Java EE企业应用实战]]

输出完成 (耗时 0 秒) - 正常终止

2.2 定制排序:创建定制排序时,传入一个Comparable对象,该对象负责对队列中的所有元素进行排序。采用定制排序时,不要求队列实现Comparable接口

import java.util.PriorityQueue;
class M 
{
	int age;
	public M(int age)
	{
		this.age=age;
	}
	@Override
	public String toString()
	{
		return "M[age:"+age+"]";
	}
}
public class PriorityQueueSort2
{
	public static void main(String[] args)
	{
		//此处传入lambda表达式的目标类型是Comparator
		var pq=new PriorityQueue((o1,o2)->{
			var m1=(M)o1;
			var m2=(M)o2;
			//age越大的排序越靠前
			return m1.age>m2.age?-1:m1.age<m2.age?1:0;
		});
		pq.offer(new M(1));
		pq.offer(new M(-8));
		pq.offer(new M(5));
		pq.offer(new M(20));
		System.out.println(pq);
	}
}
---------- 运行Java捕获输出窗 ----------
[M[age:20], M[age:5], M[age:1], M[age:-8]]

输出完成 (耗时 0 秒) - 正常终止

二、Deque接口与ArrayDeque实现类

Deque接口是Queue接口的子接口,它代表一个双端队列,Deque接口里定义了一些双端队列的操作方法,这些方法允许从两端来操作队列的元素。
★void addFirst(Object e):将指定元素插入到该双端队列的开头。
★void addLast(Object e):将指定元素插入到该双端队列的末尾。
★Iterator descendingIterator():放回该双端队列的对应的迭代器,该迭代器以逆向顺序来迭代队列中的元素。
★Object getFirst():获取但不删除双端队列的第一个元素。
★Object getLast():获取但不删除双端队列的最后一个元素。
★boolean offerFirst(Object e):将指定元素插入到双端队列的开头。
★boolean offerLast(Object e):将指定元素插入到双端队列的末尾。
★Object peekFirst():获取但不删除双端队列的第一个元素;如果此双端队列为空,则返回null。
★Object peekLast():获取但不删除双端队列的最后一个元素;如果此双端队列为空,则返回null。
★Object pollFirst():获取并删除双端队列的第一个元素;如果此双端队列为空,则返回null。
★Object pollLast():获取并删除双端队列的最后一个元素;如果此双端队列为空,则返回null。
★Object pop()(栈方法):pop出双端队列所表示的栈的栈顶元素。相当于removeFirst()。
★void push()(栈方法):将一个元素push进双端队列所表示的栈的栈顶。相当于addFirst()。
★Object removeFirst():获取并删除双端队列的第一个元素。
★Object removeFirstOccurrence(Object o):删除双端队列第一次出现的元素o。
★Object removeLast():获取并删除双端队列的最后一个元素。
★Object removeLastOccurence(Object o):删除双端队列最后一次出现的元素o。
Deque不仅可以当作双端队列使用,也可以被当作栈来使用,因为该类话包含了pop(出栈)、push(入栈)两个方法
Deque的方法与Queue方法对照表:

Queue方法 Deque方法
插入:add(e)/offer(e) addLast(e)/offerLast(e)
删除:remove()/poll() removeFirst()/pollFirst()
获取头部元素:element()/peek() getFirst()/peekFirst()

Deque的方法与Stack的方法对照表:

Stack的方法 Deque方法
进栈:push(e) addFirst()/offerFirst()
出栈:pop() removeFirst()/pollFirst()
获取栈顶元素:peek() getFirst()/peekFirst()

Deque接口提供了一个典型的实现类:ArrayDeque,它是一个基于数组实现的双端队列,创建Deque时,同样可以指定一个numElement参数,该参数用于指定Object[]数组的长度;如果不指定numElements参数,Deque底层数组的长度为16.

1、ArrayDeque当作栈使用

import java.util.ArrayDeque;
public class ArrayDequeTest 
{
	public static void main(String[] args) 
	{
		var stack=new ArrayDeque();
		//依次将三个元素push入栈
		stack.push("疯狂Java讲义");
		stack.push("轻量级Java EE企业应用实战");
		stack.push("疯狂Android讲义");
		System.out.println(stack);//[疯狂Android讲义, 轻量级Java EE企业应用实战, 疯狂Java讲义]

		//访问第一个元素但不删除
		System.out.println(stack.getFirst());//疯狂Android讲义
		System.out.println(stack.peekFirst());//疯狂Android讲义

		//访问第一个元素并删除
		System.out.println(stack.pop());//疯狂Android讲义
		System.out.println(stack);//[轻量级Java EE企业应用实战, 疯狂Java讲义]
	}
}

当程序中需要使用栈这种数据结构时,推荐使用ArrayDeque,尽量避免Stack这种古老的集合,性能比较差。

2、ArrayDeque当作队列使用

按照先入先出的方法来操作集合元素

import java.util.ArrayDeque;
public class ArrayDequeQueue
{
	public static void main(String[] args) 
	{
		var queue=new ArrayDeque();
		//将三个元素依次加入队列
		queue.offer("疯狂Java讲义");
		queue.offer("疯狂Java EE企业应用实战");
		queue.offer("疯狂Android讲义");
		System.out.println(queue);

		//访问头部元素,但不将它poll出栈
		System.out.println(queue.peek());//疯狂Java讲义
		//poll出第一个元素
		System.out.println(queue.poll());//疯狂Java讲义
		System.out.println(queue);//[疯狂Java EE企业应用实战, 疯狂Android讲义]

		//访问尾部元素,不删除
		System.out.println(queue.peekLast());//疯狂Android讲义
	}
}
#三、LinkedList实现类
LinkedList类是一个List接口的实现类——这意味着它是一个List集合,可以根据索引访问集合中的元素。除此之外,LinkedList还实现了Deque接口,还可以被当作双端队列来使用,因此既可以被当作栈来使用,也可以被当作队列使用。
```java
import java.util.LinkedList;
public class LinkedListTest
{
	public static void main(String[] args)
	{
		var books=new LinkedList();
		//将一个元素加入队列的尾部
		books.offer("疯狂Java讲义");
		//将一个字符串加入栈顶(相当于队列的头部)
		books.push("轻量级Java EE企业应用实战");
		System.out.println(books);
		//将字符串降入到队列的头部,相当于栈顶
		books.offerFirst("疯狂Android讲义");
		System.out.println(books);//[疯狂Android讲义, 轻量级Java EE企业应用实战, 疯狂Java讲义]

		//以List方式遍历集合元素
		for(int i=0;i<books.size();i++)
		{
			System.out.println(books.get(i));
		}
		//访问不删除栈顶元素
		System.out.println(books.peek());//疯狂Android讲义
		//访问不删除队列的最后一个元素
		System.out.println(books.peekLast());//疯狂Java讲义
		//将栈顶元素弹出栈
		System.out.println(books.pop());//疯狂Android讲义
		//删除队列最后一个元素
		System.out.println(books.pollLast());//疯狂Java讲义
	}
}

LinkedList与ArrayList、ArrayQueue的实现机制完全不同,ArrayList、ArrayQueue内部以数组的形式来保存结合中的元素,因此随机访问数组元素时具有较好的性能;而LinkedList内部以链表的形式来保存集合的元素,因此随机访问数组元素时性能较差,但在插入删除元素时性能比较出色(只改变指针所指的地址即可)。虽然Vector也是以数组形式存储集合元素的,但因为它实现了线程同步功能(而且实现机制也不好),所以各方方面性能比较差。

原文地址:https://www.cnblogs.com/weststar/p/12574120.html