27.6 Java集合之Queue学习(Queue接口,PriorityQueue)

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)

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

程序员小牧之

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值