分析 Queue 和 Priority Queue 的源码

Queue

首先简单认识下Queue,查看API定义了6个方法,归纳为新增、删除、查询三种不同操作,每种操作有2个不同的实现,基于JDK 1.8
api接口如下:

新增接口

  • boolean add(E e)
  • boolean offer(E e)

查询:

  • E element()
  • E peek()

删除:

  • E remove()
  • E poll()

每种操作有不同的实现,他们区别是什么?实战操作

public class App 
{
    public static void main( String[] args ) {

        //addAndOffer();
        //removeAndPoll();
        elementAndPeek();
    }
    

    /**
     * element 和 peek 操作区别
     */
    private static void elementAndPeek() {
        // 设置一个固定长度为2的队列
        Queue<String> queue = new ArrayBlockingQueue<String>(2);
        try {
            System.out.println(queue.element()); //java.util.NoSuchElementException
        }catch (Exception e){
            e.printStackTrace();
        }

        System.out.println(queue.poll()); //null

    }


    /**
     * remove 和 poll 操作区别
     */
    private static void removeAndPoll() {
        // 设置一个固定长度为2的队列
        Queue<String> queue = new ArrayBlockingQueue<String>(2);
        try {
            queue.add("a");
            queue.add("b");
            System.out.println(queue); // [a, b]
            System.out.println(queue.remove()); // a
            System.out.println(queue.remove()); // b
            System.out.println(queue.remove()); // NoSuchElementException
        }catch (Exception e){
            e.printStackTrace();
        }finally {
            queue.clear();
        }


        queue.add("a");
        queue.add("b");
        System.out.println(queue);// [a, b]
        System.out.println(queue.poll()); // a
        System.out.println(queue.poll()); // b
        System.out.println(queue.poll()); // null



    }


    /**
     * add 和 offer 操作区别
     */
    private static void addAndOffer() {
        // 设置一个固定长度为2的队列
        Queue<String> queue = new ArrayBlockingQueue<String>(2);
        try {
            System.out.println(queue.add("a")); // true
            System.out.println(queue.add("b")); // true
            System.out.println(queue.add("c")); // java.lang.IllegalStateException: Queue full
        }catch (Exception e){
            e.printStackTrace();
        }finally {
            queue.clear();
        }

        System.out.println(queue.offer("a")); // true
        System.out.println(queue.offer("b")); // true
        System.out.println(queue.offer("c")); // false
    }
}

通过上面演示可以看出,add、remove、element三个方法在操作过程中,如果访问超过了容器的固定存储范围,那么会报异常,而另一组offer、poll、peek则会给出一种特殊的返回值,不会报错。

查看Queue源码,发现继承了Collection,从这里也可以初步知道Queue实际是一个集合,对应的api也是针对集合的一个扩展

public interface Queue<E> extends Collection<E> {

    boolean add(E e);
    boolean offer(E e);

    E remove();
    E poll();

    E element();
    E peek();
}

PriorityQueue

PriorityQueue是Queue的一种实现,而Queue是一种规范,通过Queue中api简单操作,初步了解PriorityQueue

public class PriorityQueueTest {
    public static void main(String[] args) {
        PriorityQueue<Integer> queue = new PriorityQueue<Integer>();
        System.out.println("入队前数据:[5, 3, 2, 4, 7]");
        queue.add(5);
        System.out.println("入队第一个数据后的队列:" + queue);
        queue.add(3);
        System.out.println("入队第二个数据后的队列:" + queue);
        queue.add(2);
        System.out.println("入队第三个数据后的队列:" + queue);
        queue.add(4);
        System.out.println("入队第四个数据后的队列:" + queue);
        queue.add(7);
        System.out.println("全部入队后的队列:" + queue);


        System.out.println("移除第一个数据: " + queue.remove());
        System.out.println("移除第一个数据后的队列:" + queue);

        System.out.println("移除第二个数据: " + queue.remove());
        System.out.println("移除第二个数据后的队列:" + queue);

        System.out.println("移除第三个数据: " + queue.remove());
        System.out.println("移除第三个数据后的队列:" + queue);

        System.out.println("移除第四个数据: " + queue.remove());
        System.out.println("移除第四个数据后的队列:" + queue);

        System.out.println("移除最后个数据: " + queue.remove());
        System.out.println("移除最后个数据后的队列:" + queue);
    }
}
入队前数据:[5, 3, 2, 4, 7]
入队第一个数据后的队列:[5]
入队第二个数据后的队列:[3, 5]
入队第三个数据后的队列:[2, 5, 3]
入队第四个数据后的队列:[2, 4, 3, 5]
全部入队后的队列:[2, 4, 3, 5, 7]
移除第一个数据: 2
移除第一个数据后的队列:[3, 4, 7, 5]
移除第二个数据: 3
移除第二个数据后的队列:[4, 5, 7]
移除第三个数据: 4
移除第三个数据后的队列:[5, 7]
移除第四个数据: 5
移除第四个数据后的队列:[7]
移除最后个数据: 7
移除最后个数据后的队列:[]

