优先队列了解否?

PriorityQueue

​ PriorityQueue是一个具有优先级的无界队列,其内部基于一个优先级堆实现的。优先级队列内部的元素之间的优先级是按照元素实现的Comparable的自然顺序排序的,或者是使用构造方法中传入的Comparator接口的实现类完成的。优先队列中不允许存放null元素,如果没有传入Comparator接口的实现类,那么内部的元素必须要实现Comparable接口,否则将抛出ClassCastException,因为元素在比较时转化为Comparable进行比较。
​ 优先队列中的头元素拥有最高或者最低的优先级,如果多个元素都具有相同的优先级,那么头元素将是其中的一个。
​ 优先队列是无界的,但是有一个capacity来表示内部用来存储元素的数组的容量,他应该>=队列中存储的元素的数量,随着元素不断的添加进队列,容量可以自动的扩大,但是详细的扩容策略是不固定的。
​ 优先队列返回的迭代器并不能保证迭代元素的顺序与元素的优先级一致。
​ 该优先队列不是线程安全的,因此如果任何一个线程尝试修改优先队列,多个线程不能同时访问优先队列,否则可能出现线程安全问题,如果需要使用线程安全的队列,可以用PriorityBlockingQueue代替。

声明

public class PriorityQueue extends AbstractQueue
implements java.io.Serializable
{
//代码
}

PriorityQueue继承自AbstractQueue,该抽象类继承自AbstractCollection,并实现了Queue接口。

成员变量

public class PriorityQueue extends AbstractQueue
implements java.io.Serializable
{
//用于支持序列化。
private static final long serialVersionUID = -7720805057305804111L;
//初始的容量为11
private static final int DEFAULT_INITIAL_CAPACITY = 11;
/**
* 可以看到有限队列内部使用数组来实现。
* 优先队列用一个平衡二叉树堆来实现,queue[n]的两个孩子分别为queue[2n+1]和queue[2(n+1)]
* 优先级队列中的元素将按照比较器进行排序,如果比较器为空,那么将按照元素的自然顺序排序(元素必须实现
* Comparable接口)
* queue[0]是优先级最高或者最低的元素。
/
transient Object[] queue; // non-private to simplify nested class access
/
*
* 优先队列中的元素数量。
*/
private int size = 0;

/**
 * 比较器,如果优先级队列使用元素的自然顺序,则为null。
 */
private final Comparator<? super E> comparator;
/**
 * 表示该队列被结构化修改的次数
 */
transient int modCount = 0; // non-private to simplify nested class access
/**
 * 内部的queue数组可以分配的最大空间,可以看到其实也并不是无界的。
 * 如果尝试视图分配超过这个值的数组那么将可能(强调)导致
 * 之所以说可能,是因为后面扩容是可以看到如果要扩容的容量结余[MAX_ARRAY_SIZE,Integer.MAX_VALUE]
 * 那么新容量就是Integer.MAX_VALUE,而不会抛出异常。
 * OutOfMemoryError: Requested array size exceeds VM limit
 */
private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;

}

构造方法——如何初始化
/**
 * 创建一个默认容量的优先队列(11),并使用元素的自然顺序进行排序。
 */
public PriorityQueue() 
{
    this(DEFAULT_INITIAL_CAPACITY, null);
}

/**
 * 创建一个指定初始容量的优先队列,并使用元素的自然顺序进行排序。
 * 如果initialCapacity<1,将抛出IllegalArgumentException
 */
public PriorityQueue(int initialCapacity) 
{
    this(initialCapacity, null);
}

/**
 * 创建一个默认容量的优先队列(11),并使用传入的比较器进行比较,
 * 如果传入的比较器为null,那么将使用元素的自然顺序进行比较。
 */
public PriorityQueue(Comparator<? super E> comparator)
{
    this(DEFAULT_INITIAL_CAPACITY, comparator);
}

/**
 * 创建一个指定初始容量的优先队列,并使用传入的比较器进行比较,
 * 如果传入的比较器为null,那么将使用元素的自然顺序进行比较。
 * 如果initialCapacity<1,将抛出IllegalArgumentException
 */
