文章目录
1. 前言
我们在实际的软件开发中经常会使用两种数据结构:栈和队列。Java语言有实现一个Stack的类,但Stack是一个古老的类,性能较差。Java提供了Queue接口,但没有提供Queue实现类。当我们需要用到Stack或Queue时,这是就推荐使用ArrayDeque。ArrayDeque是一个双端循环队列,既可以当成Stack使用,也可以当成Queue使用。
2. ArrayDeque的类继承图
3.ArrayDeque类法
3.1 构造函数
3.2常用方法
4. ArrayDeque 实现栈
栈的特点是后进先出,我们将push()方法,pop()方法进行组合使用,即可实现将ArrayDeque双端对列作为栈使用。
private static void arrayDequeStackTest() {
System.out.println("ArrayDeque:栈使用测试");
ArrayDeque stack = new ArrayDeque();
stack.push("宋江"); //入栈
stack.push("吴用");
stack.push("公孙胜");
System.out.println("栈中的元素");
stack.forEach(o -> System.out.println(o)); //打印出栈中元素
//出栈测试
stack.forEach(o-> System.out.println(stack.pop()));
}
运行结果:
ArrayDeque:栈使用测试
栈中的元素
公孙胜
吴用
宋江
出栈(后进先出):
公孙胜
吴用
宋江
5. ArrayDeque实现队列
我们知道队列的特点是先进先出,我们将offer()方法,pop()方法进行组合使用,即可将ArrayDeque双端对列作为队列使用。
//双向队列当队列使用
private static void arrayDequeQueueTest( ){
System.out.println("ArrayDeque:队列测试");
ArrayDeque queue = new ArrayDeque();
queue.offer("宋江"); //入队
queue.offer("吴用");
queue.offer("公孙胜");
System.out.println("队列中的元素");
queue.forEach(o -> System.out.println(o)); //先进先出,先输出宋江
System.out.println("出队(先进先出");
queue.forEach(o -> System.out.println(queue.pop()));
}
执行结果:
ArrayDeque:队列测试
队列中的元素
宋江
吴用
公孙胜
出队(先进先出
宋江
吴用
公孙胜
6. ArrayDeque底层算法研究
6.1 初始容量分配
6.1.2 初始容量分配算法
前面我们调到ArrayDeque双端队列容量分配都是2^n, 其容量分配算法如下:
6.1. 2 ^n 初始容量分配算法
我们先看下ArrayDeque实现 2^n容量分配的代码:
private static int calculateSize(int numElements) {
int initialCapacity = MIN_INITIAL_CAPACITY;
// Find the best power of two to hold elements.
// Tests "<=" because arrays aren't kept full.
if (numElements >= initialCapacity) {
initialCapacity = numElements;
initialCapacity |= (initialCapacity >>> 1);
initialCapacity |= (initialCapacity >>> 2);
initialCapacity |= (initialCapacity >>> 4);
initialCapacity |= (initialCapacity >>> 8);
initialCapacity |= (initialCapacity >>> 16);
initialCapacity++;
if (initialCapacity < 0) // Too many elements, must back off
initialCapacity >>>= 1;// Good luck allocating 2 ^ 30 elements
}
return initialCapacity;
}
这个容量计算实现就很巧妙,其实现巧妙利用了位操作操作,具体如下:
- 它使用整数的最高位不断的进行无符号右移运算,并将结果与原值进行或操作,
最后得到一个从最高位–最低位全1的数值。 - 将全1值再加1,最高位进1,其余为变0,即变成了比指定值更大,且为2^n值。
下面我们画图演示下创建容量为512的ArrayDeque,其创建2^n容量的过程。
我们创建指定的容量为512,实际分配的数组容量为1024。
6.1.2 自动扩容
当ArrayDeque的队列使用过程,当队列满时,其自动创建一个容量翻倍的数组进行扩容。
7 双端队列ArrayDeque存储内存图
ArrayDeque是一个双端队列,其用成员head,tail分别表示队列的头和尾。
Head指向队列首元素,tail指向下一个可用队列元素空间
ArrayDeque statck = new ArrayDeque(6);
其内存图如下:
队首添加一个元素:
stack.addFirst("宋江");
队尾添加一个元素:
stack.addLast("吴用");
8. ArrayDeque索引
1.队列数添加元素,head -1
elements[head = (head - 1) & (elements.length - 1)] = e;
head初始值为0,减1后变成-1, 与0x0000 0111(7)相与后变成最大索引7.
2.队尾添加元素,tail加1,
(tail = (tail + 1) & (elements.length - 1)
9.总结
- ArrayDeque实现了双端队列,内部存储是基于一个动态扩展的数组。
- 通过位操作,对Head和Tail高效处理,对队列的首,尾元素进行操作效率高。
- 根据内容进行查找或删除的效率低,效率为O(N)。
- 使用ArrayDeque时,初始创建湿申请合适的大小,避免其反复扩容造成效率低下。
- ArrayDeque做为栈,FIFO队列使用时,需顺序添加,或删除元素。
我的公众号
分享嵌入式开发,java开发原创文章。
作者鼓励
如果您觉得本文帮助,打赏鼓励下