跳转至

Java ArrayDeque 与 Stack:深入解析与实践

简介

在 Java 的集合框架中,ArrayDequeStack 是两个用于处理栈和队列相关操作的数据结构。Stack 类是 Java 早期版本中用于实现栈数据结构的类,而 ArrayDeque 是 Java 1.6 引入的,它既可以当作栈使用,也可以当作队列使用。理解它们的基础概念、使用方法以及最佳实践,对于编写高效且正确的代码至关重要。本文将详细探讨这两个数据结构,帮助读者更好地掌握它们在实际项目中的应用。

目录

  1. 基础概念
    • ArrayDeque
    • Stack
  2. 使用方法
    • ArrayDeque 作为栈使用
    • Stack 的使用
  3. 常见实践
    • 表达式求值
    • 深度优先搜索(DFS)
  4. 最佳实践
    • 性能考量
    • 选择合适的数据结构
  5. 小结

基础概念

ArrayDeque

ArrayDeque 是一个基于数组实现的双端队列(Deque,即 Double - ended Queue)。它允许在队列的两端进行插入和删除操作,既可以当作栈使用(后进先出,LIFO),也可以当作队列使用(先进先出,FIFO)。ArrayDeque 是非线程安全的,在单线程环境下性能较高。

Stack

Stack 类是一个较老的类,它继承自 Vector。它是一个典型的栈数据结构,遵循后进先出(LIFO)的原则。与 ArrayDeque 不同,Stack 是线程安全的,但由于其实现方式和线程安全机制,在单线程环境下性能相对较差。

使用方法

ArrayDeque 作为栈使用

import java.util.ArrayDeque;
import java.util.Deque;

public class ArrayDequeAsStackExample {
    public static void main(String[] args) {
        // 创建一个 ArrayDeque 对象作为栈
        Deque<Integer> stack = new ArrayDeque<>();

        // 入栈操作
        stack.push(1);
        stack.push(2);
        stack.push(3);

        // 查看栈顶元素
        System.out.println("栈顶元素: " + stack.peek());

        // 出栈操作
        while (!stack.isEmpty()) {
            System.out.println("出栈元素: " + stack.pop());
        }
    }
}

在上述代码中: 1. 首先创建了一个 ArrayDeque 对象,并将其当作栈使用。 2. 使用 push 方法将元素压入栈中。 3. 使用 peek 方法查看栈顶元素,而不弹出元素。 4. 使用 pop 方法弹出栈顶元素,并在循环中依次将所有元素出栈。

Stack 的使用

import java.util.Stack;

public class StackExample {
    public static void main(String[] args) {
        // 创建一个 Stack 对象
        Stack<Integer> stack = new Stack<>();

        // 入栈操作
        stack.push(1);
        stack.push(2);
        stack.push(3);

        // 查看栈顶元素
        System.out.println("栈顶元素: " + stack.peek());

        // 出栈操作
        while (!stack.isEmpty()) {
            System.out.println("出栈元素: " + stack.pop());
        }
    }
}

这段代码与 ArrayDeque 作为栈使用的代码结构类似,只是使用了 Stack 类。Stack 类也提供了 pushpeekpop 方法来进行栈的基本操作。

常见实践

表达式求值

表达式求值是栈的一个常见应用场景。以下是一个简单的中缀表达式求值的示例,使用 ArrayDeque 作为栈:

import java.util.ArrayDeque;
import java.util.Deque;

public class ExpressionEvaluation {
    public static int evaluateExpression(String expression) {
        Deque<Integer> values = new ArrayDeque<>();
        Deque<Character> operators = new ArrayDeque<>();

        for (int i = 0; i < expression.length(); i++) {
            if (Character.isDigit(expression.charAt(i))) {
                int val = 0;
                while (i < expression.length() && Character.isDigit(expression.charAt(i))) {
                    val = val * 10 + expression.charAt(i) - '0';
                    i++;
                }
                values.push(val);
            } else if (expression.charAt(i) == '(') {
                operators.push(expression.charAt(i));
            } else if (expression.charAt(i) == ')') {
                while (!operators.isEmpty() && operators.peek()!= '(') {
                    values.push(applyOperator(operators.pop(), values.pop(), values.pop()));
                }
                operators.pop(); // 弹出 '('
            } else if (isOperator(expression.charAt(i))) {
                while (!operators.isEmpty() && precedence(operators.peek()) >= precedence(expression.charAt(i))) {
                    values.push(applyOperator(operators.pop(), values.pop(), values.pop()));
                }
                operators.push(expression.charAt(i));
            }
        }

        while (!operators.isEmpty()) {
            values.push(applyOperator(operators.pop(), values.pop(), values.pop()));
        }

        return values.pop();
    }