根据打印结果,入队或出队后,会把每次队列中最小数放在队首。而且出队的时候数据依次增大。这就是PriorityQueue一个特点:根据队列中数据以某种优先级进行将数据出队
如何实现呢?查看源码

// 查看入队源码
// queue实际是一个数组
transient Object[] queue;

public boolean add(E e) {
    return offer(e);
}

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;
}

private void siftUp(int k, E x) {
    if (comparator != null)// 自定义比较器不为空
        siftUpUsingComparator(k, x); // 使用自定义比较器的进行入队
    else
        siftUpComparable(k, x); // 使用默认比较器的进行入队
}

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;
}

@SuppressWarnings("unchecked")
private void siftUpUsingComparator(int k, E x) {
    while (k > 0) {
        int parent = (k - 1) >>> 1;
        Object e = queue[parent];
        // 使用自定义的比较器进行入队操作,选择合适数据放在队首
        if (comparator.compare(x, (E) e) >= 0) 
            break;
        queue[k] = e;
        k = parent;
    }
    queue[k] = x;
}

对入队源码的分析知道,每次入队操作都会根据比较器进行判断,选择合适元素放在队首,所以添加的队列数据必须实现Comparator接口或者实例化Priority Queue 提供一个自定义比较器
在看看出队操作。同时add的内部实现是通过offer实现的,

public E remove() {
    E x = poll(); // 出队
    if (x != null)
        return x;
    else
        throw new NoSuchElementException();
}

public E poll() {
    if (size == 0)
        return null;
    int s = --size;
    modCount++;
    E result = (E) queue[0]; // 选择第一个元素出队
    E x = (E) queue[s]; // 取出队尾元素
    queue[s] = null; // 队尾元素置空
    if (s != 0)
        // 目前队首元素是满足最合适数据,队首元素移除后,在剩余队列中选择最适合数据放入队首,从一个元素位置,与最后一个元素对比
        siftDown(0, x); 
    return result;
}

private void siftDown(int k, E x) {
    if (comparator != null)
        siftDownUsingComparator(k, x);
    else
        siftDownComparable(k, x);
}

@SuppressWarnings("unchecked")
private void siftDownComparable(int k, E x) {
    Comparable<? super E> key = (Comparable<? super E>)x;
    int half = size >>> 1;        // loop while a non-leaf
    while (k < half) {
        int child = (k << 1) + 1; // assume left child is least
        Object c = queue[child];
        int right = child + 1;
        // 取相邻中最小数
        if (right < size &&
            ((Comparable<? super E>) c).compareTo((E) queue[right]) > 0)
            c = queue[child = right];
        // 最小数与目标数对比,目标数小,则退出
        if (key.compareTo((E) c) <= 0)
            break;
        queue[k] = c;
        k = child;
    }
    //将最小数放置队首
    queue[k] = key;
}

@SuppressWarnings("unchecked")
private void siftDownUsingComparator(int k, E x) {
    int half = size >>> 1;
    while (k < half) {
        int child = (k << 1) + 1;
        Object c = queue[child];
        int right = child + 1;
        if (right < size &&
            comparator.compare((E) c, (E) queue[right]) > 0)
            c = queue[child = right];
        if (comparator.compare(x, (E) c) <= 0)
            break;
        queue[k] = c;
        k = child;
    }
    queue[k] = x;
}

出队与入队当数据变动后,就会计算出最新数据放到队首,

可以总结PriorityQueue一些特点:

  • 1、入队元素必须实现Comparator比较器接口,或者用户自己提供比较器,否则无法使用
  • 2、PriorityQueue通过数组实现,数组长度虽然固定,但可以扩容,
  • 3、因为可以扩容add不会存在容器长度问题报异常,而remove和element仍然存在此问题
  • 4、PriorityQueue是非线程安全的,新增、删除操作没有加锁操作

PriorityQueue应用场景

  • 处理很多任务且任务有不同的执行级别
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章