JAVA中的堆和栈
在 Java 中,“堆” 和 “栈” 有两层含义:一是JVM 内存模型中的堆(Heap)和栈(Stack),二是数据结构中的堆(Heap)和栈(Stack)。而 “优先队列(PriorityQueue)” 是基于数据结构中的堆实现的高级容器。下面从这两个维度分别总结,并结合使用方法和案例说明。
一、JVM 内存模型中的堆与栈
这是 Java 内存管理的核心概念,与对象存储、内存分配直接相关。
1. 堆(Heap)
- 概念:JVM 中最大的内存区域,线程共享,用于存储所有对象实例(包括数组)和字符串常量池(JDK7 后移至堆)。
- 特性:- 动态分配内存,大小可通过 JVM 参数(如-Xms、-Xmx)调整。
- 内存回收由垃圾回收器(GC)自动管理,无需手动释放。
- 分配速度较慢(需处理内存碎片、GC 等),但存储容量大。
 
- 动态分配内存,大小可通过 JVM 参数(如
2. 栈(Stack,虚拟机栈)
- 概念:线程私有,每个线程对应一个虚拟机栈,由多个栈帧组成(每个方法调用对应一个栈帧)。
- 特性:- 存储局部变量(基本类型、对象引用)、方法返回地址、操作数栈等。
- 遵循 “后进先出(LIFO)” 原则,方法调用时入栈,执行完毕后出栈。
- 内存自动分配和释放(随方法调用 / 结束),速度快,无内存碎片。
 
3. 核心区别与使用场景
| 维度 | 堆(JVM) | 栈(JVM) | 
|---|---|---|
| 线程共享性 | 共享(所有线程可访问) | 私有(仅当前线程访问) | 
| 存储内容 | 对象实例、字符串常量池 | 局部变量、方法栈帧 | 
| 内存管理 | 垃圾回收器自动回收 | 随方法调用 / 结束自动释放 | 
| 速度 | 较慢 | 较快 | 
案例:变量存储位置
public class MemoryDemo {// 成员变量(存储在堆中,随对象实例存在)private String heapVar = "堆中的字符串";public void method() {// 局部变量(基本类型,存储在栈中)int stackVar = 10;// 对象引用(引用存栈中,对象实例存堆中)String ref = new String("栈中引用,堆中对象");}
}二、数据结构中的栈(Stack)
数据结构中的栈是一种线性表,仅允许在一端(栈顶)进行插入和删除操作,遵循 “后进先出(LIFO)” 原则。
1. 常用实现与方法
Java 中不推荐使用java.util.Stack(继承Vector,线程安全导致性能开销),推荐用Deque接口的ArrayDeque实现(更高效)。
核心方法:
- push(E e):入栈(添加元素到栈顶)。
- pop():出栈(移除并返回栈顶元素,栈空时抛异常)。
- poll():出栈(移除并返回栈顶元素,栈空时返回- null)。
- peek():查看栈顶元素(不移除)。
- isEmpty():判断是否为空。
2. 使用案例:逆波兰表达式求值
逆波兰表达式(后缀表达式)是运算符在操作数后的表达式,适合用栈计算结果。
import java.util.Deque;
import java.util.ArrayDeque;public class RPNCalculator {public static int evalRPN(String[] tokens) {Deque<Integer> stack = new ArrayDeque<>();for (String token : tokens) {if (token.equals("+") || token.equals("-") || token.equals("*") || token.equals("/")) {// 弹出两个操作数计算int b = stack.pop();int a = stack.pop();switch (token) {case "+": stack.push(a + b); break;case "-": stack.push(a - b); break;case "*": stack.push(a * b); break;case "/": stack.push(a / b); break;}} else {// 数字入栈stack.push(Integer.parseInt(token));}}return stack.pop();}public static void main(String[] args) {String[] tokens = {"2","1","+","3","*"}; // 等价于 (2+1)*3System.out.println(evalRPN(tokens)); // 输出 9}
}三、数据结构中的堆(Heap)
数据结构中的堆是一种完全二叉树,分为两种:
- 最大堆:父节点值 ≥ 子节点值(堆顶为最大值)。
- 最小堆:父节点值 ≤ 子节点值(堆顶为最小值)。
Java 中无直接的Heap类,通过PriorityQueue间接实现堆功能(默认是最小堆)。
1. 常用方法(基于PriorityQueue)
- offer(E e):添加元素并调整堆结构(维持堆特性)。
- poll():移除并返回堆顶元素(最小 / 最大值),自动调整堆。
- peek():查看堆顶元素(不移除)。
- size():返回元素个数。
2. 堆的创建方式
- 最小堆(默认):new PriorityQueue<>()。
- 最大堆:new PriorityQueue<>(Collections.reverseOrder())。
- 自定义排序:传入Comparator,如new PriorityQueue<>((a, b) -> b - a)(最大堆)。
3. 使用案例:查找数据流中的中位数
用两个堆(一个最大堆存较小一半元素,一个最小堆存较大一半元素)动态维护中位数。
import java.util.*;public class MedianFinder {private PriorityQueue<Integer> maxHeap; // 存较小的一半(堆顶为最大值)private PriorityQueue<Integer> minHeap; // 存较大的一半(堆顶为最小值)public MedianFinder() {maxHeap = new PriorityQueue<>(Collections.reverseOrder());minHeap = new PriorityQueue<>();}public void addNum(int num) {// 先加入最大堆,再平衡两个堆的大小maxHeap.offer(num);minHeap.offer(maxHeap.poll());// 保证最大堆 size ≥ 最小堆 size(最多差1)if (maxHeap.size() < minHeap.size()) {maxHeap.offer(minHeap.poll());}}public double findMedian() {if (maxHeap.size() > minHeap.size()) {return maxHeap.peek(); // 奇数个元素,最大堆顶为中位数} else {return (maxHeap.peek() + minHeap.peek()) / 2.0; // 偶数个,取平均}}public static void main(String[] args) {MedianFinder finder = new MedianFinder();finder.addNum(1);finder.addNum(2);System.out.println(finder.findMedian()); // 1.5finder.addNum(3);System.out.println(finder.findMedian()); // 2.0}
}四、优先队列(PriorityQueue)
优先队列是一种特殊队列,元素按优先级排序,每次出队的是优先级最高的元素(底层基于堆实现)。
1. 核心特性
- 排序规则:默认按元素自然顺序(Comparable),或通过Comparator自定义。
- 无序性:迭代器遍历不保证顺序,仅poll()/peek()能获取优先级最高的元素。
- 时间复杂度:插入(offer)和删除(poll)为O(log n),查询堆顶为O(1)。
2. 使用案例:合并 K 个有序链表
利用优先队列每次取出最小节点,高效合并多个有序链表。
import java.util.*;// 链表节点定义
class ListNode {int val;ListNode next;ListNode(int val) { this.val = val; }
}public class MergeKLists {public ListNode mergeKLists(ListNode[] lists) {if (lists == null || lists.length == 0) return null;// 优先队列(按节点值升序,每次取最小节点)PriorityQueue<ListNode> pq = new PriorityQueue<>(Comparator.comparingInt(a -> a.val));// 初始化:将每个链表的头节点加入队列for (ListNode node : lists) {if (node != null) pq.offer(node);}ListNode dummy = new ListNode(0);ListNode cur = dummy;// 循环取最小节点,拼接结果链表while (!pq.isEmpty()) {ListNode minNode = pq.poll();cur.next = minNode;cur = cur.next;// 若当前节点有下一个节点,加入队列if (minNode.next != null) {pq.offer(minNode.next);}}return dummy.next;}
}五、总结
| 概念 | 本质 / 核心特性 | 典型使用场景 | 
|---|---|---|
| JVM 堆 | 线程共享,存储对象实例,GC 管理 | 对象创建、长生命周期数据存储 | 
| JVM 栈 | 线程私有,存储局部变量和方法帧,LIFO | 方法调用、临时变量存储 | 
| 数据结构栈 | LIFO 线性表,仅栈顶操作 | 括号匹配、表达式求值、递归模拟 | 
| 数据结构堆 | 完全二叉树,最大 / 小堆,动态取最值 | Top K 问题、中位数查找、优先级调度 | 
| 优先队列(PriorityQueue) | 基于堆实现,按优先级出队 | 任务调度、合并有序数据、事件触发机制 | 