    private static boolean isOperator(char c) {
        return c == '+' || c == '-' || c == '*' || c == '/';
    }

    private static int precedence(char c) {
        switch (c) {
            case '+':
            case '-':
                return 1;
            case '*':
            case '/':
                return 2;
            default:
                return -1;
        }
    }

    private static int applyOperator(char operator, int b, int a) {
        switch (operator) {
            case '+':
                return a + b;
            case '-':
                return a - b;
            case '*':
                return a * b;
            case '/':
                return a / b;
            default:
                return 0;
        }
    }

    public static void main(String[] args) {
        String expression = "3 + 5 * 2 / ( 7 - 2 )";
        System.out.println("表达式结果: " + evaluateExpression(expression));
    }
}

在这个示例中,我们使用两个栈,一个用于存储操作数(values),另一个用于存储操作符(operators)。通过遍历表达式字符串,根据操作符的优先级和括号的匹配情况,逐步计算表达式的值。

深度优先搜索(DFS)

在图的遍历中,深度优先搜索可以使用栈来实现。以下是一个简单的图的 DFS 实现,使用 ArrayDeque 作为栈:

import java.util.ArrayDeque;
import java.util.Deque;
import java.util.HashMap;
import java.util.Map;

public class DFSExample {
    private Map<Integer, boolean[]> visited;
    private Map<Integer, int[]> adj;

    public DFSExample(int vertices) {
        visited = new HashMap<>();
        adj = new HashMap<>();
        for (int i = 0; i < vertices; i++) {
            visited.put(i, new boolean[1]);
            adj.put(i, new int[0]);
        }
    }

    public void addEdge(int src, int dest) {
        int[] oldAdj = adj.get(src);
        int[] newAdj = new int[oldAdj.length + 1];
        System.arraycopy(oldAdj, 0, newAdj, 0, oldAdj.length);
        newAdj[oldAdj.length] = dest;
        adj.put(src, newAdj);
    }

    public void dfs(int start) {
        Deque<Integer> stack = new ArrayDeque<>();
        stack.push(start);

        while (!stack.isEmpty()) {
            int vertex = stack.pop();
            if (!visited.get(vertex)[0]) {
                System.out.print(vertex + " ");
                visited.get(vertex)[0] = true;
                int[] neighbors = adj.get(vertex);
                for (int i = neighbors.length - 1; i >= 0; i--) {
                    int neighbor = neighbors[i];
                    if (!visited.get(neighbor)[0]) {
                        stack.push(neighbor);
                    }
                }
            }
        }
    }

    public static void main(String[] args) {
        DFSExample graph = new DFSExample(4);
        graph.addEdge(0, 1);
        graph.addEdge(0, 2);
        graph.addEdge(1, 2);
        graph.addEdge(2, 0);
        graph.addEdge(2, 3);
        graph.addEdge(3, 3);

        System.out.println("从顶点 2 开始的 DFS 遍历:");
        graph.dfs(2);
    }
}

在这个示例中,我们定义了一个简单的图,并使用 ArrayDeque 实现了深度优先搜索。通过将顶点入栈和出栈,我们可以遍历图中的节点,并标记已经访问过的节点,以避免重复访问。

最佳实践

性能考量

  • ArrayDeque:在单线程环境下,ArrayDeque 的性能通常优于 Stack。因为 ArrayDeque 是非线程安全的,它避免了线程同步带来的开销。此外,ArrayDeque 的实现相对更紧凑和高效,适合大多数栈和队列的操作场景。
  • Stack:由于 Stack 是线程安全的,在多线程环境下,如果需要对栈进行并发访问,Stack 是一个选择。但在单线程环境下,由于其线程安全机制带来的性能损耗,应优先考虑 ArrayDeque

选择合适的数据结构

  • 如果只需要一个简单的栈结构,并且在单线程环境下,ArrayDeque 是首选。它提供了高效的栈操作方法,并且性能较好。
  • 如果需要线程安全的栈,或者在代码中已经大量使用了 Vector 相关的类(因为 Stack 继承自 Vector),可以考虑使用 Stack。但在多线程环境下,也可以考虑使用并发安全的栈实现,如 ConcurrentLinkedDeque

小结

本文详细介绍了 Java 中的 ArrayDequeStack 数据结构。我们了解了它们的基础概念、使用方法、常见实践以及最佳实践。ArrayDeque 作为一个功能强大且高效的数据结构,在大多数情况下,尤其是单线程环境下,是实现栈和队列操作的理想选择。而 Stack 虽然是一个较老的类,但在特定的多线程场景下仍有其应用价值。通过掌握这两个数据结构的特点和使用方法,开发者可以更加灵活地编写高效、健壮的代码。希望本文能帮助读者更好地理解和运用 ArrayDequeStack,在实际项目中发挥它们的优势。