队列这种数据结构的物理实现一般是两种,一种是链表,一种是数组。
1. 带哨兵的双向链表
使用一个sentinel来代替之前的head头指针,sentinel的item没有实际的意义,主要关注它的next和pre,初始的时候,链表只有一个sentinel节点,sentinel.next指向自己,sentinel.pre也指向自己。当添加了若干个节点之后,sentinel.next指向头节点,而sentinel.pre则指向尾节点;而同样的,这时头节点的pre不再是NULL而是指向sentinel,尾节点的next也不再是NULL,也是指向sentinel。
不需要标明队头和队尾,在操作的时候想清楚增加哪些指向和删除哪些指向即可。
2. 循环数组
nextLast到达尾端时通过取模可以回到初始位置,通过判断nextLast+1是否等于nextFirst来判断队列满。(其实还有一个空位置
入队操作
public void addLast(T item) {
if (item == null)
throw new NullPointerException();
array[nextLast] = item;
if ((nextLast = (nextLast + 1) & (elements.length - 1)) == nextFirst)
doubleCapacity();
)
在构造的时候会让arry的长度为2的指数值,可以提高入队的效率,因为对于任意一个2的指数级的值减去1之后必然所有位全为1,例如:8-1之后为111,16-1之后1111。而对于nextLast来说,当nextLast+1小于等于array.length - 1,两者与完之后的结果还是nextLast+1,但是如果大于array.length - 1,两者与完之后就为0,回到初始位置。这种判断队列是否满的方式要远远比使用符号%直接取模高效。如果队列满,会调用扩充容量的方法。
private void doubleCapacity() {
assert nextFirst== nextLast;
int p = nextFirst;
int n = array.length;
int r = n - p;
int newCapacity = n << 1;
if (newCapacity < 0)
throw new IllegalStateException("Sorry");
Object[] a = new Object[newCapacity];
System.arraycopy(elements, p, a, 0, r);
System.arraycopy(elements, 0, a, r, p);
array = a;
nextFirst= 0;
nextLast = n;
}
n << 1就是乘2的动作。
出队操作
public T removeLast() {
T x = pollLast(); // 调用pollLast方法
if (x == null)
throw new NoSuchElementException();
return x;
}
public T pollLast() {
int t = (nextLast- 1) & (elements.length - 1); // 尾索引 -1
@SuppressWarnings("unchecked")
T result = (T) array[t]; // 根据尾索引,得到尾元素
if (result == null)
return null;
array[t] = null; // 尾元素置空
nextLast= t;
return result;
}
ArrayDeque的主要优势在于尾部添加元素,头部出队元素的效率是比较高的,内部使用位操作来判断队满条件,效率相对有所提高,并且该结构使用动态扩容,所以对队列长度也是没有限制的。
还应该加上缩容,比如当size小于length的四分之一时,减小数组长度
Q: I can’t get Java to create an array of generic objects!
A: Use the strange syntax we saw in January 30th’s lecture,
i.e. T[] a = (T[]) new Object[1000];
. Here, T
is a generic type, it’s a placeholder for other Object types like “String” or “Integer”.