public PriorityQueue(int initialCapacity,
                     Comparator<? super E> comparator) 
{
    // Note: This restriction of at least one is not actually needed,
    // but continues for 1.5 compatibility
    if (initialCapacity < 1)
        throw new IllegalArgumentException();
    this.queue = new Object[initialCapacity];
    this.comparator = comparator;
}

/**
 * 创建一个优先队列用来存放Collection实现类中的所有元素,如果这个c是一个SortedSet实现类的实例,或者是
 * 另一个PriorityQueue实例。那么新创建的将遵从原先的顺序,否则将按照元素的自然顺序排列。
 * 如果c为空,或者c中任何元素为空(ArrayList和LinkedList可能要注意一下下)。那么将抛出NullPointerException
 * 如果c中的缘故没有实现Comparable接口,那么将抛出ClassCastException。
 */
@SuppressWarnings("unchecked")
public PriorityQueue(Collection<? extends E> c) 
{
    if (c instanceof SortedSet<?>) 
    {
        SortedSet<? extends E> ss = (SortedSet<? extends E>) c;
        this.comparator = (Comparator<? super E>) ss.comparator();
        initElementsFromCollection(ss);
    }
    else if (c instanceof PriorityQueue<?>) 
    {
        PriorityQueue<? extends E> pq = (PriorityQueue<? extends E>) c;
        this.comparator = (Comparator<? super E>) pq.comparator();
        initFromPriorityQueue(pq);
    }
    else 
    {
        this.comparator = null;
        initFromCollection(c);
    }
}

private void initElementsFromCollection(Collection<? extends E> c) 
{
    Object[] a = c.toArray();
    // If c.toArray incorrectly doesn't return Object[], copy it.
    if (a.getClass() != Object[].class)
        a = Arrays.copyOf(a, a.length, Object[].class);
    int len = a.length;
    if (len == 1 || this.comparator != null)
        for (int i = 0; i < len; i++)
            if (a[i] == null)
                throw new NullPointerException();
    			//检查是否有空元素
    this.queue = a;
    this.size = a.length;
}

private void initFromPriorityQueue(PriorityQueue<? extends E> c)
{
    //不需要判断元素是否为空,因为PriorityQueue本身要求元素不能为空。
    //如果继承了PriorityQueue,那么这种方法不能初始化。就是因为这个地方。
    if (c.getClass() == PriorityQueue.class)
    {
        this.queue = c.toArray();
        this.size = c.size();
    }
    else
    {
        initFromCollection(c);
    }
}

/**
 * 从给定的Collection实现类实例c中初始化队列内部的数组。
 * 因为添加的顺序可能不是元素的自然顺序,所以这个时候需要重建堆。
 */
private void initFromCollection(Collection<? extends E> c) 
{
    initElementsFromCollection(c);
    //将数组堆化。
    heapify();
}

/**
 * 从底向上重建整个堆。
 */
@SuppressWarnings("unchecked")
private void heapify() 
{
    //只需要从size/2-1开始就可以了,因为其他的都是叶子节点,没有孩子。
    for (int i = (size >>> 1) - 1; i >= 0; i--)
        siftDown(i, (E) queue[i]);
}

/**
 * 将x插入到位置k,通过不断重复使x降低,直到x<=他的孩子或者x已经是叶子节点。
 *
 * @param k the position to fill
 * @param x the item to insert
 */
private void siftDown(int k, E x) 
{
    //使用自定义比较器调整堆
    if (comparator != null)
        siftDownUsingComparator(k, x);
    //使用元素的自然顺序调整堆
    else
        siftDownComparable(k, x);
}

