1. Queue传承后代介绍java
Queue: Deque : ArrayDeque数组
LinkedList数据结构
AbstractQueue : PriorityQueue性能
注:橙色为继承,黑色为实现。测试
Queue队列,最明显的特征是:先进先出。该接口提供了6个接口,分为三组:spa
(1) add 和 offer : 队尾添加元素code
(2) remove 和 poll: 移除 队列的头对象
(3) element 和 peek: 获取队列的头,但不移除继承
很明显,这三对的add,remove,element继承Collection的, 后面三个才是Queue新增的接口。队列的三个接口当遇到超出边界时,不会抛出异常,要么返回false,要么返回null;而Collection的三个接口会抛出异常。索引
2. Deque接口详情
Deque打破了队列的限制,能够在队尾队头实现元素的添加、移除、获取。
(1) addFirst / offerFirst 和 addLast / offerLast: 添加元素
(2) removeFirst / pollFirst 和 removeLast / pollLast: 移除元素
(3) getFirst / peekFirst 和 getLast / peekLast: 获取队列的头,但不移除
3. ArrayDeque具体实现
要弄清楚ArrayDeque具体实现,则只需研究它的存储结构和元素存储方式索引便可。下面是关键变量:
public class ArrayDeque<E> extends AbstractCollection<E> implements Deque<E>, Cloneable, Serializable { transient Object[] elements; // non-private to simplify nested class access transient int head; transient int tail; // ... }
由上面源码可知,这里的存储结构是数组。说实话很容易想固然,一个数组末端是队头,开端是队尾,正常队列添加元素的位置是队尾,也就是说数组,每添加一个元素,全队向后移1位;而后考虑队头出队列...每添加新元素,队头索引+1,出队列减1....每添加1个元素还需从新拷贝到新数组...等等,仔细想来这也太不靠谱了吧。其实,咱们无需关注队列元素实际是怎么存储的,只需保证出队列的顺序正确就能够;换一句话说,数组里面无需保证队尾和对头的位置在两端,他们甚至能够在任何一位置。
ArrayDeque的添加以及索引方式:
(1) 熟悉一下位运算
// 00000001 & 00000010 = 0 System.out.println( 1 & 2); // 11111111 & 00000010 = 2 System.out.println( -1 & 2) // 结论: 当 y>=0 时, 有 x & y <= y 成立
在这里咱们注意到: 利用 & 运算能够进行边界控制,数组无需进行边界检查,提升性能(一样能够用于集合)。
(2) 添加元素
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(); elements[tail] = e; if ( (tail = (tail + 1) & (elements.length - 1)) == head) doubleCapacity(); }
首先测试一下:
String[] array = new String[4]; //数组 int h = 0; // head int t = 0; // tail h = ((h - 1) & (array.length - 1)); // h = 3 h = ((h - 1) & (array.length - 1)); // h = 2 h = ((h - 1) & (array.length - 1)); // h = 1 h = ((h - 1) & (array.length - 1)); // h = 0 h = ((h - 1) & (array.length - 1)); // h = 3 h = ((h - 1) & (array.length - 1)); // h = 2 t = ((t + 1) & (array.length - 1)); // t = 1 t = ((t + 1) & (array.length - 1)); // t = 2 t = ((t + 1) & (array.length - 1)); // t = 3 t = ((t + 1) & (array.length - 1)); // t = 0 t = ((t + 1) & (array.length - 1)); // t = 1 t = ((t + 1) & (array.length - 1)); // t = 2
进一步证实上面的结论。
(3) 当 t = h时,队列满了,则能够扩容了:
private void doubleCapacity() { assert head == tail; int p = head; int n = elements.length; int r = n - p; // 头下标索引到数组末端的元素个数 int newCapacity = n << 1; // 左移,扩大两倍 if (newCapacity < 0) throw new IllegalStateException("Sorry, deque too big"); Object[] a = new Object[newCapacity]; // Object src, int srcPos, Object dest, int destPos, int length System.arraycopy(elements, p, a, 0, r); // 旧数组中头右边的元素(包括头)拷贝到从下标0开始的新数组中 System.arraycopy(elements, 0, a, r, p); // 旧数组开始到头前面的元素拷贝到从r下标开始的新数组中 elements = a; head = 0; // 头下标变为0 这和刚开始添加元素的head是同样的,下一次队头的位置是 新数组末端 tail = n; // 尾下标变为旧数组的长度 }
(4) 存储特色(出队列)
public E pollFirst() { int h = head; @SuppressWarnings("unchecked") E result = (E) elements[h]; // Element is null if deque empty if (result == null) return null; elements[h] = null; // Must null out slot head = (h + 1) & (elements.length - 1); // 头索引变化,添加时是减1,移除天然是加1 return result; }
看上图,第1次在队头添加元素,索引为10;第2次在队头添加元素,索引为9,可见队头的位置并非在数组的末端,而是一直在变。同理,队尾也是如此。咱们只需记住位置索引,保证出队列的顺序就能够了(这一点经过上面&运算实现)。
4. PriorityQueue队列
PriorityQueue 里面存放的元素不能为null,且要求是能做比较的元素(即实现了 Comparator 接口);最小的元素在队头,能够存在相等的元素;固然因此该类的构造方法中,能够传递一个Comparator进去。它的数据结构叫堆(虽然容器是数组),在这里是最小堆,也就是堆的最顶端是最小的。
该堆实现一个二叉树,这棵二叉树的根节点是queue[0],左子节点是queue[1],右子节点是queue[2],而queue[3]又是queue[1]的左子节点,依此类推,给定元素queue[i],该节点的父节点是queue[(i-1)/2] 。因此给定位置k,它的父节点是:(k-1)/2,即(k-1)<<<2。
public boolean offer(E e) { if (e == null) throw new NullPointerException(); modCount++; int i = size; // 添加新元素的节点老是在数组的末端 if (i >= queue.length) // 扩容 grow(i + 1); size = i + 1; if (i == 0) // 队列插入第一个元素的节点,无需比较大小 queue[0] = e; else siftUp(i, e); // 调整堆 拿父节点和新插入元素节点比较,若父节点值大于新元素,则交换。至关于提高位置 return true; } /** * @param k the position to fill * @param x the item to insert */ private void siftUp(int k, E x) { if (comparator != null) siftUpUsingComparator(k, x); // 使用构造传递进来的Comparator对象 else siftUpComparable(k, x); // 使用元素对象实现的Comparator比较方法 } private void siftUpUsingComparator(int k, E x) { while (k > 0) { int parent = (k - 1) >>> 1; // 无符号右移, 拿到即将插入元素节点对于的父节点位置 Object e = queue[parent]; if (comparator.compare(x, (E) e) >= 0) // 若是新元素节点的值大于父节点的,则无需调整,直接赋值 break; queue[k] = e; // 不然,新元素节点和父节点交换位置 k = parent; } queue[k] = x; }