服务器测评网
我们一直在努力

java 中栈怎么使用

在 Java 中,栈(Stack)是一种重要的线性数据结构,遵循“后进先出”(LIFO, Last In First Out)的原则,即最后入栈的元素将最先被出栈,栈在算法实现、表达式求值、函数调用管理、括号匹配等场景中有着广泛应用,本文将详细介绍 Java 中栈的使用方法,包括核心 API、实际应用场景、注意事项及最佳实践。

java 中栈怎么使用

Java 核心栈实现:java.util.Stack

Java 提供了内置的 Stack 类,继承自 Vector,属于线程安全的动态栈实现,其核心方法如下:

入栈操作:push(E item)

push() 方法用于将元素压入栈顶,并返回该元素。

Stack<Integer> stack = new Stack<>();  
stack.push(10); // 栈底 [10]  
stack.push(20); // 栈底 [10, 20]  
stack.push(30); // 栈底 [10, 20, 30]  

出栈操作:pop()

pop() 方法用于移除并返回栈顶元素,若栈为空则抛出 EmptyStackException

Integer top = stack.pop(); // 返回 30,栈变为 [10, 20]  

查看栈顶元素:peek()

peek() 方法仅获取栈顶元素而不移除,若栈为空同样抛出 EmptyStackException,常用于检查栈顶数据而不修改栈结构。

Integer currentTop = stack.peek(); // 返回 20,栈仍为 [10, 20]  

判断栈是否为空:empty()

该方法返回布尔值,判断栈中是否无元素。

boolean isEmpty = stack.empty(); // 返回 false  

搜索元素:search(Object o)

search() 方法返回元素在栈中的位置(从 1 开始计数,栈顶为 1),若元素不存在则返回 -1。

java 中栈怎么使用

int position = stack.search(10); // 返回 2(10 位于栈底,从栈顶数第二个)  

栈的常见应用场景

括号匹配验证

栈的经典应用之一是检查括号是否成对匹配(如 、[()]),遍历字符串时,遇到左括号则入栈,遇到右括号则检查栈顶左括号是否匹配,匹配则出栈,否则不合法,遍历结束后,若栈为空则说明完全匹配。

public boolean isValid(String s) {  
    Stack<Character> stack = new Stack<>();  
    for (char c : s.toCharArray()) {  
        if (c == '(' || c == '[' || c == '{') {  
            stack.push(c);  
        } else {  
            if (stack.isEmpty()) return false;  
            char top = stack.pop();  
            if (c == ')' && top != '(') return false;  
            if (c == ']' && top != '[') return false;  
            if (c == '}' && top != '{') return false;  
        }  
    }  
    return stack.isEmpty();  
}  

函数调用栈与递归

JVM 的内存管理中,每个线程都有一个“虚拟机栈”(JVM Stack),用于存储方法调用时的局部变量、操作数栈等信息,递归本质上就是利用栈的特性:每次递归调用时,当前方法的状态(参数、局部变量)压入调用栈,递归返回时再依次弹出。

计算阶乘的递归方法:

public int factorial(int n) {  
    if (n == 1) return 1;  
    return n * factorial(n - 1); // 每次调用都会将当前 n 和返回地址压栈  
}  

表达式求值与转换

  • 后缀表达式(逆波兰表达式)求值:使用栈存储操作数,遇到运算符时弹出两个操作数计算,结果压栈,表达式 3 4 + 2 * 的计算过程:

    1. 遇到 3、4 压栈(栈:[3, 4]);
    2. 遇到 ,弹出 4 和 3,计算 3+4=7 压栈(栈:[7]);
    3. 遇到 2 压栈(栈:[7, 2]);
    4. 遇到 ,弹出 2 和 7,计算 7*2=14 压栈(栈:[14]),最终结果为 14。
  • 中缀转后缀:通过栈处理运算符优先级,确保表达式转换正确。a + b * c 转换为 a b c * +

Stack 类的注意事项

线程安全问题

Stack 继承自 Vector,其所有方法(如 pushpop)均被 synchronized 修饰,是线程安全的,但在单线程场景下,同步操作会带来不必要的性能开销,若无需线程安全,推荐使用 LinkedListArrayDeque(后文将介绍)。

java 中栈怎么使用

继承自 Vector 的潜在问题

Vector 是基于动态数组实现的,Stack 继承了其扩容机制:当容量不足时,会重新分配数组(默认容量翻倍)并复制元素,可能导致性能波动。Stack 暴露了父类 Vector 的方法(如 add(index, element)),允许直接操作栈中间元素,破坏了栈的“后进先出”特性。

Stack<Integer> stack = new Stack<>();  
stack.push(1);  
stack.push(2);  
stack.add(0, 3); // 直接在栈底插入 3,栈变为 [3, 1, 2],违反 LIFO 规则  

更优的替代方案:ArrayDeque

Java 6 引入了 ArrayDeque(基于循环数组实现的双端队列),它实现了 Deque 接口,既可作为队列使用,也可作为栈使用,相比 StackArrayDeque 具有以下优势:

  • 非线程安全:单线程下性能更高(无同步开销);
  • 扩容机制更优:初始容量为 16,扩容时按 2 倍增长,减少内存浪费;
  • API 更符合栈语义:提供 push()pop()peek() 等方法,且不会暴露破坏数据结构的方法。

使用示例:

Deque<Integer> stack = new ArrayDeque<>(); // 使用 Deque 接口编程,提高灵活性  
stack.push(10);  
stack.push(20);  
stack.push(30);  
System.out.println(stack.pop()); // 输出 30  
System.out.println(stack.peek()); // 输出 20  

栈的最佳实践

  1. 优先选择 ArrayDeque:在不需要线程安全的场景下,ArrayDeque 是比 Stack 更好的栈实现,性能和内存效率更优。
  2. 避免直接操作栈中间元素:即使使用 Stack,也应通过 pushpop 等核心方法操作,避免使用 Vector 的随机访问方法。
  3. 处理空栈异常:调用 pop()peek() 前,应通过 empty()isEmpty() 检查栈是否为空,或使用 try-catch 捕获 EmptyStackException
  4. 明确栈的容量需求:若元素数量可预估,可通过 ArrayDeque 的构造方法指定初始容量,减少扩容次数。Deque<Integer> stack = new ArrayDeque<>(100);

栈作为基础数据结构,在 Java 中可通过 StackArrayDeque 实现。Stack 虽然简单易用,但因其线程安全和继承设计的问题,在现代开发中逐渐被 ArrayDeque 替代,理解栈的核心原理、应用场景及最佳实践,有助于在算法设计、系统开发中灵活运用栈解决实际问题,提升代码的效率和可维护性,无论是括号匹配、表达式求值,还是递归算法,栈都发挥着不可替代的作用,是开发者必须掌握的工具之一。

赞(0)
未经允许不得转载:好主机测评网 » java 中栈怎么使用