一、神奇的环形快递传送带
在 Java 王国的快递中心,有一台神奇的环形快递传送带(ArrayDeque),它和普通传送带不一样:普通传送带只能从一端放快递、另一端取快递,而环形传送带可以从两端任意放取快递,就像魔法一样!
这台传送带的秘密在于它的环形设计:当快递放到传送带末尾时,下一个快递可以接着放到开头,形成一个循环。快递员们可以从传送带的两头快速存取快递,效率极高!
java
arduino
// 创建一个默认的环形传送带(初始容量16)
ArrayDeque<String> expressLine = new ArrayDeque<>();
二、传送带的内部构造
2.1 传送带的核心部件
环形传送带的关键部件包括:
-
环形轨道(elements):存放快递的传送带,长度总是 2 的幂次方(8,16,32...),这样方便用魔法计算位置
-
取件口(head):传送带取快递的位置
-
放件口(tail):传送带放快递的下一个位置
-
最小轨道长度(MIN_INITIAL_CAPACITY):至少能放 8 个快递
java
java
transient Object[] elements; // 环形轨道
transient int head; // 取件口索引
transient int tail; // 放件口索引
private static final int MIN_INITIAL_CAPACITY = 8; // 最小轨道长度
2.2 传送带的建造方式
传送带可以按不同需求建造:
-
标准传送带:
new ArrayDeque()
(默认 16 个位置) -
定制大小传送带:
new ArrayDeque(20)
(能放 20 个快递,实际会调整为 32,因为必须是 2 的幂) -
复制传送带:
new ArrayDeque(existingExpress)
(复制现有快递到新传送带)
java
ini
// 建造一个能放100个快递的传送带(实际容量128,2的7次方是128)
ArrayDeque<Integer> customDeque = new ArrayDeque<>(100);
2.3 环形轨道的魔法循环
传送带的环形轨道用魔法公式计算位置:
-
放快递到下一个位置:
tail = (tail + 1) & (length - 1)
-
取快递后移动取件口:
head = (head + 1) & (length - 1)
比如长度为 8 的传送带,当 tail 到 7 时,下一个位置是 0,形成循环:
java
ini
// 模拟环形轨道操作
Object[] track = new Object[8];
int head = 0, tail = 0;
// 放第一个快递
track[tail] = "快递1";
tail = (tail + 1) & (track.length - 1); // tail=1
// 放第二个快递
track[tail] = "快递2";
tail = (tail + 1) & (track.length - 1); // tail=2
// 取快递
Object express = track[head]; // 快递1
head = (head + 1) & (track.length - 1); // head=1
三、传送带的工作流程
3.1 快递员放快递(插入操作)
3.1.1 紧急快递优先放(addFirst)
当有紧急快递时,快递员会把它放到取件口前面,这样下一个就会被取走:
java
arduino
// 放紧急快递到取件口前
expressLine.addFirst("加急文件");
3.1.2 普通快递按顺序放(addLast)
普通快递放到传送带末尾:
java
arduino
// 放普通快递到末尾
expressLine.addLast("普通包裹");
expressLine.addLast("另一个普通包裹");
3.1.3 传送带扩容(doubleCapacity)
当传送带满了(head==tail),会启动扩容魔法:
-
建造一个两倍长的新传送带
-
把旧传送带上的快递搬到新传送带上
-
调整取件口和放件口位置
java
ini
// 扩容魔法简化示意
private void doubleCapacity() {
// 旧传送带长度
int oldLength = elements.length;
// 新传送带长度是旧的2倍
int newLength = oldLength << 1;
Object[] newTrack = new Object[newLength];
// 搬运快递:先搬取件口到末尾的,再搬开头到取件口前的
System.arraycopy(elements, head, newTrack, 0, oldLength - head);
System.arraycopy(elements, 0, newTrack, oldLength - head, head);
elements = newTrack;
head = 0;
tail = oldLength;
}
3.2 快递员取快递(删除操作)
3.2.1 取最前面的快递(removeFirst)
从取件口取走第一个快递:
java
ini
// 取最前面的快递
String firstExpress = expressLine.removeFirst();
3.2.2 取最后面的快递(removeLast)
从传送带末尾取快递:
java
ini
// 取最后面的快递
String lastExpress = expressLine.removeLast();
3.3 查看快递(查看操作)
3.3.1 看最前面的快递(getFirst)
不取出,只查看取件口的快递:
java
ini
// 查看最前面的快递
String firstExpress = expressLine.getFirst();
3.3.2 看最后面的快递(getLast)
查看传送带末尾的快递:
java
ini
// 查看最后面的快递
String lastExpress = expressLine.getLast();
四、传送带的特殊功能
4.1 传送带遍历员(迭代器)
有一个专门的遍历员可以沿着传送带走一圈,按顺序查看所有快递:
java
scss
// 遍历传送带上的所有快递
for (String express : expressLine) {
System.out.println(express);
}
4.2 多快递员问题(线程安全)
如果多个快递员同时操作传送带,可能会混乱。这时候需要用安全传送带(LinkedBlockingDeque):
java
arduino
// 安全传送带,多线程可用
LinkedBlockingDeque<String> safeLine = new LinkedBlockingDeque<>();
五、环形传送带的应用场景
5.1 叠盘子游戏(栈实现)
传送带可以当栈用,就像叠盘子,先叠的在下面,后叠的在上面:
java
arduino
// 用传送带当栈
ArrayDeque<Integer> stack = new ArrayDeque<>();
stack.addFirst(1); // 压入1
stack.addFirst(2); // 压入2,现在栈顶是2
int top = stack.removeFirst(); // 弹出2,栈里剩下1
5.2 排队买咖啡(队列实现)
也可以当普通队列,先到先得:
java
arduino
// 用传送带当队列
ArrayDeque<String> queue = new ArrayDeque<>();
queue.addLast("顾客A"); // 排队
queue.addLast("顾客B");
String first = queue.removeFirst(); // 顾客A先买到咖啡
5.3 滑动窗口找最大值(滑动窗口算法)
比如计算最近 3 天的最高温度,传送带只保留窗口内的温度,并维护最大值:
java
arduino
// 滑动窗口找最大值
public int[] maxSlidingWindow(int[] nums, int k) {
ArrayDeque<Integer> deque = new ArrayDeque<>();
int[] result = new int[nums.length - k + 1];
int index = 0;
for (int i = 0; i < nums.length; i++) {
// 移除窗口外的索引
while (!deque.isEmpty() && deque.peekFirst() <= i - k) {
deque.pollFirst();
}
// 移除比当前值小的索引,保持队列降序
while (!deque.isEmpty() && nums[deque.peekLast()] <= nums[i]) {
deque.pollLast();
}
deque.offerLast(i);
// 窗口形成时记录最大值
if (i >= k - 1) {
result[index++] = nums[deque.peekFirst()];
}
}
return result;
}
5.4 迷宫探险(BFS 算法)
传送带可以用来按层探索迷宫,就像逐层搜索:
java
arduino
// BFS迷宫探险
class Maze {
void bfs(int start) {
ArrayDeque<Integer> queue = new ArrayDeque<>();
boolean[] visited = new boolean[100]; // 假设迷宫100个节点
queue.offerLast(start);
visited[start] = true;
while (!queue.isEmpty()) {
int current = queue.pollFirst();
System.out.println("探索节点: " + current);
// 探索所有相邻节点
for (int neighbor : getNeighbors(current)) {
if (!visited[neighbor]) {
visited[neighbor] = true;
queue.offerLast(neighbor);
}
}
}
}
}
六、快递传送带常见问题
6.1 传送带太小怎么办?
如果知道有很多快递,提前设置大一点的传送带,避免频繁扩容:
java
arduino
// 预估放100个快递,设置初始容量128
ArrayDeque<String> bigLine = new ArrayDeque<>(128);
6.2 想放 null 快递?
传送带不接受 null 快递,放的时候会报错,需要提前检查:
java
ini
String express = getExpress();
if (express != null) {
expressLine.addLast(express);
}
6.3 传送带太慢?
传送带的操作大部分是 O (1) 的,比如头尾插入删除,比 LinkedList 随机访问快,但不支持快速随机访问,适合头尾操作场景。
七、传送带的未来升级
Java 王国的工匠们还在优化传送带:
-
更快的扩容算法,减少快递搬运时间
-
多线程安全的传送带升级版本
-
支持更多魔法操作,比如批量处理快递
通过这台神奇的环形快递传送带(ArrayDeque),我们可以高效地处理各种需要双端操作的数据,就像快递员高效处理快递一样,这就是 ArrayDeque 的魅力!