ARTICLE DETAIL

资讯详情

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

Java课程设计:用栈、队列和哈希表实现停车场管理系统

Java课程设计:用栈、队列和哈希表实现停车场管理系统 简介这份资源是面向Java初学者与数据结构课程设计学习者的停车场管理系统完整项目采用Java语言结合Swing或JavaFX构建图形界面解决车辆存取、车位容量限制与候车区调度等实际问题。压缩包共15个文件包含6个java源码与9个class编译文件整体约17KB涵盖自定义链表队列MyListQueue、栈MyStack、链表节点ListNode、车辆信息类CarInfo以及数据管理类Data等核心模块UI内部类负责按钮事件监听。系统实现存车取车逻辑车位空闲时直接存入满载则转入候车区队列取车时按信息查找处理并配有异常提示机制。目前已有2640人学习下载适合需要参考完整课设方案、理解队列与栈在真实场景中应用、梳理类结构与事件监听写法的读者可据此快速搭建并运行项目深化对Java与数据结构的综合运用能力。1. 停车场管理系统一个把栈、队列和哈希表串起来的 Java 课程设计如果你正在做数据结构课程设计又不想再写一遍学生成绩管理或者图书管理停车场管理系统是个很值得动手的选题。它表面上是个业务系统骨子里却是一道综合数据结构题车辆进场要排队、出场要计费、车位要分配、历史记录要能查。每一个动作背后都对应一个经典结构选错了结构代码能跑但逻辑会拧巴。这份 Java 课程设计资源围绕一个核心场景展开一个多层停车场入口排队、车位分配、出场结算、记录查询。它适合两类人——正在赶课程设计、需要一份能跑通且讲得清原理的参考实现以及想用一个小项目把栈、队列、哈希表、排序算法串起来复习的 Java 学习者。下面我从结构选型讲到代码落地再到实际跑起来会遇到的坑一步步拆开。2. 结构选型为什么入口用队列、车位用哈希、出场用栈2.1 入口排队为什么必须是队列而不是 List车辆到达入口时如果车位已满需要等待。这个等待序列有一个硬性约束先到的车先进入。这就是队列的典型场景FIFO。用 ArrayList 也能实现但每次从头部移除元素都要移动后续所有元素时间复杂度 O(n)。车辆一多这个开销会累积。Java 里实现队列有两种常见选择LinkedList 和 ArrayDeque。LinkedList 实现了 Queue 接口入队出队都是 O(1)但每个节点有额外的指针开销。ArrayDeque 底层是循环数组内存更紧凑大多数场景下性能更好。我一般会选 ArrayDeque除非需要频繁在中间插入。import java.util.ArrayDeque; import java.util.Queue; public class EntranceQueue { // 用 ArrayDeque 实现等待队列FIFO private final QueueCar waitingQueue new ArrayDeque(); private final int maxWaitSize; public EntranceQueue(int maxWaitSize) { this.maxWaitSize maxWaitSize; } // 车辆加入等待队列返回是否成功 public boolean enqueue(Car car) { if (waitingQueue.size() maxWaitSize) { return false; // 等待区已满拒绝进入 } return waitingQueue.offer(car); } // 车位空出时从队首取一辆车 public Car dequeue() { return waitingQueue.poll(); // 队列为空返回 null } public boolean isEmpty() { return waitingQueue.isEmpty(); } public int size() { return waitingQueue.size(); } }这段代码里offer和poll是队列的标准操作前者在容量允许时入队后者从队首取出并移除。maxWaitSize控制等待区上限防止无限排队。实际停车场入口通常只能容纳几辆车等待这个参数一般设 3 到 5。2.2 车位分配用哈希表O(1) 查找空位停车场有若干层每层有固定车位。车辆进场时需要快速找到一个空位。如果用数组遍历每次都要从头扫到尾车位多了效率很低。哈希表可以把「车位编号」映射到「车辆信息」查找、插入、删除都是 O(1)。但这里有个细节哈希表存的是「已占用」的车位找空位时需要反向查找。常见做法是维护一个空闲车位编号的队列或栈或者用一个布尔数组加指针。我倾向于用 HashMap 存占用信息同时用一个 TreeSet 维护空闲编号这样找最小可用编号很快。import java.util.HashMap; import java.util.Map; import java.util.TreeSet; public class ParkingLot { private final int totalSpots; // key: 车位编号, value: 当前停放的车辆 private final MapInteger, Car occupied new HashMap(); // 空闲车位编号自动排序方便分配最小号 private final TreeSetInteger freeSpots new TreeSet(); public ParkingLot(int totalSpots) { this.totalSpots totalSpots; for (int i 1; i totalSpots; i) { freeSpots.add(i); } } // 分配一个空车位返回车位编号无空位返回 -1 public int allocate(Car car) { if (freeSpots.isEmpty()) { return -1; } int spotId freeSpots.pollFirst(); // 取最小空闲编号 occupied.put(spotId, car); return spotId; } // 释放车位 public Car release(int spotId) { Car car occupied.remove(spotId); if (car ! null) { freeSpots.add(spotId); } return car; } public boolean hasFreeSpot() { return !freeSpots.isEmpty(); } }TreeSet的pollFirst保证每次分配的是当前最小空闲编号这样车位使用看起来整齐也方便人工核对。HashMap的remove返回被移除的车辆对象直接用于后续计费。这两个结构配合进场和出场的核心操作都是 O(log n) 和 O(1)足够应对课程设计的规模。2.3 出场计费用栈后进先出模拟通道约束有些停车场设计是单通道车辆只能从最外侧依次驶出。如果里面停的车要出来外面的车必须先挪走。这个场景用栈来模拟最自然最后进入通道的车最先离开。但实际业务里出场计费并不需要严格栈序因为每辆车有独立车位。栈在这里更多用于「临时通道」的模拟或者用于撤销操作。课程设计里常见的做法是出场时把通道内的车依次弹出计费后再压回。这个操作能体现栈的特性也能让代码有东西可讲。import java.util.ArrayDeque; import java.util.Deque; public class ExitChannel { // 用 Deque 当栈push/pop 都在头部 private final DequeCar channel new ArrayDeque(); public void push(Car car) { channel.push(car); } public Car pop() { return channel.pop(); } public boolean isEmpty() { return channel.isEmpty(); } // 让指定车牌的车出场先弹出挡路的车再压回 public Car releaseCar(String plate) { DequeCar temp new ArrayDeque(); Car target null; while (!channel.isEmpty()) { Car c channel.pop(); if (c.getPlate().equals(plate)) { target c; break; } temp.push(c); } // 把临时移出的车压回 while (!temp.isEmpty()) { channel.push(temp.pop()); } return target; } }releaseCar方法演示了栈的典型操作为了取出中间某辆车先把上面的车临时移到另一个栈取完再移回来。这个逻辑在课程设计答辩时很能说明问题面试里也常被问到。3. 从零跑起来环境、建表和核心业务代码3.1 环境准备与项目结构这份课程设计是纯 Java 项目不依赖 Spring 或数据库也能跑。如果你想让数据持久化可以加 MySQL但核心逻辑用内存结构就能演示完整。我建议先用控制台版本跑通再考虑加界面。JDK 版本用 8 或 11 都行代码里没有用高版本特性。IDE 用 IntelliJ IDEA 或者 Eclipse 都可以新建一个 Java 项目包名按com.parking组织。主要类包括类名职责Car车辆实体含车牌、进场时间ParkingLot车位管理分配与释放EntranceQueue入口等待队列ExitChannel出场通道栈BillingService计费逻辑Main控制台入口菜单驱动如果你要加数据库建表语句大概是这样CREATE TABLE parking_record ( id INT PRIMARY KEY AUTO_INCREMENT, plate VARCHAR(20) NOT NULL, spot_id INT NOT NULL, enter_time DATETIME NOT NULL, exit_time DATETIME, fee DECIMAL(10,2), status TINYINT DEFAULT 0 -- 0 在场, 1 已出场 );这个表结构把每次停车记录成一行进场时插入出场时更新。status字段区分在场和已出场查询历史记录时按exit_time排序即可。3.2 计费逻辑按小时还是按次计费规则直接影响代码结构。常见的有两种按次收费每次固定金额按小时收费不足一小时按一小时算。课程设计里按小时更常见因为能体现时间计算。import java.time.Duration; import java.time.LocalDateTime; public class BillingService { private static final double RATE_PER_HOUR 5.0; // 每小时 5 元 private static final double MAX_DAILY 40.0; // 单日上限 public double calculate(Car car, LocalDateTime exitTime) { LocalDateTime enterTime car.getEnterTime(); long minutes Duration.between(enterTime, exitTime).toMinutes(); // 不足一小时按一小时算 long hours (minutes 59) / 60; double fee hours * RATE_PER_HOUR; return Math.min(fee, MAX_DAILY); } }Duration.between计算两个时间点之间的间隔toMinutes得到分钟数。(minutes 59) / 60是向上取整的常用写法避免用 Math.ceil 带来的浮点问题。Math.min限制单日最高收费防止长时间停车费用过高。3.3 主流程串联进场、分配、出场、计费把前面几个类串起来主流程就是车辆到达 → 判断有无空位 → 有空位直接分配无空位进等待队列 → 出场时释放车位 → 检查等待队列 → 计费。public class ParkingSystem { private final ParkingLot lot; private final EntranceQueue queue; private final BillingService billing; public ParkingSystem(int spots, int maxWait) { this.lot new ParkingLot(spots); this.queue new EntranceQueue(maxWait); this.billing new BillingService(); } // 车辆进场 public String enter(Car car) { if (lot.hasFreeSpot()) { int spotId lot.allocate(car); return 分配车位: spotId; } else if (queue.enqueue(car)) { return 车位已满进入等待队列位置: queue.size(); } else { return 等待区已满请稍后再来; } } // 车辆出场 public String exit(String plate, LocalDateTime exitTime) { // 遍历已占用车位找到该车牌 Integer targetSpot null; Car targetCar null; for (Map.EntryInteger, Car entry : lot.getOccupied().entrySet()) { if (entry.getValue().getPlate().equals(plate)) { targetSpot entry.getKey(); targetCar entry.getValue(); break; } } if (targetCar null) { return 未找到该车辆; } double fee billing.calculate(targetCar, exitTime); lot.release(targetSpot); // 有空位后从等待队列取一辆车 if (!queue.isEmpty()) { Car waiting queue.dequeue(); int newSpot lot.allocate(waiting); // 这里可以记录日志或通知 } return 车牌: plate , 费用: fee 元; } }enter方法先判断空位再判断等待队列逻辑清晰。exit方法遍历occupied找到车牌对应的车位这里用entrySet遍历是 O(n)如果车辆多可以再加一个车牌到车位的索引HashMapString, Integer来优化到 O(1)。课程设计规模下 O(n) 足够但答辩时能说出优化方向是加分项。4. 避坑与排查课程设计里最容易翻车的五个点4.1 车牌号作为 HashMap 的 key 却忘了重写 hashCode 和 equals现象用HashMapString, Car存车辆按车牌查不到或者两个相同车牌被当成不同 key。原因如果自定义了 Car 类并用 Car 对象作为 key没有重写hashCode和equalsHashMap 会用对象地址比较永远不相等。解决用 String 类型的车牌作为 keyString 已经正确实现了这两个方法。如果非要用 Car 对象做 key必须重写hashCode和equals且保证车牌相同的两个 Car 返回相同哈希值和 true。4.2 时间计算用了 System.currentTimeMillis 导致精度丢失现象停车 1 小时 1 分钟计费按 1 小时算用户觉得少收了或者停车 59 分钟按 2 小时算用户觉得多收了。原因System.currentTimeMillis()返回毫秒手动除 3600000 取整容易在边界上出错。解决用java.time.LocalDateTime和Duration先算总分钟数再用(minutes 59) / 60向上取整。这样 1 分钟也算 1 小时59 分钟也算 1 小时逻辑一致。4.3 等待队列没有容量上限导致内存溢出现象程序跑一段时间后卡死或者抛 OutOfMemoryError。原因入口等待队列用了LinkedList且没有限制大小车辆不断加入但没人消费。解决给队列设maxWaitSize入队前检查size() maxWaitSize满了就拒绝并提示。实际停车场入口也不可能无限排队。4.4 出场时忘记从等待队列补车现象一辆车出场后车位空着但等待队列里的车一直不动。原因exit方法只做了释放车位和计费没有检查等待队列。解决在release之后加判断if (!queue.isEmpty())取出队首车辆并调用allocate。这个逻辑要放在同一个方法里保证原子性。4.5 控制台输入没有做异常处理导致程序崩溃现象输入车位数量时输入了字母程序直接抛NumberFormatException退出。原因Scanner.nextInt()遇到非数字输入会抛异常没有 try-catch。解决用Scanner.nextLine()读字符串再用Integer.parseInt并捕获异常提示用户重新输入。或者用hasNextInt()先判断。Scanner scanner new Scanner(System.in); int spots 0; while (true) { System.out.print(请输入车位数: ); String line scanner.nextLine(); try { spots Integer.parseInt(line); if (spots 0) break; System.out.println(必须大于 0); } catch (NumberFormatException e) { System.out.println(请输入数字); } }这段代码把输入校验和异常处理放在循环里直到用户输入合法值才继续。课程设计答辩时这种细节能体现工程思维。5. 进阶技巧用排序算法做停车记录统计与验证课程设计如果只做到增删改查深度不够。数据结构课通常要求体现排序和查找算法停车场管理系统里最适合加统计功能按停车时长排序、按费用排序、查找特定时间段内的记录。我一般会在BillingService里加一个方法接收ListParkingRecord用归并排序按费用从高到低排。为什么不用Collections.sort因为课程设计要展示你理解排序原理。归并排序稳定且时间复杂度 O(n log n)适合记录数量大的场景。import java.util.ArrayList; import java.util.List; public class RecordSorter { // 归并排序按费用降序 public static ListParkingRecord sortByFeeDesc(ListParkingRecord records) { if (records.size() 1) { return records; } int mid records.size() / 2; ListParkingRecord left sortByFeeDesc(new ArrayList(records.subList(0, mid))); ListParkingRecord right sortByFeeDesc(new ArrayList(records.subList(mid, records.size()))); return merge(left, right); } private static ListParkingRecord merge(ListParkingRecord left, ListParkingRecord right) { ListParkingRecord result new ArrayList(); int i 0, j 0; while (i left.size() j right.size()) { if (left.get(i).getFee() right.get(j).getFee()) { result.add(left.get(i)); } else { result.add(right.get(j)); } } while (i left.size()) result.add(left.get(i)); while (j right.size()) result.add(right.get(j)); return result; } }sortByFeeDesc递归拆分列表merge按费用降序合并。保证稳定性费用相同的记录保持原有顺序。这个实现可以直接替换Collections.sort在答辩时说明为什么选归并而不是快排——归并稳定快排最坏 O(n²)。验证方法也简单造一批测试数据费用分别是 10、30、20、50、40排序后应该是 50、40、30、20、10。再测边界空列表、单元素、全部相同费用。跑通这三个用例基本能说明排序逻辑没问题。还有一个实用技巧用HashMap统计每个车位的使用次数找出最繁忙的车位。这个功能用哈希表做计数遍历一次记录即可时间复杂度 O(n)。代码很短但能体现你对哈希表的灵活运用。public MapInteger, Integer spotUsage(ListParkingRecord records) { MapInteger, Integer count new HashMap(); for (ParkingRecord r : records) { count.merge(r.getSpotId(), 1, Integer::sum); } return count; }merge方法是 Java 8 引入的key 存在时用Integer::sum累加不存在时设为 1。一行代码完成计数比手动判断简洁得多。从那以后我每次做课程设计都会先把核心数据结构的操作复杂度写在一张纸上再动手写代码。选型错了后面怎么优化都是白费。这个停车场管理系统看起来简单但把队列、栈、哈希表、排序都串了一遍认真做下来对数据结构的理解会上一个台阶。希望帮到你。本文还有配套的精品资源点击获取
返回列表