Collections(四)Queue

队列是一个典型的FIFO先进先出结构,从一端放入元素,从另一端取出元素。队列在并发环境中比较有用,但是Java也提供了线程不安全的实现:LinkedList和PriorityQueue,ArrayDeque也实现了Queue的所有方法,它是基于可变数组实现的队列(关于ArrayDeque将在Deque章节中详细介绍)。

LinkedList的实现原理在前文中已经介绍过,下面给出LinkedList作为队列使用的一段示例代码,本文将对PriorityQueue作详细介绍。

LinkedList<String> linkedListDeque = new LinkedList<>();
linkedListDeque.offer("hello");
linkedListDeque.offer("i");
linkedListDeque.offer("am");
linkedListDeque.offer("queue");

org.junit.Assert.assertEquals("hello", linkedListDeque.peek());
org.junit.Assert.assertEquals("hello", linkedListDeque.poll());

Queue接口

我们先来看看Queue接口。

public interface Queue<E> extends Collection<E> {
    boolean add(E e);
    boolean offer(E e);
    E remove();
    E poll();
    E element();
    E peek();
}

队列操作分为三种,每一种操作都提供了两个方法:一个会在操作失败时候抛出异常(比如容量限制等),一个会返回特殊的值,比如null或者boolean。

作用抛出异常返回特殊的值
入队add(E e)offer(E e)
出队remove()poll()
获取元素element()peek()

PriorityQueue不允许元素是null,当poll操作出队时,返null是可行的。但是对于LinkedList的来说,它的元素允许为null,所以根据null来判断是否未空队列是不可行的。

PriorityQueue实现原理

PriorityQueue是一个优先级队列,FIFO队列也可以理解为优先级队列,出队的元素是等待时间最长的。PriorityQueue出队的元素则是优先级最高的元素,优先级是使用Comparator和Comparable,我们在《Java Collections Framework(一)概览》中已经描述过。

为了满足这样的结构,我们需要对这个优先级队列的元素进行排序,PriorityQueue是基于堆实现的一个无界队列,使用堆来实现优先级队列,排序的性能达到了O(lgN),如果使用数组来排序,一次排序的时间就可能是O(N)。

PriorityQueue内部维护了一个数组Object[] queue来实现平衡二叉堆的结构,即queue[n]的两个儿子是queue[2*n+1]和queue[2*(n+1)],所以它是有初始容量的,并且提供了容量增长策略。

初始容量

PriorityQueue的默认初始容量是由一个常量指定的,值为11,正如ArrayList一样,为了性能优化,它也提供了一个构造器传入初始容量。

private static final int DEFAULT_INITIAL_CAPACITY = 11;

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

接下来我们从入队来分析它的容量增长策略。

入队和增长容量策略分析

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

可以看到,优先队列是不允许NULL值存在的,当元素个数超过内部数组长度时,就会扩容,我们接着看grow方法:

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));
    // overflow-conscious code
    if (newCapacity - MAX_ARRAY_SIZE > 0)
        newCapacity = hugeCapacity(minCapacity);
    queue = Arrays.copyOf(queue, newCapacity);
}

源码中可以看出容量增长策略:如果当前容量小于64,则扩容后容量是现有容量的两倍多2,否则容量增长现有容量的50%

源码简要分析

peek

public E peek() {
    return (size == 0) ? null : (E) queue[0];
}

可见,是一个大根堆。

poll

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

正如offer方法调用了siftUp方法,poll方法调用了siftDown方法来保证新增或者删除的时候,它仍然是一个堆。具体保证堆的算法这里就不贴出来了,可以自行查看源码或者算法书籍。

总结

并发包下面提供了很多有界线程安全的队列,提供了一些接口扩展了Queue,比如阻塞队列接口BlockingQueue,这些将在并发系列的文章中讲解。

  • ArrayBlockingQueue 基于数组的有界阻塞队列,线程安全
  • LinkedBlockingQueue 基于链表的可选有界阻塞队列,线程安全
  • PriorityBlockingQueue 基于堆的无界阻塞优先队列,线程安全
  • DelayQueue  基于堆的无界阻塞根据延迟时间优先顺序的队列,线程安全
  • SynchronousQueue  同步队列,线程安全
  • LinkedTransferQueue  线程安全
  • LinkedBlockingDeque 基于链表的可选有界阻塞双向队列,线程安全

转载于:https://my.oschina.net/LeBronJames/blog/3101529

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

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值