Java实现列车时刻表数据处理:从时间格式化到业务逻辑完整实战
📅 2026/7/18 17:08:05
👁️ 阅读次数
📝 编程学习
最近在开发铁路调度系统时,经常需要处理列车时刻表数据,其中列车发车时间的计算和格式化是个关键需求。今天我们就来深入探讨如何用Java实现"D3519包头站发车"这类列车时刻信息的处理,从基础的时间格式化到完整的业务逻辑实现。
1. 列车时刻数据处理背景
1.1 业务场景分析
在铁路运输系统中,列车时刻表数据包含丰富的业务信息。以"D3519包头站发车"为例,这不仅仅是一个简单的字符串,而是包含了车次号、站点名称、业务动作等多个维度的信息。在实际业务中,我们需要准确解析这些信息,并关联具体的发车时间、到站时间等时间数据。
1.2 技术挑战
处理列车时刻数据面临几个主要挑战:时间格式的标准化、多时区处理、业务逻辑的复杂性等。特别是当系统需要处理全国范围的列车数据时,时区转换和夏令时等问题都需要仔细考虑。
2. 环境准备与依赖配置
2.1 开发环境要求
- JDK 8或更高版本
- Maven 3.6+
- IDE:IntelliJ IDEA或Eclipse
2.2 Maven依赖配置
在pom.xml中添加必要依赖:
<dependencies> <dependency> <groupId>joda-time</groupId> <artifactId>joda-time</artifactId> <version>2.10.14</version> </dependency> <dependency> <groupId>com.fasterxml.jackson.core</groupId> <artifactId>jackson-databind</artifactId> <version>2.15.2</version> </dependency> </dependencies>3. 核心数据模型设计
3.1 列车时刻实体类设计
首先设计一个完整的列车时刻数据模型:
public class TrainSchedule { private String trainNumber; // 车次号,如D3519 private String stationName; // 站点名称,如包头站 private String actionType; // 动作类型:发车、到站等 private LocalDateTime scheduleTime; // 计划时间 private TimeZone timeZone; // 时区信息 // 构造函数 public TrainSchedule(String trainNumber, String stationName, String actionType, LocalDateTime scheduleTime) { this.trainNumber = trainNumber; this.stationName = stationName; this.actionType = actionType; this.scheduleTime = scheduleTime; this.timeZone = TimeZone.getDefault(); } // getter和setter方法 public String getTrainNumber() { return trainNumber; } public void setTrainNumber(String trainNumber) { this.trainNumber = trainNumber; } public String getStationName() { return stationName; } public void setStationName(String stationName) { this.stationName = stationName; } public String getActionType() { return actionType; } public void setActionType(String actionType) { this.actionType = actionType; } public LocalDateTime getScheduleTime() { return scheduleTime; } public void setScheduleTime(LocalDateTime scheduleTime) { this.scheduleTime = scheduleTime; } public TimeZone getTimeZone() { return timeZone; } public void setTimeZone(TimeZone timeZone) { this.timeZone = timeZone; } }3.2 时间格式化工具类
创建专门的时间格式化工具类来处理各种时间格式:
public class TimeFormatUtil { private static final DateTimeFormatter STANDARD_FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"); private static final DateTimeFormatter SIMPLE_FORMATTER = DateTimeFormatter.ofPattern("HH:mm"); /** * 标准化时间格式输出 */ public static String formatStandardTime(LocalDateTime dateTime) { return dateTime.format(STANDARD_FORMATTER); } /** * 简单时间格式(仅小时分钟) */ public static String formatSimpleTime(LocalDateTime dateTime) { return dateTime.format(SIMPLE_FORMATTER); } /** * 解析字符串时间为LocalDateTime */ public static LocalDateTime parseTime(String timeString) { try { // 尝试解析完整格式 return LocalDateTime.parse(timeString, STANDARD_FORMATTER); } catch (DateTimeParseException e) { // 解析简单格式(需要结合当前日期) LocalTime localTime = LocalTime.parse(timeString, SIMPLE_FORMATTER); return LocalDateTime.of(LocalDate.now(), localTime); } } }4. 业务逻辑实现
4.1 列车时刻解析器
实现一个强大的解析器来处理各种格式的列车时刻信息:
public class TrainScheduleParser { /** * 解析列车时刻信息字符串 * 支持格式:"D3519包头站发车 08:30" */ public static TrainSchedule parseScheduleInfo(String scheduleInfo) { if (scheduleInfo == null || scheduleInfo.trim().isEmpty()) { throw new IllegalArgumentException("时刻信息不能为空"); } // 使用正则表达式解析字符串 Pattern pattern = Pattern.compile("([A-Z]\\d+)(.+?)(发车|到站)\\s*(\\d{1,2}:\\d{2})"); Matcher matcher = pattern.matcher(scheduleInfo); if (matcher.find()) { String trainNumber = matcher.group(1); // 车次号 String stationName = matcher.group(2).trim(); // 站点名称 String actionType = matcher.group(3); // 动作类型 String timeString = matcher.group(4); // 时间字符串 LocalDateTime scheduleTime = TimeFormatUtil.parseTime(timeString); return new TrainSchedule(trainNumber, stationName, actionType, scheduleTime); } else { throw new IllegalArgumentException("无法解析的列车时刻格式: " + scheduleInfo); } } /** * 生成标准化的显示信息 */ public static String generateDisplayInfo(TrainSchedule schedule) { String timeStr = TimeFormatUtil.formatSimpleTime(schedule.getScheduleTime()); return String.format("%s%s%s %s", schedule.getTrainNumber(), schedule.getStationName(), schedule.getActionType(), timeStr); } }4.2 时刻表管理服务
实现一个完整的时刻表管理服务:
@Service public class TrainScheduleService { private List<TrainSchedule> schedules = new ArrayList<>(); /** * 添加列车时刻 */ public void addSchedule(TrainSchedule schedule) { // 验证数据完整性 validateSchedule(schedule); schedules.add(schedule); // 按时间排序 schedules.sort(Comparator.comparing(TrainSchedule::getScheduleTime)); } /** * 根据车次号查询时刻信息 */ public List<TrainSchedule> getSchedulesByTrainNumber(String trainNumber) { return schedules.stream() .filter(schedule -> schedule.getTrainNumber().equals(trainNumber)) .collect(Collectors.toList()); } /** * 查询指定时间范围内的时刻信息 */ public List<TrainSchedule> getSchedulesInTimeRange(LocalDateTime start, LocalDateTime end) { return schedules.stream() .filter(schedule -> !schedule.getScheduleTime().isBefore(start) && !schedule.getScheduleTime().isAfter(end)) .collect(Collectors.toList()); } /** * 数据验证 */ private void validateSchedule(TrainSchedule schedule) { if (schedule.getTrainNumber() == null || schedule.getTrainNumber().isEmpty()) { throw new IllegalArgumentException("车次号不能为空"); } if (schedule.getStationName() == null || schedule.getStationName().isEmpty()) { throw new IllegalArgumentException("站点名称不能为空"); } if (schedule.getScheduleTime() == null) { throw new IllegalArgumentException("计划时间不能为空"); } } }5. 完整实战示例
5.1 创建测试数据
编写一个完整的演示程序:
public class TrainScheduleDemo { public static void main(String[] args) { // 创建时刻表服务 TrainScheduleService scheduleService = new TrainScheduleService(); // 解析并添加示例数据 String[] scheduleInfos = { "D3519包头站发车 08:30", "D3519呼和浩特站到站 10:15", "D3520北京站发车 14:20", "D3520天津站到站 16:05" }; for (String info : scheduleInfos) { try { TrainSchedule schedule = TrainScheduleParser.parseScheduleInfo(info); scheduleService.addSchedule(schedule); System.out.println("成功添加: " + TrainScheduleParser.generateDisplayInfo(schedule)); } catch (Exception e) { System.err.println("解析失败: " + info + ", 错误: " + e.getMessage()); } } // 查询D3519车次的全部时刻 System.out.println("\n=== D3519车次时刻表 ==="); List<TrainSchedule> d3519Schedules = scheduleService.getSchedulesByTrainNumber("D3519"); for (TrainSchedule schedule : d3519Schedules) { System.out.println(TrainScheduleParser.generateDisplayInfo(schedule)); } // 查询时间范围内的时刻 System.out.println("\n=== 上午时刻表(08:00-12:00)==="); LocalDateTime morningStart = LocalDateTime.of(LocalDate.now(), LocalTime.of(8, 0)); LocalDateTime morningEnd = LocalDateTime.of(LocalDate.now(), LocalTime.of(12, 0)); List<TrainSchedule> morningSchedules = scheduleService.getSchedulesInTimeRange(morningStart, morningEnd); for (TrainSchedule schedule : morningSchedules) { System.out.println(TrainScheduleParser.generateDisplayInfo(schedule)); } } }5.2 运行结果示例
运行上述程序,预期输出如下:
成功添加: D3519包头站发车 08:30 成功添加: D3519呼和浩特站到站 10:15 成功添加: D3520北京站发车 14:20 成功添加: D3520天津站到站 16:05 === D3519车次时刻表 === D3519包头站发车 08:30 D3519呼和浩特站到站 10:15 === 上午时刻表(08:00-12:00)=== D3519包头站发车 08:30 D3519呼和浩特站到站 10:156. 高级功能实现
6.1 时区处理增强
在实际铁路系统中,需要处理跨时区的时刻转换:
public class TimeZoneConverter { /** * 转换时区 */ public static LocalDateTime convertTimeZone(LocalDateTime originalTime, TimeZone fromZone, TimeZone toZone) { ZonedDateTime fromZoned = ZonedDateTime.of(originalTime, ZoneId.of(fromZone.getID())); ZonedDateTime toZoned = fromZoned.withZoneSameInstant(ZoneId.of(toZone.getID())); return toZoned.toLocalDateTime(); } /** * 获取站点所在时区 */ public static TimeZone getStationTimeZone(String stationName) { // 实际项目中这里可以从数据库或配置文件中获取 Map<String, String> stationTimeZones = new HashMap<>(); stationTimeZones.put("包头站", "Asia/Shanghai"); stationTimeZones.put("呼和浩特站", "Asia/Shanghai"); stationTimeZones.put("北京站", "Asia/Shanghai"); String timeZoneId = stationTimeZones.getOrDefault(stationName, "Asia/Shanghai"); return TimeZone.getTimeZone(timeZoneId); } }6.2 JSON序列化支持
为了方便API接口使用,添加JSON序列化支持:
public class TrainScheduleSerializer { private static final ObjectMapper objectMapper = new ObjectMapper(); static { // 注册JavaTime模块以支持LocalDateTime序列化 objectMapper.registerModule(new JavaTimeModule()); objectMapper.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS); } /** * 序列化为JSON字符串 */ public static String toJson(TrainSchedule schedule) throws JsonProcessingException { return objectMapper.writeValueAsString(schedule); } /** * 从JSON字符串反序列化 */ public static TrainSchedule fromJson(String json) throws JsonProcessingException { return objectMapper.readValue(json, TrainSchedule.class); } }7. 异常处理与数据验证
7.1 自定义异常类
定义业务相关的异常类:
public class TrainScheduleException extends RuntimeException { private final ErrorCode errorCode; public TrainScheduleException(ErrorCode errorCode, String message) { super(message); this.errorCode = errorCode; } public TrainScheduleException(ErrorCode errorCode, String message, Throwable cause) { super(message, cause); this.errorCode = errorCode; } public ErrorCode getErrorCode() { return errorCode; } } public enum ErrorCode { INVALID_TRAIN_NUMBER("无效的车次号格式"), INVALID_TIME_FORMAT("无效的时间格式"), STATION_NOT_FOUND("站点不存在"), DUPLICATE_SCHEDULE("重复的时刻记录"); private final String description; ErrorCode(String description) { this.description = description; } public String getDescription() { return description; } }7.2 数据验证增强
完善数据验证逻辑:
public class ScheduleValidator { /** * 验证车次号格式 */ public static boolean isValidTrainNumber(String trainNumber) { if (trainNumber == null) return false; // 车次号格式:字母开头,后跟数字,如D3519、G1234等 return trainNumber.matches("[A-Z]\\d+"); } /** * 验证时间合理性 */ public static boolean isValidScheduleTime(LocalDateTime time) { if (time == null) return false; // 不能是过去的时间(允许当天的未来时间) LocalDateTime now = LocalDateTime.now(); return !time.isBefore(now.with(LocalTime.MIN)); // 允许当天的时间 } /** * 完整验证 */ public static void validateComplete(TrainSchedule schedule) { if (!isValidTrainNumber(schedule.getTrainNumber())) { throw new TrainScheduleException(ErrorCode.INVALID_TRAIN_NUMBER, "无效车次号: " + schedule.getTrainNumber()); } if (!isValidScheduleTime(schedule.getScheduleTime())) { throw new TrainScheduleException(ErrorCode.INVALID_TIME_FORMAT, "无效时间: " + schedule.getScheduleTime()); } } }8. 单元测试编写
8.1 解析器测试
编写完整的单元测试确保代码质量:
public class TrainScheduleParserTest { @Test public void testParseValidSchedule() { String scheduleInfo = "D3519包头站发车 08:30"; TrainSchedule schedule = TrainScheduleParser.parseScheduleInfo(scheduleInfo); assertEquals("D3519", schedule.getTrainNumber()); assertEquals("包头站", schedule.getStationName()); assertEquals("发车", schedule.getActionType()); assertEquals(LocalTime.of(8, 30), schedule.getScheduleTime().toLocalTime()); } @Test public void testParseInvalidFormat() { String invalidInfo = "无效的格式"; assertThrows(IllegalArgumentException.class, () -> { TrainScheduleParser.parseScheduleInfo(invalidInfo); }); } @Test public void testGenerateDisplayInfo() { TrainSchedule schedule = new TrainSchedule("D3519", "包头站", "发车", LocalDateTime.of(2024, 1, 1, 8, 30)); String displayInfo = TrainScheduleParser.generateDisplayInfo(schedule); assertEquals("D3519包头站发车 08:30", displayInfo); } }8.2 服务层测试
测试业务逻辑的正确性:
public class TrainScheduleServiceTest { private TrainScheduleService service; @BeforeEach public void setUp() { service = new TrainScheduleService(); } @Test public void testAddAndRetrieveSchedules() { TrainSchedule schedule1 = new TrainSchedule("D3519", "包头站", "发车", LocalDateTime.of(2024, 1, 1, 8, 30)); TrainSchedule schedule2 = new TrainSchedule("D3519", "呼和浩特站", "到站", LocalDateTime.of(2024, 1, 1, 10, 15)); service.addSchedule(schedule1); service.addSchedule(schedule2); List<TrainSchedule> schedules = service.getSchedulesByTrainNumber("D3519"); assertEquals(2, schedules.size()); // 验证按时间排序 assertTrue(schedules.get(0).getScheduleTime().isBefore(schedules.get(1).getScheduleTime())); } }9. 性能优化建议
9.1 数据存储优化
对于大规模的时刻表数据,建议使用数据库存储并建立合适的索引:
CREATE TABLE train_schedule ( id BIGINT AUTO_INCREMENT PRIMARY KEY, train_number VARCHAR(20) NOT NULL, station_name VARCHAR(100) NOT NULL, action_type VARCHAR(10) NOT NULL, schedule_time DATETIME NOT NULL, time_zone VARCHAR(50) NOT NULL, INDEX idx_train_number (train_number), INDEX idx_schedule_time (schedule_time), INDEX idx_station_time (station_name, schedule_time) );9.2 缓存策略
对于频繁查询的数据,可以引入缓存机制:
@Service public class CachedTrainScheduleService { private final TrainScheduleService delegate; private final Cache<String, List<TrainSchedule>> cache; public CachedTrainScheduleService(TrainScheduleService delegate) { this.delegate = delegate; this.cache = Caffeine.newBuilder() .expireAfterWrite(10, TimeUnit.MINUTES) .maximumSize(1000) .build(); } public List<TrainSchedule> getSchedulesByTrainNumber(String trainNumber) { return cache.get(trainNumber, key -> delegate.getSchedulesByTrainNumber(key)); } }10. 生产环境注意事项
10.1 配置管理
将易变的配置外化:
# application.properties train.schedule.timezone.default=Asia/Shanghai train.schedule.cache.enabled=true train.schedule.cache.duration=10m10.2 日志记录
添加详细的日志记录以便排查问题:
@Slf4j public class TrainScheduleService { public void addSchedule(TrainSchedule schedule) { try { validateSchedule(schedule); schedules.add(schedule); schedules.sort(Comparator.comparing(TrainSchedule::getScheduleTime)); log.info("成功添加列车时刻: {}", TrainScheduleParser.generateDisplayInfo(schedule)); } catch (Exception e) { log.error("添加列车时刻失败: {}", schedule, e); throw e; } } }10.3 监控指标
添加业务监控指标:
@Component public class ScheduleMetrics { private final MeterRegistry meterRegistry; private final Counter addScheduleCounter; private final Counter parseErrorCounter; public ScheduleMetrics(MeterRegistry meterRegistry) { this.meterRegistry = meterRegistry; this.addScheduleCounter = Counter.builder("train.schedule.add") .description("添加列车时刻计数") .register(meterRegistry); this.parseErrorCounter = Counter.builder("train.schedule.parse.error") .description("解析错误计数") .register(meterRegistry); } }通过本文的完整实现,我们建立了一个健壮的列车时刻处理系统,能够准确解析"D3519包头站发车"这类业务信息,并提供完整的增删改查功能。在实际项目中,可以根据具体需求进一步扩展功能,如实时数据同步、多语言支持、移动端适配等。
编程学习
技术分享
实战经验