在Java中(但在PHP中类似),ArrayDeque
实现的能力始终为2的幂:
对于HashMap
,此选择很明显-基于修剪的32位哈希值具有均匀的元素分布。但是Deque
会顺序插入/删除元素。
此外,ArrayList
并不将容量限制为2的幂,只是确保其至少为元素数量。
因此,为什么Deque
实现要求其容量必须为2的幂次方?
答案 0 :(得分:5)
我猜是出于性能原因。例如,让我们看一下addLast
函数的实现:
public void addLast(E e) {
if (e == null)
throw new NullPointerException();
elements[tail] = e;
if ( (tail = (tail + 1) & (elements.length - 1)) == head)
doubleCapacity();
}
因此,可以写tail = (tail + 1) % elements.length
代替tail = (tail + 1) & (elements.length - 1)
(&
的运行速度比%
快)。 ArrayDeque
的源代码中多次使用了这种构造。
答案 1 :(得分:3)
2的幂可用于某些掩蔽操作。例如,从整数中获取低位位数。
因此,如果大小为64,则64-1为63,即二进制111111。
这有助于在双端队列中定位或放置元素。
答案 2 :(得分:3)
好问题。
查看代码:
正如您所说,容量始终是2的幂。此外,决不允许双端队列达到容量。
public class ArrayDeque<E> extends AbstractCollection<E>
implements Deque<E>, Cloneable, Serializable
{
/**
* The array in which the elements of the deque are stored.
* The capacity of the deque is the length of this array, which is
* always a power of two. The array is never allowed to become
* full, except transiently within an addX method where it is
* resized (see doubleCapacity) immediately upon becoming full,
* thus avoiding head and tail wrapping around to equal each
* other....
“ 2的幂”约定简化了“初始大小”:
/**
* Allocates empty array to hold the given number of elements.
*
* @param numElements the number of elements to hold
*/
private void allocateElements(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
}
最后,请注意“ mask”的使用:
/**
* Removes the last occurrence of the specified element in this
* deque (when traversing the deque from head to tail).
* If the deque does not contain the element, it is unchanged.
* More formally, removes the last element {@code e} such that
* {@code o.equals(e)} (if such an element exists).
* Returns {@code true} if this deque contained the specified element
* (or equivalently, if this deque changed as a result of the call).
*
* @param o element to be removed from this deque, if present
* @return {@code true} if the deque contained the specified element
*/
public boolean removeLastOccurrence(Object o) {
if (o == null)
return false;
int mask = elements.length - 1;
int i = (tail - 1) & mask;
Object x;
while ( (x = elements[i]) != null) {
if (o.equals(x)) {
delete(i);
return true;
}
i = (i - 1) & mask;
}
return false;
}
private boolean delete(int i) {
checkInvariants();
...
// Invariant: head <= i < tail mod circularity
if (front >= ((t - h) & mask))
throw new ConcurrentModificationException();
...
// Optimize for least element motion
if (front < back) {
if (h <= i) {
System.arraycopy(elements, h, elements, h + 1, front);
} else { // Wrap around
System.arraycopy(elements, 0, elements, 1, i);
elements[0] = elements[mask];
System.arraycopy(elements, h, elements, h + 1, mask - h);
}
elements[h] = null;
head = (h + 1) & mask;
答案 3 :(得分:3)
最后我找到了!!!
原因不仅在于性能和位掩码操作(是的,它们速度更快,但不明显)。真正的原因是允许环回 elements
的容量(如果我们使用顺序的添加/删除操作)。换句话说:在remove()
操作之后重新使用释放的单元格。
请考虑以下示例(初始容量为 16 ):
仅add()
:
添加15个元素=> head = 0,tail = 15
添加5个元素=> doubleCapacity()
=>头= 0,尾= 20,容量= 32
add()
-remove()
-add()
:
添加15个元素=> head = 0,tail = 15
删除 10个元素=> tail 向后循环以删除索引=> head = 10,tail = 15
添加更多5个元素=>容量仍然为16,elements[]
数组未重建或重新分配! =>将新元素添加到已删除元素的位置到数组的开头=> head = 10,tail = 4(循环从15-> 0-返回数组的开头> 1-> 2-> 3-> 4)。注意,将值16-19插入到索引0-3
因此,在这种情况下,使用2的幂和简洁的位运算会更有意义。通过这种方法,if ( (tail = (tail + 1) & (elements.length - 1)) == head)
之类的操作可以轻松分配并验证 looped tail
与head
不重叠(是的,实际上是愚蠢的蛇尾巴咬住头部:))
要播放的代码段:
ArrayDeque<String> q = new ArrayDeque<>(15); // capacity is 16
// add 15 elements
q.add("0"); q.add("1"); q.add("2"); q.add("3"); q.add("4");
q.add("5"); q.add("6"); q.add("7"); q.add("8"); q.add("9");
q.add("10"); q.add("11");q.add("12");q.add("13");q.add("14");
// remove 10 elements from the head => tail LOOPS BACK in the elements[]
q.poll();q.poll();q.poll();q.poll();q.poll();q.poll();q.poll();q.poll();q.poll();q.poll();
// add 5 elements => the elements[] is not reallocated!
q.add("15");q.add("16");q.add("17");q.add("18");q.add("19");
q.poll();