ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

Java课程设计停车场管理系统:用栈、队列、链表讲透数据结构

Java课程设计停车场管理系统:用栈、队列、链表讲透数据结构 简介面向数据结构课程设计场景的Java停车场管理系统项目完整提供用户界面、车辆存取、候车区调度等功能模块。项目通过自定义链表队列、链栈及链表节点等数据结构实现车位占用与候车区调度逻辑车辆信息类保存车牌号和停车时间数据总控类统一维护停车场及候车区状态整体层次清晰。压缩包共15个文件包含6个可读的Java源文件和9个编译后的class字节码文件大小仅17KB非常适合直接阅读源码、学习界面与数据层的关联或作为课程设计实现参考。目前已有2640人学习尤其适合正在学习Java界面编程、链表队列栈原理或需要完成同类课设的高校学生。借助这一项目读者能理解栈和队列在真实管理系统中的具体应用以及面向对象封装带来的可维护性。1. 停车场管理系统Java课程设计里真正能讲清楚的数据结构题很多人的Java课程设计是从网上拖一份停车场管理系统源码、改个作者名就交的答辩前夜还在背代码。结果老师一问“你这个停车场为什么要用栈队列用在哪”当场卡壳。停车场管理系统这个题目的价值恰恰在这里栈、队列、链表、排序、时间计算这些数据结构知识点被一个真实的业务场景串起来了。它不考验你写多炫的界面而是考验你能不能把“一辆车进来、一辆车出去、临时让路、排队等待”这些动作用正确的数据结构一一对应。这篇文章会把需求拆解、数据结构选型、核心代码、典型踩坑和答辩技巧一次讲透适合正在做Java课程设计的学生也适合想拿真实业务复习数据结构的人。2. 需求先于代码把停车业务翻译成栈、队列和链表2.1 停车场业务拆解先来后到、倒车让路、排队等待课程设计里的停车场通常不是地上画好一个个独立车位而是“狭长通道式”停车场——车只能从一端进入开出去的时候如果目标车前面停着别的车那些车得先倒出来让路等目标车开走后再按原顺序倒回去。这个设定很关键它就是栈的适用场景。把业务拆成三个连续的动作。第一车辆驶入停车场内有空位就停进去没空位就在入口处的候车道排队。第二车辆驶出找到目标车把它后面停的车临时挪到旁边目标车开出去临时挪走的车按原顺序倒回去然后从候车道放一辆车进来补位。第三计费和统计车辆离场时根据停车时长算费用日报表要按停车时长从长到短排序。几乎每本教材里的车辆管理题都是这套规则。学生党最容易犯的错是一上来就写代码写着写着发现“这辆车怎么跑到那辆车前面去了”。所以动手前先把这三条业务规则写在最显眼的地方后面的数据结构选型是跟着它们走的。2.2 为什么是栈、队列和链表而不是直接上HashMap有人会觉得都是存车嘛用一个HashMap存车牌号和车辆信息不是更简单HashMap适合“按键查值”比如“按车牌号查这辆车停了多久”。但停车场管理的核心动作是“先进后出”和“先进先出”这是顺序语义HashMap完全帮不上忙而且HashMap的遍历顺序也不稳定。先来后到、没车位就在外面排着这一条对应队列QueueFIFO先进先出先进候车道的车先获得补位资格。倒车让路这一条对应栈StackLIFO后进先出最后停进来的车最先被挪走正好是栈顶元素。临时挪走的车再倒回去还是栈因为挪出时是“从上往下挪”回去时也得“从下往上放”顺序不能乱。空闲位的管理最朴素的实现是一个计数器capacity - parkingStack.size()如果想做得更真实也可以用链表记录哪些车位是空闲的。这里的数据结构链表不是花架子它可以按车位编号组织空闲位每次分配从头部拿一个释放时挂到尾部配合计数器使用。报表排序再用到List Comparator一个课设下来栈、队列、链表、排序算法全沾上了这正是老师想看到的选型思路。数据结构选型对照表如下业务场景数据结构选择理由临时让路与倒回栈 Stack后进先出正好匹配“后面的车先挪走先倒回”候车道等待补位队列 Queue先进先出先排队的先进入停车场空闲车位分配计数器/链表计数器最简单链表可扩展空闲位顺序管理历史记录排序List Comparator存储对象后再按时长字段排序复习排序算法2.3 收费规则与时间差把业务变量拆成参数别写死在代码里计费模块看着简单其实是课设答辩时老师最爱追问的点。常见收费规则有三种固定费用不论停多久都是X元、阶梯费用首小时X元之后每小时Y元封顶Z元、时段费用白天和夜间单价不同。第一版不要贪多做固定费用或阶梯费用就够用但要把单价、封顶金额这些值从代码里拆出来。推荐做法是在src目录下放一个parking.properties配置文件# 停车场基础配置 parking.capacity10 fee.base5 fee.perHour2 fee.maxPerDay30Java侧用一个配置类去读而不是散落在各处硬编码。这样答辩的时候你可以说“单价和容量都是从配置文件读的改配置不用重新编译。”这一句话能抵过很多代码。时间差计算也有讲究入场时间用LocalDateTime存下来离场时用Duration.between入场时间离场时间取分钟数再向上取整到小时。注意别用System.currentTimeMillis()存两个long再减虽然也能算但可读性差而且面试官看到LocalDateTime会给你加印象分。3. 最小可运行版本车辆进场、出场与报表的三段核心代码3.1 车辆进入用Stack模拟停车场用Queue模拟候车道先定义Vehicle实体类字段越少越好够用就行。这里用Java 8以上的localDateTime记录入场时间。import java.time.LocalDateTime; public class Vehicle { private String plateNo; // 车牌号 private LocalDateTime entryTime; // 入场时间 public Vehicle(String plateNo) { this.plateNo plateNo; this.entryTime LocalDateTime.now(); } public String getPlateNo() { return plateNo; } public LocalDateTime getEntryTime() { return entryTime; } }然后写ParkingLot核心类。停车场用Stack 候车道用Queue 实现类用LinkedList它实现了Queue接口用起来最顺手。import java.util.Queue; import java.util.LinkedList; import java.util.Stack; public class ParkingLot { private int capacity; // 总车位数从配置文件读取 private StackVehicle parkingStack new Stack(); private QueueVehicle waitingQueue new LinkedList(); public ParkingLot(int capacity) { this.capacity capacity; } // 车辆驶入 public void enter(Vehicle vehicle) { if (parkingStack.size() capacity) { parkingStack.push(vehicle); System.out.println(vehicle.getPlateNo() 已进入停车场停在 parkingStack.size() 号位); } else { waitingQueue.offer(vehicle); System.out.println(vehicle.getPlateNo() 停车场已满进入候车道等待); } } }逻辑说明enter方法先判断栈的大小是否小于容量小于说明还有空位直接入栈等于容量说明满了进候车道等待用offer方法入队。这里用parkingStack.size()而不是维护一个独立计数器是为了避免两个变量不同步的麻烦。参数说明capacity是停车场总位数记得从配置类里读出来传给构造函数waitingQueue用LinkedList实例化因为LinkedList同时实现了Queue和List后续要打印排队车辆时遍历也方便。3.2 车辆驶出栈倒车队列补位顺序千万别反驶出是这整个项目里最容易翻车的逻辑核心是“临时栈”的运用。要开走一辆车得先把停在它后面的车栈顶到它上面一层依次弹出暂存在一个临时栈里等目标车弹出后再把临时栈里的车一辆辆弹回停车场栈。import java.util.Stack; public void exit(String plateNo) { StackVehicle tempStack new Stack(); boolean found false; // 第一步从停车栈顶往下找目标车沿途车辆暂存到临时栈 while (!parkingStack.isEmpty()) { Vehicle vehicle parkingStack.pop(); if (vehicle.getPlateNo().equals(plateNo)) { System.out.println(vehicle.getPlateNo() 驶出停车场停车时长 vehicle.getEntryTime() 到 LocalDateTime.now()); found true; break; } tempStack.push(vehicle); } if (!found) { System.out.println(未找到车牌号为 plateNo 的车辆); // 没找到时要把临时栈的车倒回去否则停车场就乱了 while (!tempStack.isEmpty()) { parkingStack.push(tempStack.pop()); } return; } // 第二步临时栈的车按原顺序倒回停车栈 while (!tempStack.isEmpty()) { parkingStack.push(tempStack.pop()); } // 第三步候车道补一辆车进来 if (!waitingQueue.isEmpty()) { Vehicle nextVehicle waitingQueue.poll(); parkingStack.push(nextVehicle); System.out.println(nextVehicle.getPlateNo() 从候车道进入停车场); } }这段代码有三个关键点。第一临时栈的倒回顺序临时栈里最顶上的车是最后被挪走的倒回时它最后被push进停车栈正好又停在最外面和原来的相对顺序一致。第二目标车没找到时临时栈里的车必须倒回去再return漏掉这一步会导致停车场栈里的车凭空消失。第三补位动作放在最后先倒回再补位顺序不能反否则停车栈的栈顶顺序会错乱。答辩时老师喜欢问“为什么不是先补位再倒回”回答就是倒回动作针对的是已经在停车场里的车补位动作针对的是候车道的车业务顺序上必须等现有车辆归位后再放新车进来。3.3 报表与排序按停车时长排序顺便复习比较器课程设计通常要求输出当日停车报表按停车时长从长到短排序。实际项目中车辆离场时可以先把记录放进一个ArrayList报表时统一排序。Java里对对象排序有两种常见方式让Vehicle类实现Comparable或者单独写Comparator。建议单独写Comparator把排序规则和实体类解耦。import java.time.Duration; import java.util.ArrayList; import java.util.Comparator; import java.util.List; public static void printReport(ListVehicle history) { ListVehicle sortedList new ArrayList(history); // 按停车时长从长到短排序时长从入场时间与出场时间差计算 sortedList.sort(Comparator.comparingLong((Vehicle v) - { LocalDateTime exitTime v.getExitTime(); if (exitTime null) { return 0L; } return Duration.between(v.getEntryTime(), exitTime).toMinutes(); }).reversed()); for (Vehicle v : sortedList) { System.out.println(v.getPlateNo() 停车时长 Duration.between(v.getEntryTime(), v.getExitTime()).toMinutes() 分钟); } }参数说明Vehicle类里需要额外加一个getExitTime方法驶出时setExitTime(LocalDateTime.now())报表才能算时长。这样写的好处是Comparator的comparingLong指定了排序字段的提取方式reversed()实现降序要改成升序就去掉reversed()。排序算法的底层仍然是Java的TimSort一种稳定的归并插入的混合排序时间复杂度O(n log n)答辩提到这里可以直接说“我调用了Collections的排序底层是稳定排序不需要自己重写排序算法但我知道它的复杂度。”这一句能把排序算法知识点带出来。4. 避坑课设里最容易让程序崩掉的5个地方4.1 用ArrayList当栈用出场顺序全乱了现象打印停车顺序时发现后进来的车反而被排在列表前面倒车逻辑也乱了明明应该最后进来的车最先让路结果变成最先来的车先被挪走。原因图省事用ArrayList管理场内车辆直接add()把新车加到尾部然后remove()删中间元素。ArrayList是线性表它本身不提供“先取末尾元素”的语义你需要自己维护size并在每次删除时搬移元素过程中很容易漏更新索引。解决直接用java.util.Stack或ArrayDeque。Stack的push/pop就是教科书级栈操作。如果出于性能考虑用ArrayDeque替代Stack也可以它实现了Deque接口push/pop语义一致而且线程不安全但性能更好。课设层面用Stack最直观面试官挑不出问题。4.2 SimpleDateFormat在多线程下报错或时间错乱现象把进场时间用FastDateFormat格式化或者用SimpleDateFormat做格式化时偶尔出现时间显示成“2023-13-45 99:99”这样的乱码甚至抛出NumberFormatException。原因SimpleDateFormat不是线程安全的它的内部calendar是共享可变状态。课程设计里如果你在车辆进出场逻辑里用了多线程比如模拟多辆车同时到并发调用同一个SimpleDateFormat实例就会出问题。解决改用java.time包下的LocalDateTime和DateTimeFormatter它们是不可变且线程安全的。格式化示例如下DateTimeFormatter formatter DateTimeFormatter.ofPattern(yyyy-MM-dd HH:mm:ss); String timeStr vehicle.getEntryTime().format(formatter);如果没有用多线程也出现了时间问题多半是格式化pattern写错了检查是不是把HH24小时制写成了hh12小时制以及分钟是否用了MM月份而不是mm。4.3 车辆没停就驶出时间差计算空指针现象调用exit方法时报NullPointerException定位到Duration.between(entryTime, exitTime)这行。原因要么Vehicle对象在构造时没有给entryTime赋值比如从数据库读出来的时候没映射上要么exitTime在set之前就被拿去计算时长。业务上没停就驶出属于非法操作你的程序没有拦截。解决在exit方法的一开始就校验车辆状态入场时间和出场时间任何一个为null就直接抛业务异常而不是等到计算时才爆空指针。if (vehicle.getEntryTime() null) { throw new IllegalStateException(车辆 plateNo 缺少入场时间无法计费); }另外注意LocalDateTime.now()不会返回null入场时间基本不会为空空指针大概率出在“退场记录”的出场时间字段上该set的时候没set或者set了另一个对象的字段。4.4 遍历集合时删除元素抛ConcurrentModificationException现象在报表模块里想顺手把超时车辆从List里移除用for-each循环遍历循环体里调list.remove(vehicle)运行时报ConcurrentModificationException。原因for-each循环底层是Iterator删除用的是list.remove这会改变集合的modCount和Iterator内部的expectedModCount不一致迭代器检测到并发修改直接拒绝继续迭代。解决改用Iterator显式删除这是最规矩的写法IteratorVehicle iterator history.iterator(); while (iterator.hasNext()) { Vehicle v iterator.next(); if (v.getExitTime() ! null Duration.between(v.getEntryTime(), v.getExitTime()).toMinutes() 24 * 60) { iterator.remove(); } }如果只是为了排序后取前几条数据完全不用删除直接sortedList.subList(0, Math.min(5, sortedList.size()))就够了少动集合结构就少一个坑。4.5 中文车牌号在控制台读进来乱码现象控制台输入“京A12345”回车程序打印出来的车牌变成“???A12345”或“浜A12345”。原因Windows控制台默认GBK编码而IDE的项目文件是UTF-8编码System.in读入的字节流按GBK解成字符到了内部字符串再打印时编码对不上就成乱码。解决分情况处理。如果你用的是IDEA菜单里设置Help - Edit Custom VM Options加上-Dfile.encodingUTF-8然后重启IDE同时确保控制台编码也是UTF-8Run - Edit ConfigurationsVM options里加-Dfile.encodingUTF-8。如果你用的是Windows命令行直接跑可以在代码里指定读入的编码BufferedReader reader new BufferedReader(new InputStreamReader(System.in, StandardCharsets.UTF_8));不过更省心的做法是第一版先别在控制台中文输入上较劲用英文车牌或数字测试把业务逻辑跑通后再处理编码。课设评审更看重逻辑正确而不是中文显示——但如果你主动把编码问题处理好这本身就是个可讲的加分项。5. 从课设到面试让停车场代码变成你讲得清的面试题5.1 用实验报告把复杂度分析写清楚答辩才站得住做课设不只是交代码数据结构实验报告才是评分重头。报告里值得写清楚的三张表各操作的时间复杂度入栈、出栈、队列poll都是O(1)但在停车栈里查找指定车牌是O(n)空间复杂度栈队列共O(n)以及“如果停车场扩容到1000个车位会怎么样”——引导老师往你准备好的方向问。写报告时注意把“为什么出栈是O(1)但找人要O(n)”说透这才是数据结构知识的真正考点代码反而其次。5.2 两个进阶方向文件持久化与并发模拟基础版跑通后想拿高分通常做两件事。第一件是把进出场记录持久化到本地文件用serialization或文本追加都行。第二件是用并发模拟多辆车同时进场比如用线程池创建5个线程同时调用enter和exit这时候你就能真切理解为什么第4章提到SimpleDateFormat线程安全问题了。两个方向都不难挑一个深入即可。做并发模拟时记得给enter和exit加锁synchronized作用于ParkingLot对象否则停车栈会线程不安全。也因为这个原因面试题里常出现“Stack是线程安全的吗”——Stack继承自Vector方法加了synchronized但只在单个方法级别加锁复合操作比如先判断size再push仍然需要外部加锁才能保证原子性。这个知识点能答上来比你多写100行代码都管用。最后分享一个教训。当年我自己做这个课设时堆了300行代码封装了六七个类结果答辩讲得磕磕绊绊因为心里只有代码块没有把业务动作连成故事线。后来养成的习惯是开写之前在纸上画出车辆从进到出的动作链标清楚哪一步对应栈、哪一步对应队列再动手写代码。这个习惯后来也带到了项目问题的排查里。做一个项目课设值钱的不是交上去的代码而是你脑子里能不能随时把业务讲成数据结构。希望帮到你。本文还有配套的精品资源点击获取
返回列表