ARTICLE DETAIL

建站实战干货

来自一线的建站与推广经验沉淀,每一条都经过真实交付验证。

Java课程设计停车场管理系统:栈与队列核心实现与避坑指南

2026/10/2 1:02:15 拓冰建站 浏览量
Java课程设计停车场管理系统:栈与队列核心实现与避坑指南 简介一份基于Java的数据结构课程设计源码压缩包实现停车场管理系统。系统带有图形界面覆盖车辆存取与满位候车流程适合高校学生作为期末课程设计参考或数据结构实践项目。压缩包共15个文件含Java源码与已编译class文件大小约17KB。源码围绕自定义数据结构展开MyListQueue链表队列维护车位MyStack栈处理候车逻辑ListNode作为链表节点CarInfo承载车牌号与停车时间Data类负责全局数据管理UI模块采用Swing组件搭建存车、取车入口并使用匿名内部类实现事件监听。已有2640人学习下载。通过该项目可直观掌握队列先进先出与栈后进先出的实际应用同时理解Java GUI搭建和异常处理。代码结构清晰、体量轻量适合源码阅读与二次扩展便于在此基础上增加计费、车位查询等功能。1. 数据结构课程设计选停车场管理系统是性价比最高的题目如果你正在为Java课程设计选题发愁或者已经选了停车场管理系统但不知从哪下手这篇实战拆解就是给你看的。停车场管理系统的核心价值在于它用到了栈和队列这两种最基础的数据结构实现起来不复杂但能完整覆盖“选型、建模、编码、排错、验证”的全流程是数据结构课设里性价比最高的题目。更实在的是这套代码逻辑能直接平移到图书馆座位管理、机房上机管理、洗车店工位调度等一堆场景不是只能交作业的一次性代码。本文会从数据结构选型、模块拆分、核心操作实现到高频踩坑点和验证技巧带你完整走一遍这个Java版停车场管理系统让你拿到资源后能跑能改能答辩。2. 核心建模为什么用栈模拟停车场用队列模拟便道刚拿到题目很多人的第一反应是直接用ArrayList或数组硬写逻辑也能跑通但答辨的时候说不清楚为什么选这个结构老师一问就露怯。“停车场管理系统”这个题目的核心考点其实就一句话你要理解栈和队列各自最适合模拟什么场景。下面把这两个结构怎么映射到真实停车场讲清楚然后再给你能直接抄的代码骨架。2.1 数据结构选型栈的“后进先出”对应停车场队列的“先进先出”对应便道真实停车场的物理规则是只有一个出入口车开进去之后排在后面的车必须给前面的车让路。这正是栈的“后进先出”LIFO语义——后进来的车停在栈顶距离出口最近先出去先进来的车压在栈底必须等上面的车都开走才能动。所以停车场内部用一个栈模拟是数据结构教材上的经典解法。便道则不同。停车场满了之后后来的车在门口排队等待先来的车优先进入停车场。这是标准的“先进先出”FIFO语义用队列模拟再合适不过。注意这里有个容易搞混的地方很多同学把“便道上的车优先进入”误写成“后到的先进”一旦写反整个系统逻辑就错了。所以在动手写代码前先在草稿纸上把两个结构和业务规则对应清楚停车场栈只负责存已停入的车辆等待队列只负责存排队车辆一辆车绝不会同时出现在两个结构里。下面给出的代码骨架会用java /**车辆实体类 */ public class Car { private String licensePlate; // 车牌号 private LocalDateTime enterTime; // 进场时间方便后续扩展计费public Car(String licensePlate) { this.licensePlate licensePlate; }// getter / setter 略 }这里有两个设计决定要说明一下。一是为什么用 Deque 而不是 StackJava 的 Stack 继承自 Vector所有方法带 synchronized 锁性能差且属于遗留类官方文档明确建议用 Deque 替代。二是为什么暂时不加计费、会员等字段课程设计的核心是数据结构演示先保持模型简洁后续扩展的代码我放在最后一章。你在答辩时如果能主动说出“这里用 ArrayDeque 替代 Stack 是参考了 Java 官方文档建议”是加分项。 接着给出业务核心进出场调度。 java /** * 停车场管理服务核心进出场逻辑 */ public class ParkingService { private DequeCar parkingStack; // 停车场内部栈结构 private QueueCar waitingQueue; // 便道等待队列 private int capacity; // 停车场容量 public ParkingService(int capacity) { this.capacity capacity; this.parkingStack new ArrayDeque(); this.waitingQueue new LinkedList(); } /** * 车辆进场 * 停车栈未满则直接入栈否则进入等待队列 */ public void enter(Car car) { if (parkingStack.size() capacity) { parkingStack.push(car); System.out.println(车辆 car.getLicensePlate() 进入停车场当前车位 parkingStack.size()); } else { boolean offered waitingQueue.offer(car); if (offered) { System.out.println(停车场已满车辆 car.getLicensePlate() 进入等待队列位置 waitingQueue.size()); } else { System.out.println(等待队列已满车辆 car.getLicensePlate() 请前往其他停车场); } } }这段代码的核心是push和offer两个动作的区分push是入栈、进入停车场offer是入队、进入便道。一辆车只会进入其中一个容器。capacity 是停车场总车位数你可以在 main 方法里初始化为 5 或 10 来测试。waitingQueue 理论上可以无限排队但在真实场景中便道容量是有限的所以后面避坑章会教你怎么加一个MAX_WAITING上限。再看出场逻辑这是整个系统最复杂的地方也是最容易写错的点。/** * 车辆出场找到指定车辆挪走上层车辆补位等待车辆 */ public void leave(String licensePlate) { if (parkingStack.isEmpty()) { System.out.println(停车场为空无车辆出场); return; } // 用临时栈暂存被挪开的车 DequeCar tempStack new ArrayDeque(); Car targetCar null; // 从栈顶开始找目标车辆 while (!parkingStack.isEmpty()) { Car current parkingStack.pop(); if (current.getLicensePlate().equals(licensePlate)) { targetCar current; // 找到目标跳出 break; } tempStack.push(current); // 不是目标暂存到临时栈 } // 把临时栈的车放回原栈 while (!tempStack.isEmpty()) { parkingStack.push(tempStack.pop()); } if (targetCar null) { System.out.println(未找到车辆: licensePlate); return; } // 如果等待队列有车自动补位进入停车场 if (!waitingQueue.isEmpty()) { Car nextCar waitingQueue.poll(); parkingStack.push(nextCar); System.out.println(等待车辆 nextCar.getLicensePlate() 补位进入停车场); } System.out.println(车辆 licensePlate 已出场); } }这段代码的关键在于临时栈tempStack目标车辆上面的车要先全部挪走找到目标后再把挪走的车按原顺序放回。这里最常见的错误是把tempStack.push(current)写成parkingStack.push(current)导致死循环或车辆丢失。还有一个容易被忽略的细节补位策略是“只要等待队列非空就立刻补一辆进停车场”至于补位的车停在哪它自然会被push到栈顶也就是离出口最近的位置——这符合真实停车场“后来的车停门口”的习惯。到这里核心的数据结构和业务逻辑已经清楚了。但一个能交差的课程设计还需要完整的类设计和交互入口下一章展开。3. 模块化设计用 Service 层把栈、队列和交互解耦很多课程设计翻车不是因为算法不会而是把全部代码堆在 main 方法里几百行挤在一起答辩时老师追问某个方法的具体逻辑你翻代码都要翻半天。停车场管理系统虽然数据量小但代码结构如果混乱后续改需求会非常痛苦。这一章给出标准的分层结构以及一个核心设计取舍。3.1 类结构设计Car、StackParking、WaitingQueue、ParkingService 与 Main我建议把代码拆成五个部分每个类只干一件事。这个分层方式不复杂但能让代码结构清晰到一眼看懂数据流向。src/ ├── model/Car.java // 车辆实体只有车牌号和时间 ├── service/StackParking.java // 停车场栈封装栈操作 ├── service/WaitingQueue.java // 等待队列封装队列操作 ├── service/ParkingService.java // 业务编排进出场、补位 └── Main.java // 入口Scanner 交互为什么不把ParkingService里的逻辑直接写进 main因为 main 只负责读取输入和展示结果业务规则应该集中在ParkingService。这样如果以后要把命令行改成图形界面只需要改Main.java业务代码完全不动。这一点在做课程设计答辩时非常加分。3.2 基础环境的搭建创建 Maven 项目与引入 JUnit 依赖如果你准备用 IDEIDEA 或 Eclipse新建项目建议直接创建 Maven 项目因为后面做单元测试要引入 JUnit 依赖。虽然这是一个命令行小项目但用 Maven 管理依赖是 Java 工程化习惯提前养成没坏处。在pom.xml里加入 JUnitdependencies dependency groupIdorg.junit.jupiter/groupId artifactIdjunit-jupiter/artifactId version5.10.2/version scopetest/scope /dependency /dependencies如果你不用 Maven也可以直接在 IDE 里把 JUnit 的 jar 包添加到 classpath。但用 Maven 的好处是下载源码和依赖都在配置文件里声明换一台电脑也能快速还原环境。3.3 界面层设计用 Scanner 构建命令行交互菜单最后是Main.java。这块不需要图形界面用Scanner读键盘输入即可。菜单设计成数字选择简单直接import java.util.Scanner; public class Main { public static void main(String[] args) { ParkingService service new ParkingService(5); Scanner scanner new Scanner(System.in); while (true) { System.out.println( 停车场管理系统 ); System.out.println(1. 车辆进场); System.out.println(2. 车辆出场); System.out.println(3. 查看停车场状态); System.out.println(4. 退出系统); System.out.print(请选择操作: ); String choice scanner.nextLine(); switch (choice) { case 1: System.out.print(请输入车牌号: ); String plate scanner.nextLine(); service.enter(new Car(plate)); break; case 2: System.out.print(请输入要出场的车牌号: ); String outPlate scanner.nextLine(); service.leave(outPlate); break; case 3: service.printStatus(); break; case 4: System.out.println(感谢使用停车场管理系统); return; default: System.out.println(无效指令请重新输入); } } } }这里有个小经验读取菜单选择时不要用nextInt()后用nextLine()读车牌号因为nextInt()会留下一个换行符在缓冲区导致nextLine()直接读到空字符串。统一全部用nextLine()读字符串再通过 switch 分支处理能避开这个非常经典的 Scanner 坑。另外注意choice和plate都是字符串所以 switch 的 case 必须写成case 1而不是case 1。如果不小心写错编译会直接报类型不匹配错误一眼就看得出来。到这里整个项目的骨架已经完整可以跑起来了。但代码能跑只是第一步课程设计答辩问的往往是边界情况和异常分支。下面进入避坑指南。4. 避坑指南停车场管理系统最常见的五个翻车现场这个题目我前前后后帮人调试过不下十次很多问题不是因为不懂数据结构而是栽在 Java 语言的细节上。下面五条是按出现频率排的你写代码时对照自查能省下大量 debug 时间。4.1 车辆凭空丢失现象停车场满了以后调用enter()输入车牌号后没有任何反应既没提示停车成功也没提示排队成功车辆就从系统里“消失”了。原因enter()方法里没有写 else 分支。比如只判断了栈未满的情况栈满时直接return或者什么也不做导致车辆既没进栈也没进队列。解决enter()必须覆盖全部分支栈未满入栈、栈满则入队、队列满则拒绝。每个分支必须有明确的输出提示。建议通过printStatus()打印当前栈和队列里的所有车牌号进入一次就验证一次。4.2 出场时把临时栈的车辆弄丢现象车辆出场后原来停着的车少了一辆或多辆或者出场后栈顺序与入场顺序不符。原因临时栈tempStack在查找目标车辆时把挪走的车pop出来后没有在查找结束后重新push回去。有的同学甚至把临时栈直接clear()了那被挪走的车就彻底丢了。解决把“从临时栈放回原栈”写成一个独立操作放在leave()方法的finally块里或确保所有return分支前都执行。每次出场后打印栈内全部车牌号确认车辆数量和顺序恢复原状。4.3nextInt()和nextLine()混用导致输入被跳过现象菜单选择“1”输入回车后系统没有等用户输入车牌号直接提示“请输入车牌号”然后又立刻输出“停车场已满”或“入场成功”看起来输入没生效。原因nextInt()只读取数字不消费数字后面的换行符。紧接着调用nextLine()读到的就是一个空字符串车牌号自然是空的。解决主菜单和所有输入都统一用nextLine()然后做字符串匹配或者在nextInt()之后手动加一个scanner.nextLine()消费掉残留换行符。这个坑我在第三版的代码里已经帮你处理好了。4.4ArrayDeque不能存null现象使用ArrayDeque作为停车场栈时如果enter()传入一个null车辆对象会抛出NullPointerException而且错误信息指向push方法。原因ArrayDeque不允许存储null元素这是它的内部实现决定的。LinkedList则允许。我们在入口处已经做了非空校验但如果你改代码时绕过了Main直接调service.enter(null)就会触发这个异常。解决在enter()方法开头加一行判断如果传入的car为null直接返回并提示参数错误。4.5 等待队列无上限导致程序假死现象模拟连续 1000 辆车进场时程序变卡打印排队信息时控制台刷屏严重甚至看起来像死循环。原因等待队列用LinkedList且没有设置上限根本停不下来。真实场景中便道长度有限不会无限排队。解决给WaitingQueue增加一个MAX_WAITING常量比如 10队列满时新来的车直接拒绝进入并提示“请前往其他停车场”。这样系统有明确的“满载”概念管理逻辑也更完整。5. 验证与进阶从能跑通到能讲清楚课程设计做到“能跑”只是及格线答辩时能说清楚“为什么这么设计”才是高分关键。这一章先讲验证方法再给两个进阶方向让你的代码和讲解都比同组同学高一截。5.1 用 JUnit 验证栈和队列的边界行为手工测试只能验证“按顺序进出”这种理想情况但答辩老师最喜欢问的是边界停车场满了怎么办出场时队列自动补位了吗等待队列满了怎么办这些用 JUnit 写自动化测试最稳妥。import org.junit.jupiter.api.Test; import static org.junit.jupiter.api.Assertions.*; public class ParkingServiceTest { Test public void testEnterAndLeave() { ParkingService service new ParkingService(2); service.enter(new Car(A001)); service.enter(new Car(A002)); // 停车场满了第三辆应该进入等待队列 service.enter(new Car(A003)); // 出场一辆等待队列的车应该自动补位 service.leave(A001); assertEquals(2, service.getParkingStack().size(), 停车位应该还有2辆车); assertEquals(0, service.getWaitingQueue().size(), 等待队列应该为空); assertEquals(A003, service.getParkingStack().peek().getLicensePlate()); } Test public void testWaitingQueueFull() { ParkingService service new ParkingService(1); // 第一个进停车场2-11个进等待队列上限10 service.enter(new Car(A001)); for (int i 2; i 11; i) { service.enter(new Car(A00 i)); } // 第12个应该被拒绝 service.enter(new Car(A012)); assertEquals(10, service.getWaitingQueue().size(), 等待队列应该只有10辆); } }单元测试的目标不是追求覆盖率而是把核心业务规则固定下来。上面两个测试分别验证了“自动补位”和“队列上限”这两个最容易出逻辑错误的分支。你可以把ParkingService里的getParkingStack()和getWaitingQueue()两个 getter 补上测试就能直接跑。5.2 进阶方案一添加停车时长计费逻辑栈和队列管的是车辆位置而计费逻辑需要记录每辆车的进场时间。给Car加一个LocalDateTime enterTime字段进场时赋值LocalDateTime.now()。出场时用Duration.between(enterTime, now)计算分钟数再乘上费率。这个设计已经不涉及数据结构的改动但能展示你对时间处理的理解。public double calculateFee(Car car, double ratePerMinute) { long minutes Duration.between(car.getEnterTime(), LocalDateTime.now()).toMinutes(); return minutes * ratePerMinute; }要注意的是不足一分钟按一分钟计算还是直接舍掉需要明确规则。真实停车场一般向上取整所以这里建议minutes 1再做乘法。5.3 进阶方案二用 HashMap 做车牌与车位的索引目前的leave()方法查找车辆是遍历栈容量小的时候没问题但如果车位扩容到 500每次出场都遍历一次栈就不是最优解了。可以额外维护一个HashMapString, Carkey 是车牌号value 是对应的 Car 对象进出场时同步更新。这样出场时直接 O(1) 查找不需要遍历栈。HashMap 解决的是“通过车牌找到车辆”的效率问题 栈和队列解决的还是“车辆位置管理”的结构问题但这时候你要想清楚一个问题HashMap只是索引车辆的位置仍然由栈和队列决定。如果车辆补位之后HashMap没有同步更新就会出现索引不一致的 bug。所以引入HashMap的同时一定要保证所有对栈和队列的修改操作都同步维护索引。6. 查漏与优化打印中间状态是排查玄学 bug 的万能钥匙写了这么多年代码我发现一个最朴素的排查技巧凡是用栈、队列这类容器存数据操作完一定要打印出来看看。不要依赖调试器断点直接打印容器内全部元素肉眼扫一眼就能发现问题。我建议给ParkingService加一个printStatus()把当前停车场栈和等待队列的全部车牌号都打出来每次进出场后都看一眼。 当前停车场状态 停车场内车辆从栈顶到栈底: [A003, A002] 等待队列车辆从队头到队尾: [A004, A005] 这个习惯在调试“出场后车辆丢失”“补位顺序错误”这类问题时极其有效。尤其是涉及临时栈的时候你只需要在“放回原栈”前后各打印一次就能立刻定位是哪个环节丢了数据。每次写完容器相关的代码都会强制在关键操作后打印一次状态。这个习惯后来帮我解决过很多比停车场管理系统复杂得多的项目问题而且原理都一样——容器代码的黑匣子最可怕打印出来才能让问题自己现身。课设阶段养成的这个习惯比代码本身更值钱。这份资源里的代码已经把这些打印和校验都写好了拿到后运行一次看效果再对照本文的避坑点逐条检查比从零开始写省太多时间。希望帮到你。本文还有配套的精品资源点击获取