Collection的其它两大分支:List和Set在前面已近分析过,这篇来分析一下Queue的底层实现。html
前三篇关于Java容器类的文章:java
public interface Queue<E> extends Collection<E> { boolean add(E var1); boolean offer(E var1); E remove(); E poll(); E element(); E peek(); }
这就是Queue接口的代码,相比于List或者Set简洁明了不少。下面介绍一下它里面接口的含义:
在处理元素前用于保存元素的 collection。除了基本的 操做外,队列还提供其余的Collection
插入、提取和检查c++
操做。每一个方法都存在两种形式:
一种抛出异常(操做失败时),另外一种返回一个特殊值(null 或 false,具体取决于操做)。web
插入操做的后一种形式是用于专门为有容量限制的 Queue 实现设计的;在大多数实现中,插入操做不会失败。
AbstractQueue中实现了queue和Collection中部分函数,比较简单,源码以下:api
public abstract class AbstractQueue<E> extends AbstractCollection<E> implements Queue<E> {
protected AbstractQueue() {
}
public boolean add(E e) { if (offer(e)) return true; else throw new IllegalStateException("Queue full"); } public E remove() { E x = poll(); if (x != null) return x; else throw new NoSuchElementException(); } public E element() { E x = peek(); if (x != null) return x; else throw new NoSuchElementException(); } public void clear() { while (poll() != null) ; } 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; } }
从上面的调用关系能够看出来,Queue的解释中哪些是会抛出异常的调用,哪些是不会抛出异常的调用接口。数组
一个线性 collection,支持在两端插入和移除元素。名称 deque 是“double ended queue(双端队列)”的缩写,一般读为“deck”。大多数 Deque 实现对于它们可以包含的元素数没有固定限制,但此接口既支持有容量限制的双端队列,也支持没有固定大小限制的双端队列。(java 1.6版本中的家扣,1.8中接口有变更,可是大概含义类似)数据结构
在Java容器类1中介绍了LinkedList,链表类其实实现了 Deque的接口,因此链表支持从头部和尾部添加和移除元素。多线程
由于链表的存储结构可能比较简单,这里介绍一下ArrayDeque,它的里面存储元素使用一个数组。 做为一个双端队列的数组,涉及到扩容和元素的拷贝的逻辑可能比较复杂些。ide
看一下里面的几个构造函数:函数
public ArrayDeque() {
elements =
new Object[16];
} public ArrayDeque(int numElements) {allocateElements(numElements);
} public ArrayDeque(Collection<? extends E> c) { allocateElements(c.size()); addAll(c); }
从构造函数能够看出默认的会分配一个长度为16的数组。同时,也支持指定大小的队列(这里的allocateElements函数以前在
java容器类2:Map及HashMap深刻解读 中已经深刻分析过,是个很是精妙的函数)。下面看一下究竟是如何实现插入?又是如何自动扩充数组的?
ArrayQueue中维护了两个成员变量:head和tail分别表明 队列的头和尾在数组中的下标。
/** * The index of the element at the head of the deque (which is the * element that would be removed by remove() or pop()); or an * arbitrary number equal to tail if the deque is empty. */ transient int head; /** * The index at which the next element would be added to the tail * of the deque (via addLast(E), add(E), or push(E)). */ transient int tail;
在队列的首部添加元素:
public void addFirst(E e) { if (e == null) throw new NullPointerException();elements[head = (head - 1) & (elements.length - 1)] = e;
if (head == tail)
doubleCapacity();
}public void
addLast(E e) {if
(e ==null
)throw new
NullPointerException();if
elements[tail] = e;
( (tail = (tail + 1) & (elements.length - 1)) == head)
doubleCapacity();
}
由构造函数和数组分配的函数能够知道,数组的长度确定是一个2的幂次方的一个整数。
当head为大于0的整数时,在头部插入很简单,将head前一个元素赋值为e就能够了。那么当head为0时,怎么计算的?由上面能够看出会插入到数组的尾部。因此ArrayDeque至关于在一个圆环上,规定一个头一个尾做为队列的先后(将数组的首位相连)。
在最后位置添加元素的原理和在首部添加类似。注意判断是否已满的 判断,这里再也不分析。
当队列已经满后,会将数组的长度double。因为数组是不能自由扩张的,因此doubleCapacity函数应该是分配一个更大的数组,并将原来的元素拷贝进去,这里再也不分析。
总的来讲双端队列ArrayDeque是在数组的基础之上实现,原理和实现都不算复杂,可是不少边界调节等细节能够斟酌。
BlockingQueue是concurrent包下面的,后续打算写一个系列文章专门分析concurrent包下面的类,及一些多线程相关的东西。
优先级队列是一个能够排序的队列。内部是一个最大堆,大部分人应该了解堆排序,因此对最大堆应该不会陌生。
每次读取元素都是读取最大的元素(默认状况下)。
对外的接口有以下:
方法名 | 功能描述 |
---|---|
add(E e) | 添加元素 |
clear() | 清空 |
contains(Object o) | 检查是否包含当前参数元素 |
offer(E e) | 添加元素 |
peek() | 读取元素,(不删除) |
poll() | 取出元素,(删除) |
remove(Object o) | 删除指定元素 |
size() | 返回长度 |
PriorityQueue 默认是一个最大堆结构,若是想构造一个最小堆:
private static final int DEFAULT_INITIAL_CAPACITY = 11; PriorityQueue<Integer> maxHeap=new PriorityQueue<Integer>(DEFAULT_INITIAL_CAPACITY, new Comparator<Integer>() { @Override public int compare(Integer o1, Integer o2) { return o2-o1; } });
关于堆的数据结构部分这里再也不分析能够参考:http://www.javashuo.com/article/p-ydzmfqux-dq.html
c++版的优先级队列分析:优先级队列用法详解(priority_queue)
因为是经过数组保存数据,因此优先级队列也会涉及到容量的扩充等,和HashMap/Setting/Collection的扩容原理相同,甚至更简单,再也不分析。PriorityQueue内部的操做都是在最大堆的基础上展开的,阅读堆的数据结构相关资料即可了解。
参考:
http://tool.oschina.net/uploads/apidocs/jdk-zh/java/util/Deque.html