Java Scanner类抛出java.util.NoSuchElementException异常求助
从你的描述来看,这种“多数时候正常、偶尔触发异常”的问题确实很棘手——毕竟3万行的规模不算特别大,定时运行的场景又增加了偶发因素的排查难度。结合你用嵌套Scanner处理~分隔字段的实现方式,我整理了几个最可能的原因和对应的解决思路:
1. 嵌套Scanner的隐性风险
你用外层Scanner读行、内层Scanner拆分字段的方式,其实存在不必要的性能开销和隐性的IO异常风险。Scanner本身依赖缓冲机制,嵌套使用时可能因为缓存同步问题,在FTP连接波动时出现读取不完整或解析错误。
替换方案:
抛弃嵌套Scanner,改用更高效直接的字符串拆分方式。如果字段中不存在转义的~(比如abc\~def这种包含分隔符的场景),直接用String.split("~")就行;如果有转义场景,推荐用Apache Commons Lang的StringUtils.splitByWholeSeparatorPreserveAllTokens()来处理,避免错误拆分。
同时,把外层的Scanner换成BufferedReader,它的IO稳定性和效率比Scanner更高,示例代码如下:
// 假设ftpInputStream是从FTP获取的输入流 try (BufferedReader reader = new BufferedReader(new InputStreamReader(ftpInputStream, StandardCharsets.UTF_8))) { String line; while ((line = reader.readLine()) != null) { // 拆分字段 String[] fields = line.split("~"); // 这里可以加字段数校验,避免格式错误的行导致后续异常 if (fields.length != 预期字段数) { // 记录错误日志,比如"格式错误行:" + line continue; } // 执行数据库插入逻辑 } } catch (IOException e) { // 一定要记录完整的异常栈和当前上下文,比如读取到的最后一行内容 log.error("读取FTP文件时发生IO异常,最后读取的行:{}", line, e); }
2. FTP连接的偶发不稳定
定时运行的程序很容易遇到网络波动:比如凌晨运行时网络带宽变化、FTP服务器临时负载高,导致连接中断或读取到不完整的行,进而触发解析或插入异常。
优化思路:
- 增加连接校验:读取文件前先获取文件的大小,读取过程中定期校验已读取的字节数,若和文件大小不匹配则触发重试逻辑。
- 断点续传+重试:如果你的FTP服务器支持断点续传,可以在异常时记录当前读取的位置,重新连接后从断点处继续读取,避免从头开始浪费时间。
- 强化日志:把FTP连接状态、读取进度(比如已读取多少行)、异常时的时间戳都记录下来,方便定位是特定时间段的网络问题还是随机波动。
3. 数据库插入阶段的隐性异常
有时候看起来是读取阶段的异常,实际上是插入数据库时触发的——比如字段长度超出表结构限制、非空字段为空、数据库连接池耗尽等,这些问题在数据偶发异常时才会暴露。
优化方案:
- 字段合法性校验:插入前对每个字段做校验,比如检查字符串长度是否符合表字段的定义,非空字段是否为空,必要时做截断或填充默认值处理。
- 批量插入代替单条插入:3万行数据单条插入不仅效率低,还容易因为频繁的数据库连接交互导致异常。用JDBC的批量插入可以大幅减少交互次数,示例代码:
try (Connection conn = yourConnectionPool.getConnection()) { conn.setAutoCommit(false); String insertSql = "INSERT INTO your_table (col1, col2, col3) VALUES (?, ?, ?)"; try (PreparedStatement pstmt = conn.prepareStatement(insertSql)) { int batchSize = 1000; // 每1000条提交一次 int rowCount = 0; String line; while ((line = reader.readLine()) != null) { String[] fields = line.split("~"); // 填充参数 pstmt.setString(1, fields[0]); pstmt.setString(2, fields[1]); pstmt.setString(3, fields[2]); pstmt.addBatch(); rowCount++; if (rowCount % batchSize == 0) { pstmt.executeBatch(); conn.commit(); } } // 提交剩余的批量数据 pstmt.executeBatch(); conn.commit(); } catch (SQLException e) { conn.rollback(); log.error("批量插入数据库失败,当前行:{}", line, e); } }
- 检查连接池配置:确保数据库连接池的最大连接数、超时时间设置合理,避免因为连接耗尽导致插入失败。
4. 文件内容的偶发格式错误
偶尔FTP上的文件可能会出现格式异常的行——比如字段数不对、行尾有不可见字符、换行符异常,这些问题在Scanner解析时可能触发NoSuchElementException之类的异常。
解决办法:
- 对每行拆分后的字段数做校验,不符合预期的行直接记录到错误日志并跳过,不要让单个错误行导致整个程序中断。
- 读取行后做简单的清理,比如去掉行尾的空白字符:
line = line.replaceAll("\\s+$", ""),避免因为不可见字符导致拆分错误。
最后再强调一下:完善的日志是排查偶发问题的关键,一定要把异常时的上下文(比如当前行内容、异常栈、FTP连接信息)都记录下来,这样下次出现问题时就能快速定位原因。
内容的提问来源于stack exchange,提问作者Sandyboy

