Queue(队列)
Queue(队列)是一种特殊的线性表,它只允许在队头(front)进行删除操作(出队),在队尾(rear)进行插入操作(入队),是一种操作受限制的线性表。即:FIFO(First In, First Out)。
从Queue的继承关系图可以看到,Queue接口实现了Collection接口,所以Queue具有Collection接口的所有特性。
但是一般来说,在操作Queue时,一般只操作Queue的相关特性方法即可。否则何必使用Queue呢?
Queue常见的方法
// 入队
boolean offer(E e);
// 出队
E poll();
// 获取队头元素(不出队)
E peek();
// 判断队列是否为空
boolean isEmpty();
// 清空队列
void clear();
// 获取队列的元素个数
int size();
自己实现一个Queue(链表实现)
public class MyLinkedQueue<E> {
private List<E> queueList = new LinkedList<>();
// 入队
public boolean offer(E e){
return queueList.add(e);
}
// 出队
public E poll(){
checkElementIndex();
return queueList.remove(0);
}
// 获取队头元素(不出队)
public E peek(){
checkElementIndex();
return queueList.get(0);
}
private void checkElementIndex(){
if(isEmpty()){
throw new IndexOutOfBoundsException("queue is empty.");
}
}
// 判断队列是否为空
public boolean isEmpty(){
return queueList.isEmpty();
}
// 清空队列
public void clear(){
queueList.clear();
}
// 获取队列的元素个数
public int size(){
return queueList.size();
}
}
自己实现一个Queue(数组实现)
public class MyArrayQueue<E>{
private Object[] objects;
private int size = 0;
private int front = 0;
private static final int DEFAULT_CAPACITY = 10;
public MyArrayQueue(int capacity){
if(capacity <= 0){
throw new IllegalArgumentException("capacity must be greater than zero.");
}
objects = new Object[capacity];
}
public MyArrayQueue(){
this(DEFAULT_CAPACITY);
}
// 入队
public boolean offer(E e){
ensureCapacity();
//因为数组从0开始,所以(front + size) % objects.length就是下一个要插入的位置,而不是+1
int index = (front + size) % objects.length;
objects[index] = e;
size++;
return true;
}
// 确保容量足够插入元素
private void ensureCapacity() {
if(size == objects.length){
// 2倍扩容
Object[] newObjects = new Object[this.objects.length * 2];
for (int i = 0; i < size; i++) {
newObjects[i] = objects[front];
moveFrontToNext();
}
objects = newObjects;
// 扩容后,队头从开始
front = 0;
}
}
// 出队
public E poll(){
checkElementIndex();
E e = (E) objects[front];
moveFrontToNext();
size--;
return e;
}
// 获取队头元素(不出队)
public E peek(){
checkElementIndex();
return (E) objects[front];
}
private void checkElementIndex(){
if(isEmpty()){
throw new IndexOutOfBoundsException("queue is empty.");
}
}
// 判断队列是否为空
public boolean isEmpty(){
return size == 0;
}
// 清空队列
public void clear(){
for (int i = 0; i < size; i++) {
objects[front] = null;
moveFrontToNext();
}
size = 0;
}
// 获取队列的元素个数
public int size(){
return size;
}
// 将front移动到下一个位置。这个是数组实现Queue的核心算法。
private void moveFrontToNext(){
front = (front + 1) % objects.length;
}
}
数组实现Queue为什么底层不使用ArrayList,而是自己实现底层逻辑呢?因为ArrayList对于出队操作,时间复杂度为O(n),性能不友好。而上面的代码通过循环数组的方式,通过front指定队头的位置,就能实现O(1)时间复杂度的出队操作。
数组实现Queue的核心算法就是:front = (front + count) % objects.length。它通过取模底层数组长度,可以定位到入队的位置,从而实现循环数组。如:
假设数组长度为10,队列的front为9(数组下标从0开始,所以front为9时是第10个元素位置),队列长度为5,那么其底层数据的位置分别为:
0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 |
---|---|---|---|---|---|---|---|---|---|
2 | 3 | 4 | 5 | 1 |
现在要插入值为6的元素,则6应该是插入到数组下标为4的位置。那么其计算公式为:
front = (front + size) % objects.length = (9 + 5) % 10 = 4
0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 |
---|---|---|---|---|---|---|---|---|---|
2 | 3 | 4 | 5 | 6 | 1 |
Deque
Deque是Queue的子接口。Queue是一种单向的队列形式,Deque则是双向队列,它支持从两个端点方向检索和插入元素。Deque既包含队列性质,也包含了栈的性质,因此Deque既可以支持FIFO形式也可以支持LIFO形式。
Deque常见的方法
// 向队列头部插入一个元素,失败时抛出异常
void push(E e);
// 向队列头部插入一个元素,失败时抛出异常
void addFirst(E e);
// 向队列尾部插入一个元素,失败时抛出异常
void addLast(E e);
// 向队列头部加入一个元素,失败时返回false
boolean offerFirst(E e);
// 向队列尾部加入一个元素,失败时返回false
boolean offerLast(E e);
// 获取队列头部元素,队列为空时抛出异常
E getFirst();
// 获取队列尾部元素,队列为空时抛出异常
E getLast();
// 获取队列头部元素,队列为空时返回null
E peekFirst();
// 获取队列尾部元素,队列为空时返回null
E peekLast();
// 删除第一次出现的指定元素,不存在时返回false
boolean removeFirstOccurrence(Object o);
// 删除最后一次出现的指定元素,不存在时返回false
boolean removeLastOccurrence(Object o);
// 弹出队列头部元素,队列为空时抛出异常
E pop();
// 弹出队列头部元素,队列为空时抛出异常
E removeFirst();
// 弹出队列头部元素,队列为空时返回null
E pollFirst();
// 弹出队列尾部元素,队列为空时返回null
E pollLast();
从上面的方法可以看出,Deque在Queue的方法上新添了对队列头尾元素的操作。
需要注意push = addFirst,pop = removeFirst,只是使用了不同的方法名体现队列表示栈结构时的特点。
Deque的实现
同Queue一样,Deque的实现也可以划分成通用实现和并发实现。
通用实现主要有两个实现类ArrayDeque和LinkedList:
ArrayDeque是个可变数组,它是在Java 6之后新添加的,而LinkedList是一种链表结构的list。LinkedList要比ArrayDeque更加灵活,因为它也实现了List接口的所有操作,并且可以插入null元素,这在ArrayDeque中是不允许的。
从效率来看,ArrayDeque要比LinkedList在两端增删元素上更为高效,因为没有在节点创建删除上的开销。最适合使用LinkedList的情况是迭代队列时删除当前迭代的元素。
总体ArrayDeque要比LinkedList更优越,在大队列的测试上有3倍与LinkedList的性能,最好的是给ArrayDeque一个较大的初始化大小,以避免底层数组扩容时数据拷贝的开销。
LinkedBlockingDeque是Deque的并发实现,在队列为空的时候,它的takeFirst,takeLast会阻塞等待队列处于可用状态。
参考:https://www.cnblogs.com/bushi/p/6681543.html