@SuppressWarnings("unchecked")
private void siftDownUsingComparator(int k, E x) 
{
    int half = size >>> 1;
    //这里刚开始k肯定是小于half的。
    while (k < half) 
    {
        int child = (k << 1) + 1;//他的孩子是2k+1,或者2(k+1)
        Object c = queue[child];
        int right = child + 1;
        //比较他们两个孩子谁大。选择两个孩子中比较小的那个
        if (right < size &&
            comparator.compare((E) c, (E) queue[right]) > 0)
            c = queue[child = right];
        //如果当前要插入的x比它的最小的孩子还小(这里小是按照比较器的规则来看),那么就可以了,不用进行下去了。
        if (comparator.compare(x, (E) c) <= 0)
            break;
        //把它的小孩子提升上去。
        queue[k] = c;
        k = child;
    }
    queue[k] = x;
}

@SuppressWarnings("unchecked")
private void siftDownComparable(int k, E x)
{
    Comparable<? super E> key = (Comparable<? super E>)x;
    int half = size >>> 1;        // 只需要到非叶子节点即可。
    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];
        //如果当前要插入的x比它的最小的孩子还小(这里小是按照比较器的规则来看),那么就可以了,不用进行下去了。
        if (key.compareTo((E) c) <= 0)
            break;
        //把它的小孩子提升上去。
        queue[k] = c;
        k = child;
    }
    queue[k] = key;
}

/**
 * 根据传入PriorityQueue实例创建并初始化该PriorityQueue,并且
 * 该优先级队列将会按照原先PriorityQueue的顺序构建。
 */
@SuppressWarnings("unchecked")
public PriorityQueue(PriorityQueue<? extends E> c) 
{
    this.comparator = (Comparator<? super E>) c.comparator();
    initFromPriorityQueue(c);
}

/**
 * 根据传入SortedSet实现类实例创建并初始化该PriorityQueue,并且
 * 该优先级队列将会按照原先SortedSet的顺序构建。
 */
@SuppressWarnings("unchecked")
public PriorityQueue(SortedSet<? extends E> c) 
{
    this.comparator = (Comparator<? super E>) c.comparator();
    initElementsFromCollection(c);
}

​ PriorityQueue只提供七个构造方法。主要可以用来指定初始容量和自定义比较器的,以及可以通过已有的SortedSet实现类实例、其他的PriorityQueue实例、或者其他的Collection构造并初始化优先队列。可以看到当执行构造方法的的时候,底层的数组就被创建了。
其他方法
grow方法
/**

  • 扩容数组的容量

  • 具体的扩容机制是:

  • 1、如果原先的容量小于64,那么新容量=2*旧容量+2

  • 2、如果原先的容量大于等于64,那么新容量=1.5*旧容量。

  • @param minCapacity the desired minimum capacity
    */
    private void grow(int minCapacity)
    {
    int oldCapacity = queue.length;
    // Double size if small; else grow by 50%
    int newCapacity = oldCapacity + ((oldCapacity < 64) ?
    (oldCapacity + 2) :
    (oldCapacity >> 1));
    // 超出了最大容量限制。
    if (newCapacity - MAX_ARRAY_SIZE > 0)
    newCapacity = hugeCapacity(minCapacity);
    queue = Arrays.copyOf(queue, newCapacity);
    }

    private static int hugeCapacity(int minCapacity)
    {
    //已经超过了Integer.MAX_VALUE
    if (minCapacity < 0) // overflow
    throw new OutOfMemoryError();
    return (minCapacity > MAX_ARRAY_SIZE) ?
    Integer.MAX_VALUE :
    MAX_ARRAY_SIZE;
    }

    • 返回用于排序优先队列内部元素的比较器,如果元素采用自然顺序比较的,那么返回null。
      */
      public Comparator<? super E> comparator()
      {
      return comparator;
      }

