Java高级技术第四章——Java容器类Queue之体验双端队列ArrayQueue设计之妙
2018-03-21 10:48
459 查看
前言
前言点击此处查看:http://blog.csdn.net/wang7807564/article/details/79113195
ArrayDeque
ArrayDeque的数据结构要比PriorityQueue要简单得多,是通过数组来实现的。但是,ArrayDeque的特点是一个双端队列,既可以实现FIFO的Queue,也可以实现LIFO的Stack.ArrayDeque虽然原理比较简单,但是其精髓是使用了位运算来加快计算效率。它的源代码如下:
采用数组存储数据,数组的默认长度是16:
/** * Constructs an empty array deque with an initial capacity * sufficient to hold 16 elements. */ public ArrayDeque() { elements = new Object[16]; }
存储数组长度一定是2的幂指数,且最小为8,其通过这样的位运算来实现的:
/** * The minimum capacity that we'll use for a newly created deque. * Must be a power of 2. */ private static final int MIN_INITIAL_CAPACITY = 8; // ****** Array allocation and resizing utilities ****** 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; } public ArrayDeque(int numElements) { allocateElements(numElements); } private void allocateElements(int numElements) { elements = new Object[calculateSize(numElements)]; }
从上面的代码可以看出来,使用calculateSize()方法可以自动计算出合适的2的n次幂的数值,用下面的代码作一个测试:
public class Main { private static int getCapacity(int numElements) { int 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; } public static void main(String[] args){ for(int i=0;i<5;i++){ System.out.println(i + " " + getCapacity(i)); } } }
输出结果是:
0 1
1 2
2 4
3 4
4 8
获取队列长度
ArrayDeque有两个变量head,tail分别代表数组中队首和队尾的索引,由于是双端队列,实现起来类似一个循环数组,有可能队尾的索引数值比队头的数值还要小,其是通过这种方法来实现获取数组长度的:
public int size() { return (tail - head) & (elements.length - 1); }
依然使用了位运算,之所以存储数组的长度都是2的n次幂的数值,是因为:
2^n - 1
计算之后的二进制结果前半部分都是0,后半部分都是1,能够起到类似掩码的作用,
例如下面的示意图:
实际上兼取绝对值与取模于一体了。
清空队列
public void clear() { int h = head; int t = tail; if (h != t) { // clear all cells head = tail = 0; int i = h; int mask = elements.length - 1; do { elements[i] = null; i = (i + 1) & mask; } while (i != t); } }
清空队列并不将数组的数组元素数量再次置为初始值,只是将数组中对每个元素的引用置为null,这样可以被垃圾回收掉。
可以看到,在遍历队列中每个元素的时候,巧妙地运用了位运算,仍然将元素数量减1作为掩码使用。
添加元素
添加元素的核心源代码如下:
public void addLast(E e) { if (e == null) throw new NullPointerException(); elements[tail] = e; if ( (tail = (tail + 1) & (elements.length - 1)) == head) doubleCapacity(); }
判断数组容量不够时,需要进行扩容,判断方式依然是使用位运算来完成的。
对于循环数组中判断是否达到最大容量的一种方法就是判断tail与head是否相等,如果相等,那么首位相交,达到最大容量。
但是,在起始位置时候,head与tail也可能会相等。那么,可以将tail指向下一个元素将要插入的位置,那么当:
(tail + 1) % array.length == head
此时,也就是下一个将要插入的位置与head索引相同,那么证明这个数组已经被填充满了,需要扩容了。
在上面的源代码中,使用了掩码的位运算方式来代替取模的方式,效率更高。可见,数组长度是2的n次幂数值用处很大。
数组扩容
在插入元素之后,要进行数组是否扩容的判断,在如果需要扩容,则扩容一倍,确保存储数组的长度仍然是2的n次幂数值。
private void doubleCapacity() { assert head == tail; int p = head; int n = elements.length; int r = n - p; // number of elements to the right of p int newCapacity = n << 1; //扩容一倍 if (newCapacity < 0) throw new IllegalStateException("Sorry, deque too big"); Object[] a = new Object[newCapacity]; System.arraycopy(elements, p, a, 0, r); System.arraycopy(elements, 0, a, r, p); elements = a; head = 0; tail = n; }
相关文章推荐
- Java高级技术第四章——Java容器类Queue之从小顶堆到优先队列PriorityQueue
- Java高级技术第四章——Java容器类之Set从源码开始详解
- Java高级技术第四章——Java的容器类概述
- Java高级技术第四章——容器类之Queue概述
- JAVA核心技术 第四章 对象与类 类设计技巧
- 修炼Java开发技术----在架构中体验设计模式和算法之美
- Java高级技术第四章——Java容器Collection之List详解
- Java高级技术第四章——Java容器类Map之快速的HashMap
- Java高手真经. 编程基础卷:Java核心编程技术:Java基础+核心库+图形+网络+高级特性
- Java高手真经. 高级编程卷:Java Web高级开发技术:EJB+消息通信+分布式+开源框架
- oracle高级队列在通信方面的应用设计
- 由Java设计模式想到测试技术
- Java技术体验,HTTP多线程下载,端口侦听和自启动服务
- 我的《Java 高级技术》课程PPT
- Java核心技术高级群
- 利用高级Java、算法、三角学、分布计算设计自己的智能机器人
- WCF 第四章 绑定 使用队列技术进行通信
- Java高手真经. 高级编程卷:Java Web高级开发技术:EJB+消息通信+分布式+开源框架
- JAVA核心技术学习笔记(第七版,Ⅱ高级特性之集合)
- 《Java 核心技术 卷 Ⅱ:高级特性》(原书第8版) 已经上市了