翻OkHttp源码的过程中,发现有用到ArrayDeque双端队列,在此依据一个小例子研究一下。
// 实例化对象
ArrayDeque<String> arrayDeque = new ArrayDeque();
// 添加一个条目
arrayDeque.add("a");
arrayDeque.addFirst("b");
ArrayDeque的成员变量:
- elements:真实存储队列,可以看到就是一个Object[]数组
- head: 头部索引,初始0
- tail: 尾部索引,初始0
实例化走构造方法:
public ArrayDeque() {
elements = new Object[16];
}
1.实例化elements数组,初始默认容量16。
调用add方法添加元素,添加成功后返回true:
public boolean add(E e) {
addLast(e);
return true;
}
进addLast方法:
public void addLast(E e) {
// 判断空的话,直接抛异常。
if (e == null)
throw new NullPointerException();
// 把新元素直接塞进末尾
elements[tail] = e;
// tail自加1,同时限制在数组以内,超过数组最大index时通过&运算变为0
if ( (tail = (tail + 1) & (elements.length - 1)) == head)
doubleCapacity();
}
双端队列之所以叫双端队列,就是因为这一步,tail最大后,加一后,转为0,像一条蛇一样。同理,在首部添加元素后,head减1,如果已经是最小值0了,就转为数组index最大值!如下: 进addFirst方法:
public void addFirst(E e) {
if (e == null)
throw new NullPointerException();
// head自减1,如果是-1的话,经过&操作,变为末尾最大index
elements[head = (head - 1) & (elements.length - 1)] = e;
if (head == tail)
doubleCapacity();
}
当 head == tail 时,调用doubleCapacity方法,扩充容量,因为这时候,elements已经被塞满了
进 doubleCapacity 方法:
// elements已经被填满,扩容2倍
private void doubleCapacity() {
int p = head;
int n = elements.length;
// head指针右侧元素数量
int r = n - p;
// 新的容量直接扩充2倍
int newCapacity = n << 1;
if (newCapacity < 0)
throw new IllegalStateException("Sorry, deque too big");
// 直接创建新元素数组
Object[] a = new Object[newCapacity];
// 将elements数组中head右侧的元素copy进新数组中
System.arraycopy(elements, p, a, 0, r);
// 将elements数组中tail左侧的元素copy进新数组中
System.arraycopy(elements, 0, a, r, p);
// elements置空
Arrays.fill(elements, null);
elements = a;
head = 0;
tail = n;
}
doubleCapacity扩容方法中,比较难理解的可能就是copy数组这块,其实画个图就好理解了,这里有几种情况:
第一种,一直调用add方法把数组塞满,从add方法可以得出,head不变,tail每次加一,最大后归0,最后,tail = head = 0。
第二种,一直调用addFirst,tail不变,一直为0,head每次减1,-1后通过&操作转为最大值,下一次继续减1,最后,head = tail = 0
第三种,既调用add又调用addFirst方法,tail每次加1,head每次减1,最后,head = tail = 某个中间值
无论哪种情况,elements向新数组copy数据时,都分成两部分进行,以head = tail 的那个中间值为界限。 明白上面的图后,基本上就明白双端队列是怎么一回事了。再看一下源码中的其他方法:
// 设定数组数量,设定的值为给定参数对应的二进制值最大值,很好理解
// 比方说给定20,20的二进制是10100,经过下列运算后就是100000,转成十进制就是32
// 再比方说给定35,二进制是100011,经过运算后就是1000000,即64
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
}
elements = new Object[initialCapacity];
}
pollFirst和pollLast是反操作,没什么可说的。
// 移除第一遇到的某元素,从head开始找,找到为止或者找到null元素为止
public boolean removeFirstOccurrence(Object o) {
if (o != null) {
int mask = elements.length - 1;
int i = head;
for (Object x; (x = elements[i]) != null; i = (i + 1) & mask) {
if (o.equals(x)) {
delete(i);
return true;
}
}
}
return false;
}
// removeFirstOccurrence的反向操作,从末位开始找
public boolean removeLastOccurrence(Object o) {
if (o != null) {
int mask = elements.length - 1;
int i = (tail - 1) & mask;
for (Object x; (x = elements[i]) != null; i = (i - 1) & mask) {
if (o.equals(x)) {
delete(i);
return true;
}
}
}
return false;
}
// 删除某pos的元素,其实就是把其他位置的元素覆盖上去,然后空出边缘的pos置null,有点像贪吃蛇,分清head和tail后很好理解
boolean delete(int i) {
checkInvariants();
final Object[] elements = this.elements;
final int mask = elements.length - 1;
final int h = head;
final int t = tail;
final int front = (i - h) & mask;
final int back = (t - i) & mask;
// 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;
return false;
} else {
if (i < t) { // Copy the null tail as well
System.arraycopy(elements, i + 1, elements, i, back);
tail = t - 1;
} else { // Wrap around
System.arraycopy(elements, i + 1, elements, i, mask - i);
elements[mask] = elements[0];
System.arraycopy(elements, 1, elements, 0, t);
tail = (t - 1) & mask;
}
return true;
}
}