常用方法
添加元素
offer方法
/**

  • 添加元素到优先队列中,如果添加的元素不可比较,那么将抛出ClassCastException,如果

  • 如果添加的元素为null,那么将抛出NullPointerException
    */
    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;
    }

    /**

    • 将x插入到位置k,通过不断重复使x向上提升,直到x>=他的父亲或者x已经是根节点。
    • @param k the position to fill
    • @param x the item to insert
      */
      private void siftUp(int k, E x)
      {
      //使用自定义比价器调整
      if (comparator != null)
      siftUpUsingComparator(k, x);
      //根据元素自然顺序调整。
      else
      siftUpComparable(k, x);
      }

    @SuppressWarnings(“unchecked”)
    private void siftUpUsingComparator(int k, E x)
    {

     //刚开始k=size肯定符合条件
     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;
    

    }

    @SuppressWarnings(“unchecked”)
    private void siftUpComparable(int k, E x)
    {
    Comparable<? super E> key = (Comparable<? super E>) x;
    //刚开始k=size肯定符合条件
    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;
    }
    复制代码
    add方法
    /**

  • 添加元素到优先队列中,如果添加的元素不可比较,那么将抛出ClassCastException,如果

  • 如果添加的元素为null,那么将抛出NullPointerException,该方法与offer方法完全一致。
    */
    public boolean add(E e)
    {
    return offer(e);
    }
    获取元素

peek方法

/**

  • 返回队头元素,如果队列为空,那么返回null。
    */
    @SuppressWarnings(“unchecked”)
    public E peek()
    {
    return (size == 0) ? null : (E) queue[0];
    }
indexOf方法

/**

  • 如果要查找的元素为null,则返回-1,因为PriorityQueue中不允许存放null元素,否则
  • 查找队列中是否有元素于o相等,如果是返回true,否则返回false。
    */
    private int indexOf(Object o)
    {
    if (o != null)
    {
    for (int i = 0; i < size; i++)
    if (o.equals(queue[i]))
    return i;
    }
    return -1;
    }

删除元素

removeAt方法
/**
 * 删除指定位置处的元素。
 */
@SuppressWarnings("unchecked")
private E removeAt(int i)
{
    // assert i >= 0 && i < size;
    modCount++;
    int s = --size;
    if (s == i) // 如果该元素是最后一个元素,那么直接删除就可以了不需要调整堆。
        queue[i] = null;
    else
    {
        //把堆中的最后一个元素摘下来
        E moved = (E) queue[s];
        queue[s] = null;
        //覆盖i的位置,也就是要删除的元素。
        //然后向下调整,使得符合堆的特性。
        //为什么选择向下调整而不是向上调整,因为这时候插入i的位置的是最后一个元素
        //在此之前该元素是优先级最大或者最小的。
        siftDown(i, moved);
        //覆盖之后,堆没有调整,说明下面没有比moved小的了,但是上面还是有可能有比moved
        //小的,所以还需要先上检查调整。
        //但是如果queue[i]!=moved说明肯定进行了向下调整。
        //这个时候上面的肯定都是小鱼moved的了。
        if (queue[i] == moved) 
        {
            siftUp(i, moved);
            if (queue[i] != moved)
                return moved;
        }
    }
    //如果moved从堆尾部摘下来后既没有向上调整也没有向下调整。那么就返回null。
    return null;
}
remove方法

/**

  • 查找队列中是否存在于指定元素相等的元素,如果存在,则删除该元素,否则,直接返回false。
  • 该方法将首先通过indexOf方法查看队列中是否有该元素,如果有根据下标删除该元素。
    */
    public boolean remove(Object o)
    {
    int i = indexOf(o);
    if (i == -1)
    return false;
    else
    {
    removeAt(i);
    return true;
    }
    }
PriorityQueue内部的序列化规则
writeObject方法

/**

  • 将该优先队列保存到流中。
    */
    private void writeObject(java.io.ObjectOutputStream s)
    throws java.io.IOException
    {
    // Write out element count, and any hidden stuff
    s.defaultWriteObject();

    // 注意这里序列化的队列的容量并不是真正的队列的容量。
    //因为后续反序列化的时候并没有用到。
    s.writeInt(Math.max(2, size + 1));

    // Write out all elements in the “proper order”.
    for (int i = 0; i < size; i++)
    s.writeObject(queue[i]);
    }

readObject方法

