【源码】Queue AbstractQueue PriorityQueue 浅析

前言

一般我们认为的 Queue 是队列,其遵循 FIFO(先进先出)的原则,此时 head 元素即为我们要取出的下一个元素,而插入的下一个元素即 tail。而对于 LIFO (后进先出)比如 栈,其下一个取出的元素依然是 head,插入的元素则就不是 tail 了。

Queue

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

	// 添加元素,失败抛异常
    boolean add(E e);

	// 添加元素,失败返回 false
    boolean offer(E e);

	// 移除元素,失败抛异常
    E remove();

	// 移除元素,失败返回 false
    E poll();

	// 检索第一个元素,失败抛异常
    E element();

	// 检索第一个元素,失败返回 false
    E peek();
}

Queue 拓展了 Collection,提供的操作方法分类如下

Queue操作失败抛出异常操作失败返回指定值(true false 等)
插入add(e)offer(e)
移除remove()poll()
检索element()peek()

AbstractQueue

public abstract class AbstractQueue<E>
    extends AbstractCollection<E>
    implements Queue<E> {

    protected AbstractQueue() {
    }

	// 交给 offer(子类实现),失败则抛异常
    public boolean add(E e) {
        if (offer(e))
            return true;
        else
            throw new IllegalStateException("Queue full");
    }

	// 交给 poll(子类实现),失败则抛异常 
    public E remove() {
        E x = poll();
        if (x != null)
            return x;
        else
            throw new NoSuchElementException();
    }

	// 交给 peek(子类实现),失败则抛异常 
    public E element() {
        E x = peek();
        if (x != null)
            return x;
        else
            throw new NoSuchElementException();
    }

	// 无限 poll
    public void clear() {
        while (poll() != null)
            ;
    }

	// 遍历 add
    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;
    }
}

AbstractQueueQueue 的抽象实现,相当于搭了个框架,将 失败后抛异常 的操作都委托给 子类方法 实现,我们看一个具体的实现类 PriorityQueue

PriorityQueue

PriorityQueue 是一个 无界的、有优先级 的队列

属性、构造方法

	// 默认数组大小 11
	private static final int DEFAULT_INITIAL_CAPACITY = 11;

    // 维护元素的无界数组
    transient Object[] queue;

    // 元素数量
    int size;

    // 维护优先级的 Comparator
    private final Comparator<? super E> comparator;

    // 记录操作次数
    transient int modCount; 

    public PriorityQueue() {
        this(DEFAULT_INITIAL_CAPACITY, null);
    }

    public PriorityQueue(int initialCapacity) {
        this(initialCapacity, null);
    }

	// 指定 comparator
    public PriorityQueue(Comparator<? super E> comparator) {
        this(DEFAULT_INITIAL_CAPACITY, comparator);
    }

	// 指定初始容量 initialCapacity 和 comparator
    public PriorityQueue(int initialCapacity,
                         Comparator<? super E> comparator) {
        if (initialCapacity < 1)
            throw new IllegalArgumentException();
        this.queue = new Object[initialCapacity];
        this.comparator = comparator;
    }

虽然称为无界队列,但是也需要定义 初始容量(类似 ArrayList),且会在元素达到 阈值 时进行 扩容,允许自定义 初始化容量Comparator

add(E e)
	// 因为无界,因此复写了 AbstractQueue 的 add 方法
	public boolean add(E e) {
        return offer(e);
    }

	public boolean offer(E e) {
		// 元素不能为 null
        if (e == null)
            throw new NullPointerException();
		
		// 操作次数
        modCount++;
        int i = size;

		// 元素是否超过容量
        if (i >= queue.length)
        	// 扩容
            grow(i + 1);

		// 元素插入
        siftUp(i, e);

		// 长度递增
        size = i + 1;
        return true;
    }

	---------------- 扩容 -----------------

	private void grow(int minCapacity) {
        int oldCapacity = queue.length;
		
		// 小于 64 时以 2 为单位,之后翻倍
        int newCapacity = oldCapacity + ((oldCapacity < 64) ?
                                         (oldCapacity + 2) :
                                         (oldCapacity >> 1));
        if (newCapacity - MAX_ARRAY_SIZE > 0)
            newCapacity = hugeCapacity(minCapacity);
        queue = Arrays.copyOf(queue, newCapacity);
    }
siftUp
	private void siftUp(int k, E x) {
        if (comparator != null)
            siftUpUsingComparator(k, x, queue, comparator);
        else
            siftUpComparable(k, x, queue);
    }

    private static <T> void siftUpComparable(int k, T x, Object[] es) {
        Comparable<? super T> key = (Comparable<? super T>) x;
		// 遍历折中
		// 如果当前元素 x 大于 折中元素 e,则直接插入数组末尾
		// 反之,则继续向前折中比对,最后交换
		// 即保证了每次插入的最小的元素放在数组首位
        while (k > 0) {
            int parent = (k - 1) >>> 1;
            Object e = es[parent];
            if (key.compareTo((T) e) >= 0)
                break;
            es[k] = e;
            k = parent;
        }
        es[k] = key;
    }

	// 同上,基于用户自定义的 Comparator
    private static <T> void siftUpUsingComparator(
        int k, T x, Object[] es, Comparator<? super T> cmp) {
        while (k > 0) {
            int parent = (k - 1) >>> 1;
            Object e = es[parent];
            if (cmp.compare(x, (T) e) >= 0)
                break;
            es[k] = e;
            k = parent;
        }
        es[k] = x;
    }	

