Java实现列车时刻表数据处理:从时间格式化到业务逻辑完整实战
最近在开发铁路调度系统时经常需要处理列车时刻表数据其中列车发车时间的计算和格式化是个关键需求。今天我们就来深入探讨如何用Java实现D3519包头站发车这类列车时刻信息的处理从基础的时间格式化到完整的业务逻辑实现。1. 列车时刻数据处理背景1.1 业务场景分析在铁路运输系统中列车时刻表数据包含丰富的业务信息。以D3519包头站发车为例这不仅仅是一个简单的字符串而是包含了车次号、站点名称、业务动作等多个维度的信息。在实际业务中我们需要准确解析这些信息并关联具体的发车时间、到站时间等时间数据。1.2 技术挑战处理列车时刻数据面临几个主要挑战时间格式的标准化、多时区处理、业务逻辑的复杂性等。特别是当系统需要处理全国范围的列车数据时时区转换和夏令时等问题都需要仔细考虑。2. 环境准备与依赖配置2.1 开发环境要求JDK 8或更高版本Maven 3.6IDEIntelliJ IDEA或Eclipse2.2 Maven依赖配置在pom.xml中添加必要依赖dependencies dependency groupIdjoda-time/groupId artifactIdjoda-time/artifactId version2.10.14/version /dependency dependency groupIdcom.fasterxml.jackson.core/groupId artifactIdjackson-databind/artifactId version2.15.2/version /dependency /dependencies3. 核心数据模型设计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 ListTrainSchedule schedules new ArrayList(); /** * 添加列车时刻 */ public void addSchedule(TrainSchedule schedule) { // 验证数据完整性 validateSchedule(schedule); schedules.add(schedule); // 按时间排序 schedules.sort(Comparator.comparing(TrainSchedule::getScheduleTime)); } /** * 根据车次号查询时刻信息 */ public ListTrainSchedule getSchedulesByTrainNumber(String trainNumber) { return schedules.stream() .filter(schedule - schedule.getTrainNumber().equals(trainNumber)) .collect(Collectors.toList()); } /** * 查询指定时间范围内的时刻信息 */ public ListTrainSchedule 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车次时刻表 ); ListTrainSchedule 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)); ListTrainSchedule 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) { // 实际项目中这里可以从数据库或配置文件中获取 MapString, 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); ListTrainSchedule 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 CacheString, ListTrainSchedule cache; public CachedTrainScheduleService(TrainScheduleService delegate) { this.delegate delegate; this.cache Caffeine.newBuilder() .expireAfterWrite(10, TimeUnit.MINUTES) .maximumSize(1000) .build(); } public ListTrainSchedule getSchedulesByTrainNumber(String trainNumber) { return cache.get(trainNumber, key - delegate.getSchedulesByTrainNumber(key)); } }10. 生产环境注意事项10.1 配置管理将易变的配置外化# application.properties train.schedule.timezone.defaultAsia/Shanghai train.schedule.cache.enabledtrue train.schedule.cache.duration10m10.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包头站发车这类业务信息并提供完整的增删改查功能。在实际项目中可以根据具体需求进一步扩展功能如实时数据同步、多语言支持、移动端适配等。