/**

  • Reconstitutes the {@code PriorityQueue} instance from a stream

  • (that is, deserializes it).

  • @param s the stream
    */
    private void readObject(java.io.ObjectInputStream s)
    throws java.io.IOException, ClassNotFoundException
    {
    // Read in size, and any hidden stuff
    s.defaultReadObject();

    // Read in (and discard) array length
    s.readInt();

    SharedSecrets.getJavaOISAccess().checkArray(s, Object[].class, size);
    queue = new Object[size];

    // Read in all elements.
    for (int i = 0; i < size; i++)
    queue[i] = s.readObject();

    //然后重新进行堆化或者时候调整堆。
    heapify();
    }

java8新增API

/**

  • Creates a late-binding
  • and fail-fast {@link Spliterator} over the elements in this
  • queue.
  • The {@code Spliterator} reports {@link Spliterator#SIZED},

  • {@link Spliterator#SUBSIZED}, and {@link Spliterator#NONNULL}.
  • Overriding implementations should document the reporting of additional
  • characteristic values.
  • @return a {@code Spliterator} over the elements in this queue
  • @since 1.8
    */
    public final Spliterator spliterator()
    {
    return new PriorityQueueSpliterator(this, 0, -1, 0);
    }

static final class PriorityQueueSpliterator implements Spliterator
{
/*
* This is very similar to ArrayList Spliterator, except for
* extra null checks.
*/
private final PriorityQueue pq;
private int index; // current index, modified on advance/split
private int fence; // -1 until first use
private int expectedModCount; // initialized when fence set

/** Creates new spliterator covering the given range */
PriorityQueueSpliterator(PriorityQueue<E> pq, int origin, int fence,
                     int expectedModCount) 
{
    this.pq = pq;
    this.index = origin;
    this.fence = fence;
    this.expectedModCount = expectedModCount;
}

private int getFence() 
{ // initialize fence to size on first use
    int hi;
    if ((hi = fence) < 0) 
    {
        expectedModCount = pq.modCount;
        hi = fence = pq.size;
    }
    return hi;
}

public PriorityQueueSpliterator<E> trySplit() 
{
    int hi = getFence(), lo = index, mid = (lo + hi) >>> 1;
    return (lo >= mid) ? null :
        new PriorityQueueSpliterator<E>(pq, lo, index = mid,
                                        expectedModCount);
}

@SuppressWarnings("unchecked")
public void forEachRemaining(Consumer<? super E> action) 
{
    int i, hi, mc; // hoist accesses and checks from loop
    PriorityQueue<E> q; Object[] a;
    if (action == null)
        throw new NullPointerException();
    if ((q = pq) != null && (a = q.queue) != null) 
    {
        if ((hi = fence) < 0) 
        {
            mc = q.modCount;
            hi = q.size;
        }
        else
            mc = expectedModCount;
        if ((i = index) >= 0 && (index = hi) <= a.length)
        {
            for (E e;; ++i) 
            {
                if (i < hi) 
                {
                    if ((e = (E) a[i]) == null) // must be CME
                        break;
                    action.accept(e);
                }
                else if (q.modCount != mc)
                    break;
                else
                    return;
            }
        }
    }
    throw new ConcurrentModificationException();
}

public boolean tryAdvance(Consumer<? super E> action) 
{
    if (action == null)
        throw new NullPointerException();
    int hi = getFence(), lo = index;
    if (lo >= 0 && lo < hi)
    {
        index = lo + 1;
        @SuppressWarnings("unchecked") E e = (E)pq.queue[lo];
        if (e == null)
            throw new ConcurrentModificationException();
        action.accept(e);
        if (pq.modCount != expectedModCount)
            throw new ConcurrentModificationException();
        return true;
    }
    return false;
}

public long estimateSize()
{
    return (long) (getFence() - index);
}

public int characteristics() 
{
    return Spliterator.SIZED | Spliterator.SUBSIZED | Spliterator.NONNULL;
}

}

几个注意

1.这里的PriorityQueue说的是无界队列,但是并不是真的无界,其内部的数组最大可以开到Integer.MAX_VALUE。
2.通过构造函数传入Collection实现类实例构造优先队列的时候是自顶向下建堆调整。而插入元素的时候是自底向上插入堆并调整。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值