文章目录
1. Queue接口
在正式学习Queue之前,我们得先复习一下上面的图片。
Queue接口继承于Collection接口,但是它有具有自己的数据结构特点,Queue,队列,它的特性是先进先出。
下面我们看一下Queue接口有没有不同于Collection的特殊的定义:
//向队列添加元素并返回true,如果队列没有剩余空间,抛出异常
boolean add(E e);
//在容量充足的情况下添加元素,添加成功返回true,失败返回false
boolean offer(E e);
//从队列中取出元素并删除,如果队列为空,则抛出异常
E remove();
//从队列中取出元素并删除,如果队列为空,则返回null
E poll();
//取出队列的第一个元素,但不删除,如果队列为空,则抛出异常
E element();
//取出队列的第一个元素,但不删除,如果队列为空,则返回null
E peek();
1.1 Queue的特性
Queue(队列)最大的特性就是,先进先出,按元素存入的顺序排队处理,对这个数据结构的所有操作应遵循这个原理。
2.具体实现
从图中,我们可以猜到实现Queue接口的具体类会有:PriorityQueue
也可以猜出其也会有一个对应的AbstractQueue抽象类,提供了一些公用代码。
下面进行源码阅读验证一下自己的猜想是否正确
2.1 AbstractQueue
果然,JDK对于Queue接口提供了一些基础实现,这个类的实现的方法不多。
2.1.1 add方法
//接口的定义是:向队列中添加元素,如果队列满了,则抛出异常。
//可以看到它的实现遵循了接口中对这个方法的定义
public boolean add(E e) {
if (offer(e))
return true;
else
throw new IllegalStateException("Queue full");
}
2.1.2 remove方法
//接口的定义是:取出一个元素并删除,如果队列为空,则抛出异常
public E remove() {
E x = poll();
if (x != null)
return x;
else
throw new NoSuchElementException();
}
2.1.3 element方法
//接口的定义是:取出一个元素但不删除,如果队列为空,则抛出异常
public E element() {
E x = peek();
if (x != null)
return x;
else
throw new NoSuchElementException();
}
2.1.4 clear方法
//清空队列
public void clear() {
while (poll() != null)
;
}
2.1.5 addAll方法
// 将集合c中的元素都添加到队列中,如果c为空则抛出异常
// 如果c就是队列本身,也抛出异常
public boolean addAll(Collection<? extends E> c) {
if (c == null)
throw new NullPointerException();
if (c == this)
throw new IllegalArgumentException();
boolean modified = false;
for (E e : c)
if (add(e))
modified = true;
return modified;
}
2.2 PriorityQueue
这个类是Queue的一个具体实现,它实现的是名为优先队列的数据结构,它会根据优先级的设置来完进行进队,出队的操作。
2.2.1 存储原理
transient Object[] queue; // non-private to simplify nested class access
通过查阅源码我们可以得知,PriorityQueue使用的数组存储。
说到数组,我们就不得不说它的扩容机制了哈哈哈。
2.2.1.1 扩容机制
由于代码比较简单,我们就简单的浅读下它的扩容源码:
private void grow(int minCapacity) {
//获取queue原来的长度
int oldCapacity = queue.length;
// 如果oldCap小于64,则newCap = oldCap*2+2
// 如果oldCap>=64, 则newCap = oldCap*1.5
int newCapacity = oldCapacity + ((oldCapacity < 64) ?
(oldCapacity + 2) :
(oldCapacity >> 1));
// 超大容量检查处理
if (newCapacity - MAX_ARRAY_SIZE > 0)
newCapacity = hugeCapacity(minCapacity);
queue = Arrays.copyOf(queue, newCapacity);
}
关于PriorityQueue的扩容机制,总结起来就是:如果原始长度小于64,则扩1倍,否则扩0.5倍。
2.2.2 优先级机制
上面介绍了它的存储原理,下面来介绍它的优先级机制。
通过阅读构造方法源码,我们可以发现,它提供了下面类型的构造方法
//可以传入一个比较器,我们可以猜出这个比较器应该就是实现优先级机制的关键
public PriorityQueue(Comparator<? super E> comparator) {
this(DEFAULT_INITIAL_CAPACITY, comparator);
}
下面我们阅读一下它的offer方法:
public boolean offer(E e) {
if (e == null)
throw new NullPointerException();
modCount++;
int i = size;
if (i >= queue.length)
grow(i + 1);
size = i + 1;
if (i == 0)
queue[0] = e;
else
siftUp(i, e);//关键点,这个方法有啥用?
return true;
}
我们会发现一个名为siftUp方法,当我们点进去的时候:
//会发现,这里用到比较器了,它的逻辑是:
// 如果传入比较器了,则用传入的比较器,否则就用元素自身默认的比较器
// 那么问题来了,如果传入的元素并没有实现Comparable接口呢?
private void siftUp(int k, E x) {
if (comparator != null)
siftUpUsingComparator(k, x);
else
siftUpComparable(k, x);
}
我们进入siftUpComparable方法看看:
//它的逻辑是首先获取自己的比较器,然后进行优先位置调整
private void siftUpComparable(int k, E x) {
Comparable<? super E> key = (Comparable<? super E>) x;
while (k > 0) {
int parent = (k - 1) >>> 1;
Object e = queue[parent];
if (key.compareTo((E) e) >= 0)
break;
queue[k] = e;
k = parent;
}
queue[k] = key;
}
也就是说,每次进行元素入队的时候都要进行位置调整。
2.3.2 使用案例
PriorityQueue, 简单来说就是优先队列,下面我们陆续测试队列的功能和性能。
正常使用
public class PriorityQueueStudy {
public static void main(String[] args) {
Queue<Integer> queue = addTest();
System.out.println("队列大小:"+queue.size());
queue = pollTest(queue);
System.out.println("取出元素后队列大小:"+queue.size());
}
/**
* 添加元素进入队列
* @return
*/
public static Queue<Integer> addTest(){
long start = System.currentTimeMillis();
Queue<Integer> queue = new PriorityQueue<>();
for(int i=1000;i>0;i--){
boolean sus = queue.offer(i);
if(sus) System.out.println("入队成功!");
}
System.out.println("添加耗时:"+ (System.currentTimeMillis()-start)+"ms");
return queue;
}
public static Queue<Integer> pollTest(Queue<Integer> queue){
System.out.println("取出10个元素");
for(int i=0;i<10;i++){
System.out.print(queue.poll());
}
System.out.println();
return queue;
}
}
可以看到,当元素是Integer类型的时候,优先队列会默认使用Integer的比较器进行优先级排序。
非正常使用
下面我们试图向队列存入不实现Comparable接口的实例
public static Queue<PriorityQueueStudy> noCompareToTest(){
long start = System.currentTimeMillis();
Queue<PriorityQueueStudy> queue = new PriorityQueue<>();
for(int i=10;i>0;i--){
boolean sus = queue.offer(new PriorityQueueStudy());
if(sus) System.out.println("入队成功!");
}
System.out.println("添加耗时:"+ (System.currentTimeMillis()-start)+"ms");
return queue;
}
发现报错了,所以当我们使用优先队列的时候,这个点我们需要注意!!!
3.代码地址
Java基础学习/src/main/java/Progress/exa27_6 · 严家豆/Study - 码云 - 开源中国 (gitee.com)