Java里有一个叫做Stack的类,却没有叫做Queue的类(它是个接口名字)。而当需要使用栈时,Java已不推荐使用Stack,而是推荐使用更高效的ArrayDeque;既然Queue只是一个接口,当需要使用队列时也就首选ArrayDeque了(次选是LinkedList)。
ArrayDeque底层实际上是使用双向队列数组结构来实现的。那我们知道使用双向队列就不得不面临一个问题,那就是取余,然而直接使用符号’%‘来进行取余在效率上是不高的。在这里ArrayDeque使用了一个非常好的方法,使用’&'符号,那么这个符号是如何发挥作用的呢?我们可以从源码当中一探究竟。
public void addFirst(E e) {
if (e == null)
throw new NullPointerException();
elements[head = (head - 1) & (elements.length - 1)] = e;
if (head == tail)
doubleCapacity();
}
在addFirst函数中显然取余是通过:(head - 1) & (elements.length - 1)
来完成的。另外,这里是先添加元素,而后检查扩容的,因为双向队列的数组中总有一个位置是没有用到的,所以可以直接添加元素而后检查扩容。回到我们的主题,在ArrayDeque中有一个规定:elements.length一定是2的指数。虽然我们可以在初始化的时候写入一个大小,但是ArrayDeque会自动将初始化数组增大到2的指数,且最小为8。
我们以一个例子分析一下,假如elements.length=0x100(256);则elements.length-1=0xff(255);而head-1最小为-1(0xffffffff)时,(head - 1) & (elements.length - 1)=0xff;指向了数组的尾部,也就没有越界。如果是addLast,tail+1的时候,最大为256(0x100)时,(tail + 1) & (elements.length - 1)=0;指向了数组头部,其他也是类似的,完美解决了取余效率不高的问题。
总结来说就是如果是对2的指数取余的时候,我们可以用num&(2^x - 1)来提高取余效率,这种方法还使用在HashMap中;