概述
双端队列ArrayDeque是Java集合框架中的一种数据结构,它实现了Deque接口,因此支持在两端进行添加和移除元素。通过名称也能看出,ArrayDeque是基于数组实现的,ArrayDeque内部使用一个可动态调整大小的环形数组来存储元素。当ArrayDeque中的元素数量超过当前数组容量时,会自动进行扩容,以确保有足够的空间存放新元素,但Java没有实现自动缩容。
环形数组
队列是一种只能在头(head)取出元素,在尾(tail)插入元素的结构,ArrayDeque是基于数组实现的,数组不同于链表,它在取出元素后不会释放内存空间,随着元素的入队出队,数组前端浪费的空间将会越来越多,直至内存溢出。使用环形数组就能完美解决这个问题,ArrayDeque内部的的数组是一个逻辑上的环形数组,实际上与普通数组并无区别,通过一个头指针head和一个尾指针tail将数组模拟成逻辑上的环。
- 队列新创建时head和tail都指向数组0下标:
- 经过几次入队出队可能的情况:
- tail到达数组末尾后继续入队,则会回到数组0下标,继续使用出队空出的空间:
环形数组的使用过程就好像两个指针在一个环形的跑道上赛跑,当head追上tail时表示队列中已经没有元素了,当tail追上head(超了一圈)时就表示数组已满需要扩容了。head永远都不会超过tail。下面是ArrayDeque元素入队的源码:
public void addLast(E var1) {
if (var1 == null) {
throw new NullPointerException();
} else {
this.elements[this.tail] = var1;
//扩容条件
if ((this.tail = this.tail + 1 & this.elements.length - 1) == this.head) {
this.doubleCapacity();//扩容方法
}
}
}
应用
ArrayDeque实现了Deque接口,Deque又继承自Queue接口,ArrayDeque可以作为先进先出(FIFO)的队列使用,由于Java集合框架里的标准栈(Stack)性能不佳,已经作为遗留类被遗弃,官方推荐使用ArrayDeque作为栈实现。而ArrayDeque并不像Stack那样是标准的后进先出(LIFO)结构,如若需要,可以对ArrayDeque进行封装。以下是作为队列和栈的简单示例:
//作为队列
Queue<String> queue = new ArrayDeque<>();
//入队
queue.offer("1");
queue.offer("2");
queue.offer("3");
//出队
System.out.println(queue.poll());
System.out.println(queue.poll());
System.out.println(queue.poll());
//输出1,2,3
Deque<String> stack = new ArrayDeque<>();
//压栈
stack.push("1");
stack.push("2");
stack.push("3");
//弹出栈顶元素
System.out.println(stack.pop());
System.out.println(stack.pop());
System.out.println(stack.pop());
//输出3,2,1
ArrayDeque和LinkedList
LinkedList也实现了Deque接口,可以作为队列或栈使用,区别在于ArrayDeque是基于数组的,LinkedList是基于链表的。作为队列或栈使用时,ArrayDeque和LinkedList性能相差不大,由于数组的地址空间是连续的,根据程序的局部性原理,ArrayDeque多数情况下可能会快一些,在做选择时可以优先考虑ArrayDeque。若还需要队列或栈之外的功能,则需要综合考虑,如:LinkedList实现了List接口,拥有Deque之外的很多功能,如果还需要用到List相关的功能,则优先考虑LinkedList。
总结
ArrayDeque是Java标准库中提供的一种高性能的双端队列数据结构,它是Deque接口的一个典型实现。双端队列允许我们在队列的前端和后端进行元素的添加与移除操作,这为开发者在处理序列数据时提供了极大的灵活性。ArrayDeque的核心在于其内部实现了一个可动态调整大小的环形数组。这一设计精妙之处在于,通过使用模运算(取余),使得数组的存储空间可以循环利用,即使在元素不断添加和移除的情况下,也无需频繁地进行数组的复制和扩容,从而大大提高了数据结构的操作效率。
在性能方面,ArrayDeque的插入(offer/put)和删除(poll/remove)操作都达到了平均O(1)的时间复杂度,这意味着无论是向队列的头部还是尾部添加或移除元素,其操作速度都非常快,这对于需要快速插入和删除的场景尤为重要,如实现各类栈、队列操作,以及在迭代器中的应用等。