插入元素时会基于 Comparator 优先级调整,保证最小的元素列在数组首位

注意此时并未保证元素完全有序,比如插入 3 2 1 6 7 时对应数组为 1 3 2 6 7,此时以中间点一分为二,前半段基本保证有序且小于中间点,但也不排除前后相邻为顺序颠倒,比如上例中的 23,具体原因可以自己模拟体会一下

remove()
	--------- from AbstractQueue ----------
	public E remove() {
        E x = poll();
        if (x != null)
            return x;
        else
            throw new NoSuchElementException();
    }
	
	public E poll() {
        final Object[] es;
        final E result;

		// 返回的是数组头元素,插入时已将最小元素排在数组头
        if ((result = (E) ((es = queue)[0])) != null) {
        	// 操作数累加
            modCount++;
            final int n;

            final E x = (E) es[(n = --size)];
            es[n] = null;
            if (n > 0) {
                final Comparator<? super E> cmp;
                if ((cmp = comparator) == null)
                	// 调整数组头为最小的元素
                    siftDownComparable(0, x, es, n);
                else
                	// 使用自定义的 comparator
                    siftDownUsingComparator(0, x, es, n, cmp);
            }
        }
        return result;
    }

	private static <T> void siftDownComparable(int k, T x, Object[] es, int n) {
        Comparable<? super T> key = (Comparable<? super T>)x;

        // 折中
        int half = n >>> 1;
        while (k < half) {

            // 从第二位开始调整
            int child = (k << 1) + 1; // assume left child is least
            Object c = es[child];

            // 此处就是解决之前提到的可能出现 临近位 次序颠倒情况
            int right = child + 1;
            if (right < n &&
                ((Comparable<? super T>) c).compareTo((T) es[right]) > 0)
                c = es[child = right];
            if (key.compareTo((T) c) <= 0)
                break;

            // 下一轮比对
            es[k] = c;
            k = child;
        }

        // 交换顺序
        es[k] = key;
    }

	// 同上,使用自定义的 Comparator
    private static <T> void siftDownUsingComparator(
        int k, T x, Object[] es, int n, Comparator<? super T> cmp) {
        int half = n >>> 1;
        while (k < half) {
            int child = (k << 1) + 1;
            Object c = es[child];
            int right = child + 1;
            if (right < n && cmp.compare((T) c, (T) es[right]) > 0)
                c = es[child = right];
            if (cmp.compare(x, (T) c) <= 0)
                break;
            es[k] = c;
            k = child;
        }
        es[k] = x;
    }

返回已排在数组头的 最小元素,然后调整数组再次将 最小元素 排在数组头,等待下一次被获取

element()
	--------- from AbstractQueue ----------
	public E element() {
        E x = peek();
        if (x != null)
            return x;
        else
            throw new NoSuchElementException();
    }

	// 返回数组头
	public E peek() {
        return (E) queue[0];
    }

调用 peek() 返回数组头元素

其他方法

还有很多其他方法,比如 remove(E e) forEach(Consumer<? super E> action) 等等就不一一列举了

demo

public class TestPriorityQueue {

    class Node {
        int id;
        String name;

        public Node(int id, String name) {
            this.id = id;
            this.name = name;
        }

        public int getId() {
            return id;
        }

        public void setId(int id) {
            this.id = id;
        }

        public String getName() {
            return name;
        }

        public void setName(String name) {
            this.name = name;
        }

        @Override
        public String toString() {
            return "Node{" +
                    "id=" + id +
                    ", name='" + name + '\'' +
                    '}';
        }
    }

    @Test
    public void test1() {
        PriorityQueue priorityQueue = new PriorityQueue(
                new Comparator<Node>() {
                    @Override
                    public int compare(Node o1, Node o2) {
                        return o1.id - o2.id;
                    }
                }
        );

        priorityQueue.add(new Node(3, "3"));
        priorityQueue.add(new Node(4, "4"));
        priorityQueue.add(new Node(1, "1"));
        priorityQueue.add(new Node(6, "6"));
        priorityQueue.add(new Node(2, "2"));

        while (!priorityQueue.isEmpty()) {
            System.out.println(priorityQueue.remove());
        }
    }
}

结果:
Node{id=1, name='1'}
Node{id=2, name='2'}
Node{id=3, name='3'}
Node{id=4, name='4'}
Node{id=6, name='6'}

总结

本章节介绍了 Queue 接口及其抽象实现类 AbstractQueue ,最后介绍了基于 优先级 的具体实现类 PriorityQueue ,其在元素 插入取出 时的同时,对数组进行调整,以保证元素的 优先级,优先级规则由可自定义的 Comparator 实现

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值