1995年Java 1.0发布时,File类已经存在。它能创建文件、删除文件、查看文件大小和修改时间——但打不开文件看里面写了什么。就像你有一把钥匙能确认门存在,却拧不开锁。
第一次妥协:Streams的诞生
Java 1.0的解决方案是把一切抽象成"流"(Stream)。InputStream负责数据流入程序,OutputStream负责数据流出。这个设计 borrowed from Unix 哲学:一切皆字节流。
但抽象类本身无法工作。InputStream和OutputStream只是接口骨架,没有操作系统对接层。开发者拿到的是一座设计图纸,不是能住的房子。
JDK 1.1补上了FileInputStream和FileOutputStream,终于能读写文件内容。代价是代码冗长:打开文件、套BufferedInputStream缓冲、手动关闭资源,样板代码占80%。
第二次补丁:NIO的异步野心
2002年Java 1.4推出NIO(New Input/Output,新输入/输出)。核心变化是Channel(通道)和Buffer(缓冲区)分离,加上Selector(选择器)支持单线程管理多连接。
这个设计瞄准高性能服务器场景。但开发者很快发现:NIO不是Stream的替代品,是并行宇宙。FileChannel能内存映射文件,却不能直接用System.in;Selector擅长网络I/O,对文件I/O支持残缺。
两套API并存了12年。企业代码里Stream和NIO混用成为常态,新人入职第一件事是搞懂该用哪个。
第三次重构:NIO.2的迟到统一
Java 7(2011年)的NIO.2终于引入Path和Files工具类。一行Files.readAllLines()替代了十几行Stream代码,WatchService能监听文件变化,AsynchronousFileChannel支持异步文件操作。
但历史包袱没消失。System.in仍是InputStream,标准输出仍是PrintStream。Java 21的虚拟线程能缓解阻塞问题,却改不了API分裂的底子。
30年间,Java I/O经历了三次范式转移,每次都在修补前代的边界。现在打开GitHub任意Java项目,大概率同时存在File、Path、InputStream、FileChannel四种操作文件的方式——它们来自三个不同的十年,却必须共存于同一个JVM。
如果一门语言的设计遗产是"四种方法做同一件事,各有限制",这是进化还是债务?
热门跟贴