System.setIn()用于重定向标准输入流,需在被测代码读取前调用,注意线程安全、流关闭、编码匹配、换行符处理及测试后恢复原始流。

System.setIn() 重定向标准输入前必须关闭原 InputStream
直接调用 System.setIn(new FileInputStream("input.txt")) 很可能失败,不是因为语法错,而是底层流未被正确释放或已关闭。JVM 启动时 System.in 默认绑定到一个由本地方法管理的 FileInputStream(对应终端/键盘),它在某些 JVM 实现中**不可重复读取或不可 reset**。如果旧流仍处于打开但已读到 EOF 的状态,新流即使成功设置,后续 Scanner 或 BufferedReader 也可能立即抛出 NoSuchElementException 或静默返回空。
实操建议:
- 在调用
System.setIn()前,确保没有其他线程正在读System.in; - 若之前用过
Scanner(System.in),先调用其close()—— 这会间接关闭底层System.in(除非构造时传入了非System.in的流); - 更稳妥的做法是:显式保存原始流(
InputStream originalIn = System.in;),再在重定向后、程序退出前恢复它(尤其测试框架中避免污染全局状态)。
文件路径不合法或编码不匹配会导致 silent 失败
System.setIn() 本身不会校验文件是否存在或是否可读,它只负责替换引用。真正报错往往发生在第一次调用 System.in.read() 或构建 Scanner 时,比如抛出 FileNotFoundException 或乱码——后者是因为文件用 UTF-8 编码而 Scanner 默认按平台编码(如 Windows-1252)解析。
实操建议:
- 用
new FileInputStream("input.txt")前,先检查文件:Files.exists(Paths.get("input.txt")); - 构建
Scanner时显式指定编码:new Scanner(System.in, "UTF-8"); - 路径尽量用绝对路径或基于
getClass().getResource()获取 classpath 下资源,避免相对路径依赖工作目录(IDE 运行目录和命令行常不一致)。
重定向后 Scanner / BufferedReader 行为变化需手动处理换行符
键盘输入时,用户敲 Enter 会触发一次完整行读取;而从文件读取时,如果最后一行**没有结尾换行符**,Scanner.nextLine() 可能阻塞或抛异常(取决于缓冲区状态),BufferedReader.readLine() 则返回 null。这不是 System.setIn() 的问题,而是流语义差异导致的误判。
实操建议:
- 文件末尾确保有换行符(Unix 风格 LF 即可);
- 读取循环务必用
hasNextLine()+nextLine()(Scanner)或readLine() != null(BufferedReader)判断,不要依赖 try-catch 捕获 EOF; - 避免混用
next()和nextLine()—— 前者不消费换行符,会导致下一次nextLine()立即返回空字符串。
单元测试中重定向 System.in 必须 restore,否则污染其他 test
JUnit 测试类共享同一个 JVM,System.setIn() 是静态全局操作。一个 test 方法改了 System.in,下一个 test 若没还原,就会读到错误文件甚至抛 NPE(因前一个 test 关闭了流)。
实操建议:
- 在
@BeforeEach中保存原始流:originalIn = System.in;; - 在
@AfterEach中恢复:System.setIn(originalIn);; - 如果测试中提前关闭了新流(如
scanner.close()),恢复前需确认originalIn未被关闭 —— 否则恢复后System.in将不可用;必要时用new FileInputStream(FileDescriptor.in)重建原始 stdin。

















