ArrayDeque在Java中基于可变数组实现,支持高效双端操作,适合作为栈(用push/pop/peek)和队列(用offer/poll/peek)使用,内存紧凑、性能优越;相比LinkedList,其内存局部性更好、迭代更快,但扩容时有O(n)开销;推荐优先使用push/pop/peek模拟栈,避免add/remove抛异常,选用offer/poll处理队列更安全,并预估初始容量以减少扩容开销。

ArrayDeque在Java中是一个非常实用的数据结构,它实现了Deque接口,可以高效地作为栈(LIFO,后进先出)和队列(FIFO,先进先出)来使用。它的核心优势在于基于可变数组实现,这意味着在两端添加和移除元素都非常迅速,通常接近O(1)的时间复杂度,并且相比于链表实现的Deque,它在内存使用上更为紧凑,避免了每个节点额外的对象开销。
ArrayDeque作为Java集合框架中的一员,其核心使用方法围绕着双端队列的特性展开。它既能模拟栈的行为,也能模拟队列的行为,这使得它在多种算法和日常编程任务中都非常得心应手。
作为栈使用时: ArrayDeque提供了
push()
pop()
peek()
java.util.Stack
Stack
Vector
push(E e)
pop()
NoSuchElementException
peek()
null
作为队列使用时: ArrayDeque提供了
offer()
poll()
peek()
Queue
offer(E e)
poll()
null
peek()
null
此外,ArrayDeque也提供了
addFirst()
addLast()
removeFirst()
removeLast()
getFirst()
getLast()
push()
offer()
pop()
poll()
add()
remove()
offer()
poll()
false
null
import java.util.ArrayDeque;
public class ArrayDequeExample {
public static void main(String[] args) {
// 作为栈使用
System.out.println("--- 作为栈使用 ---");
ArrayDeque<String> stack = new ArrayDeque<>();
stack.push("A"); // 栈顶
stack.push("B");
stack.push("C"); // 新栈顶
System.out.println("栈顶元素 (peek): " + stack.peek()); // 输出 C
System.out.println("弹出元素 (pop): " + stack.pop()); // 输出 C
System.out.println("弹出元素 (pop): " + stack.pop()); // 输出 B
System.out.println("当前栈: " + stack); // 输出 [A]
// 作为队列使用
System.out.println("\n--- 作为队列使用 ---");
ArrayDeque<String> queue = new ArrayDeque<>();
queue.offer("X"); // 队尾
queue.offer("Y");
queue.offer("Z"); // 新队尾
System.out.println("队头元素 (peek): " + queue.peek()); // 输出 X
System.out.println("出队元素 (poll): " + queue.poll()); // 输出 X
System.out.println("出队元素 (poll): " + queue.poll()); // 输出 Y
System.out.println("当前队列: " + queue); // 输出 [Z]
}
}在我个人的开发经验中,选择ArrayDeque还是LinkedList作为双端队列,这往往取决于具体的应用场景和对性能的细微要求。它们虽然都实现了Deque接口,但底层实现机制的差异,决定了它们在不同操作上的性能表现。
立即学习“Java免费学习笔记(深入)”;
首先,底层数据结构是根本区别。ArrayDeque基于动态数组实现,而LinkedList则基于双向链表。这意味着ArrayDeque的数据在内存中是连续存放的,而LinkedList的每个元素都是一个独立的节点对象,包含数据本身以及指向前后节点的引用。
这种差异直接导致了内存效率的不同。ArrayDeque通常在内存使用上更紧凑。LinkedList的每个节点除了存储实际数据外,还需要额外的空间来存储
next
prev
在两端操作(添加/移除)方面,两者都能实现接近O(1)的时间复杂度。ArrayDeque通过维护头尾指针,可以在数组的两端高效地添加或移除元素。但需要注意的是,当数组空间不足时,ArrayDeque会进行扩容,这涉及到一个新的更大数组的分配和旧数组元素的拷贝,这个操作的复杂度是O(n)。LinkedList则每次操作都涉及创建或销毁一个节点,并调整前后节点的引用,这同样是O(1)的,且没有扩容的隐患。所以,如果你的应用场景是频繁地在两端进行大量操作,并且对性能的稳定性有较高要求,LinkedList在极端情况下可能表现得更稳定一些,因为它没有ArrayDeque那种潜在的扩容开销。然而,实际情况中,ArrayDeque的扩容机制通常优化得很好,分摊下来,其平均性能依然非常优秀。
中间操作(例如在队列中间插入或删除元素)对两者来说都不是高效的。ArrayDeque需要移动大量元素,复杂度是O(n)。LinkedList虽然理论上可以通过遍历找到中间位置,然后进行O(1)的插入/删除,但寻找中间位置本身就是O(n)的,所以总体上也是O(n)。但话说回来,双端队列的设计初衷就不是为了高效地进行中间操作。
迭代性能上,ArrayDeque通常会优于LinkedList。由于ArrayDeque的数据是连续存储的,遍历时CPU的缓存命中率会更高。LinkedList在遍历时需要跳跃访问内存中的不同节点,这可能会导致更多的缓存未命中。
总的来说,如果我对性能有要求,并且操作主要集中在队列的两端,我会优先考虑ArrayDeque。它的内存效率和迭代性能通常更优。只有在需要频繁在中间进行操作(尽管这不符合Deque的典型用法),或者对内存分配的稳定性有极致要求时,我才会考虑LinkedList。
将ArrayDeque作为栈来使用,是我个人非常推崇的做法,因为它比Java标准库提供的
java.util.Stack
一个核心的推荐是优先使用push()
pop()
peek()
push(E e)
pop()
peek()
尽管ArrayDeque也提供了
addFirst()
removeFirst()
getFirst()
push/pop/peek
关于空栈处理,这是一个需要特别注意的地方。
pop()
peek()
NoSuchElementException
isEmpty()
pollFirst()
pop()
peekFirst()
peek()
null
// 示例:使用ArrayDeque进行括号匹配
import java.util.ArrayDeque;
import java.util.HashMap;
import java.util.Map;
public class BracketMatcher {
public boolean isValid(String s) {
ArrayDeque<Character> stack = new ArrayDeque<>();
Map<Character, Character> mappings = new HashMap<>();
mappings.put(')', '(');
mappings.put('}', '{');
mappings.put(']', '[');
for (char c : s.toCharArray()) {
if (mappings.containsKey(c)) { // 是闭括号
char topElement = stack.isEmpty() ? '#' : stack.pop(); // 如果栈为空,则用'#'占位,避免空指针
if (topElement != mappings.get(c)) {
return false;
}
} else { // 是开括号
stack.push(c);
}
}
return stack.isEmpty(); // 所有括号都匹配成功,栈应为空
}
public static void main(String[] args) {
BracketMatcher matcher = new BracketMatcher();
System.out.println("()[]{} is valid: " + matcher.isValid("()[]{}")); // true
System.out.println("([{}]) is valid: " + matcher.isValid("([{}])")); // true
System.out.println("({[}) is valid: " + matcher.isValid("({[})")); // false
System.out.println("(] is valid: " + matcher.isValid("(]")); // false
System.out.println("{ is valid: " + matcher.isValid("{")); // false
}
}在这个括号匹配的例子中,我们清晰地使用了
push()
pop()
pop()
isEmpty()
NoSuchElementException
最后,初始容量的预估。ArrayDeque在创建时可以指定一个初始容量。如果能大致预估栈中将要存储的元素数量,在构造函数中提供一个合适的初始容量,可以减少后续因扩容而产生的数组拷贝开销,从而在一定程度上优化性能。例如:
ArrayDeque<Integer> stack = new ArrayDeque<>(100);
将ArrayDeque作为队列来使用,同样非常高效且常见,尤其是在实现广度优先搜索(BFS)或任务调度等场景。然而,在使用过程中,一些常见的陷阱需要注意,以确保代码的健壮性和正确性。
一个主要的陷阱是混淆不同入队/出队方法的行为差异。ArrayDeque实现了Deque接口,所以它提供了多种添加和移除元素的方法。
add(E e)
offer(E e)
addLast(E e)
offerLast(E e)
add(E e)
addLast(E e)
IllegalStateException
offer(E e)
offerLast(E e)
false
remove()
poll()
removeFirst()
pollFirst()
remove()
removeFirst()
NoSuchElementException
poll()
pollFirst()
null
因此,最佳实践是优先使用offer()
poll()
另一个需要注意的方面是ArrayDeque的容量特性。ArrayDeque在逻辑上是一个无界队列,它会根据需要自动扩容。这意味着你不需要担心队列“满”的问题(除非系统内存耗尽)。但如果你的应用场景确实需要一个有固定容量限制的队列,并且在队列满时需要阻塞或拒绝新的元素,那么ArrayDeque就不适合了。在这种情况下,你可能需要考虑
java.util.concurrent
ArrayBlockingQueue
遍历队列时,也存在一些需要留意的点。使用迭代器或增强for循环遍历ArrayDeque是安全的。然而,在遍历过程中修改队列(例如添加或移除元素,除了通过迭代器自身的
remove()
ConcurrentModificationException
// 示例:使用ArrayDeque进行广度优先搜索(BFS)
import java.util.ArrayDeque;
import java.util.HashSet;
import java.util.Set;
public class BFSExample {
// 假设这是一个简单的图,用邻接列表表示
// 这里为了简化,直接用字符串表示节点,并手动构建邻居关系
private static Map<String, String[]> graph = new HashMap<>();
static {
graph.put("A", new String[]{"B", "C"});
graph.put("B", new String[]{"A", "D", "E"});
graph.put("C", new String[]{"A", "F"});
graph.put("D", new String[]{"B"});
graph.put("E", new String[]{"B", "F"});
graph.put("F", new String[]{"C", "E"});
}
public void bfs(String startNode) {
ArrayDeque<String> queue = new ArrayDeque<>();
Set<String> visited = new HashSet<>();
queue.offer(startNode);
visited.add(startNode);
System.out.println("BFS Traversal starting from " + startNode + ":");
while (!queue.isEmpty()) {
String currentNode = queue.poll(); // 安全地从队列头部取出元素
System.out.print(currentNode + " ");
// 获取当前节点的邻居
String[] neighbors = graph.get(currentNode);
if (neighbors != null) {
for (String neighbor : neighbors) {
if (!visited.contains(neighbor)) {
visited.add(neighbor);
queue.offer(neighbor); // 安全地将邻居添加到队列尾部
}
}
}
}
System.out.println();
}
public static void main(String[] args) {
BFSExample bfs = new BFSExample();
bfs.bfs("A"); // Output: A B C D E F
}
}在这个BFS示例中,我们始终使用
offer()
poll()
以上就是Java中ArrayDeque的核心使用方法的详细内容,更多请关注php中文网其它相关文章!
每个人都需要一台速度更快、更稳定的 PC。随着时间的推移,垃圾文件、旧注册表数据和不必要的后台进程会占用资源并降低性能。幸运的是,许多工具可以让 Windows 保持平稳运行。
Copyright 2014-2025 https://www.php.cn/ All Rights Reserved | php.cn | 湘ICP备2023035733号