ArrayDeque扩容发生在head==tail且size()==elements.length时,此时元素分两段存储,doubleCapacity将其合并为单段连续存储并重置head=0、tail=n,容量翻倍且保持2的幂次方。

ArrayDeque 扩容发生在 head == tail 时,这表示数组已满(不是空),此时触发 doubleCapacity() 方法。它的核心不是简单复制,而是围绕循环结构重排数据、保持逻辑连续性,并严格维持数组长度为 2 的幂次方——这是位运算高效索引的前提。
为什么 head == tail 表示“满”而不是“空”?
ArrayDeque 初始化时 head 和 tail 都为 0,此时队列为空;随着入队,tail 前进;出队时 head 前进。只有当 tail 追上 head(即再次相等),才说明所有位置都被占用过、无空位可插——这就是“假溢出”被填满的状态。
关键判断逻辑(JDK 8 源码):
- 空队列:head == tail,且数组未使用过(初始状态)
- 满队列:head == tail,但数组中已有元素(即 size() == elements.length)
- 扩容唯一入口:
if (head == tail)+size() == elements.length同时成立
扩容前的数据分布与两段拷贝策略
由于是循环数组,元素在物理数组中可能不连续:head 可能在 tail 左侧(正常顺序),也可能在 tail 右侧(绕圈后 head 在前、tail 在后)。doubleCapacity 只在 head == tail 时执行,此时必然满足 所有元素都集中在从 head 开始、绕过末尾、再回到开头的一段连续逻辑区间,因此实际只存在一种分布:元素占据 [head, elements.length) 和 [0, head) 两块区域。
源码中的拷贝逻辑正是基于此:
- 计算 head 右侧元素个数:
r = elements.length - head - 把
[head, end)拷贝到新数组开头:System.arraycopy(elements, head, a, 0, r) - 把
[0, head)拷贝到新数组紧接着的位置:System.arraycopy(elements, 0, a, r, head) - 结果:所有元素在新数组中变为 单段连续存储,从索引 0 开始
指针重置与容量翻倍的硬约束
扩容后必须重设 head 和 tail,让新数组回归“干净起点”状态,同时保证后续位运算有效:
-
head = 0:新数组中第一个元素位于索引 0 -
tail = n:n 是原数组长度,即新数组前 n 个位置已存满元素,下一个插入位置在索引 n - 新容量
newCapacity = n ,始终是 2 的幂 —— 因为初始容量就是 2 的幂(如 16),每次翻倍仍保持该性质 - 这确保了
(index + 1) & (newCapacity - 1)能正确回绕,无需取模运算
不扩容的边界情况与容量选择
虽然叫 doubleCapacity,但 ArrayDeque 实际扩容策略在早期版本有细微差别;JDK 8 及以后统一为严格翻倍,前提是初始容量已对齐 2 的幂:
- 构造时传入容量 3 → 实际分配 8(向上取最近 2ⁿ ≥ max(8,3))
- 传入 9 → 分配 16;传入 33 → 分配 64
- 扩容永远是 ×2,不存在 1.5 倍说法(该说法混淆了其他集合如 ArrayList)
- 最小容量固定为 8,避免小数组频繁扩容

















