发布于2026-07-10 阅读(0)
扫一扫,手机访问
先说一个核心判断:Ja va 的三个标准流——System.in、System.out、System.err——不是你想关就能关的。它们从 JVM 启动那一刻起,就跟操作系统底层的资源绑定在一起,生命周期与进程同生共死。如果强行调用 close(),后续所有操作都会抛出 IOException,而且这个错误是“不可逆”的。这不是 bug,这是语义约束。

根本原因在于:这些流是 JVM 启动时绑定的底层文件描述符,比如 stdin 对应 fd 0。一旦你调用 System.in.close(),就等于把这个文件描述符释放了。之后 JVM 不会提供任何机制重新建立这个连接——没有“重新打开”这个选项。
几个关键细节:
BufferedInputStream,markSupported() 返回 false,所以别指望通过 reset() 来重复读取输入。new FileInputStream(FileDescriptor.in) 也不行——因为 FileDescriptor.in 在关闭后已经失效。println() 会直接抛异常,所有依赖控制台输出的框架、日志系统都会跟着遭殃。既然不能直接操作 System.in,那就换个思路——数据缓存 + 逻辑复用。具体做法有三种:
Scanner(System.in) 或 BufferedReader(new InputStreamReader(System.in)) 把整行或整块数据读入内存,存入 String 或 byte[],后续反复解析这个内存副本即可。InputStream 接口,可以把原始输入内容转换成 ByteArrayInputStream,它天然支持 mark()/reset(),随意重读。InputStream 参数,而不是硬编码 System.in。这样既方便测试,也方便复用——比如单元测试时直接传入 new ByteArrayInputStream("test".getBytes())。需要调用 close() 的,从来都不是这三个标准流本身,而是你主动创建的、非标准的流实例。比如:
new FileInputStream("data.txt") 打开的文件流;new BufferedInputStream(System.in) 包装后的流——注意,这包装的是副本,关闭它不影响 System.in 原始引用,但一般也不建议这样操作;System.setIn(...) 替换后的自定义输入流——此时它的生命周期就由你负责了。对于这些流,务必使用 try-with-resources 或 finally 块确保关闭,防止文件句柄泄漏。
如果通过 System.setIn(new FileInputStream("input.txt")) 重定向了标准输入,有两个关键点:
System.in 之后执行;System.in(键盘流)并没有被关闭,只是暂时不可达,重定向操作本身不改变关闭逻辑——你关的是自己 new 出来的流,不是 JVM 的 stdin。一句话总结:标准流是 JVM 的“私有财产”,别碰它。你的任务是管理好自己创建的那些流。