
1. 什么是 I/O 流在 Java 编程中I/O 流Input/Output Stream是处理输入和输出的核心抽象。它代表了一个有序的数据序列可以从源如文件、网络连接、内存缓冲区读取数据或向目标写入数据。流的概念屏蔽了底层设备磁盘、网络、键盘、屏幕等的差异让程序员可以用统一的方式处理各种数据源。简单来说你可以把流想象成一根“水管”数据像水一样在管道中单向流动输入流InputStream/Reader数据从外部源如文件流向你的程序。输出流OutputStream/Writer数据从你的程序流向外部目标。2. 流的分类Java I/O 流按照不同的维度可以分为以下几类2.1 按数据单位字节流 vs 字符流类型抽象基类说明典型用途字节流InputStream/OutputStream以字节8 位为单位读写数据。处理所有二进制文件如图片、视频、音频、可执行文件、网络数据传输。字符流Reader/Writer以字符16 位 Unicode为单位读写数据会自动处理字符编码如 UTF-8。处理文本文件.txt, .java, .html避免乱码问题。简单记忆带Stream的是字节流带Reader/Writer的是字符流。2.2 按功能节点流 vs 处理流包装流类型说明示例节点流直接连接数据源/目标的“低级”流。FileInputStream,FileReader,ByteArrayOutputStream处理流对现有流进行包装提供增强功能如缓冲、转换、对象序列化。BufferedReader,BufferedInputStream,ObjectOutputStream处理流通过装饰器模式实现可以多层嵌套例如// 一个典型的处理流嵌套文件 - 缓冲 - 字符解码BufferedReaderreadernewBufferedReader(newInputStreamReader(newFileInputStream(data.txt),UTF-8));3. 核心类与常用 API3.1 字节流核心类InputStream 常用方法int read()读取一个字节返回 0-255 的 int 值文件末尾返回 -1。int read(byte[] b)读取最多 b.length 个字节到数组返回实际读取的字节数。int read(byte[] b, int off, int len)读取 len 个字节到数组的指定位置。void close()关闭流释放系统资源必须调用。OutputStream 常用方法void write(int b)写入一个字节低 8 位。void write(byte[] b)写入整个字节数组。void write(byte[] b, int off, int len)写入数组的一部分。void flush()强制将缓冲区的数据写入目标对于缓冲流很重要。void close()关闭流。3.2 字符流核心类Reader 常用方法int read()读取一个字符返回 0-65535 的 int 值末尾返回 -1。int read(char[] cbuf)读取字符到数组。int read(CharBuffer target)读取到 CharBuffer。Writer 常用方法void write(int c)写入一个字符。void write(char[] cbuf)写入字符数组。void write(String str)直接写入字符串非常方便。void flush(),void close()同字节流。4. 基础用法示例4.1 字节流复制文件基础版importjava.io.FileInputStream;importjava.io.FileOutputStream;importjava.io.IOException;publicclassByteStreamCopy{publicstaticvoidmain(String[]args){// 1. 定义源文件和目标文件路径StringsourcePathsource.jpg;StringtargetPathtarget_copy.jpg;// 2. 声明流变量在 try 外声明便于 finally 关闭FileInputStreamfisnull;FileOutputStreamfosnull;try{// 3. 创建节点流fisnewFileInputStream(sourcePath);fosnewFileOutputStream(targetPath);// 4. 创建缓冲区一次读取 8KBbyte[]buffernewbyte[8192];intbytesRead;// 5. 循环读取并写入while((bytesReadfis.read(buffer))!-1){fos.write(buffer,0,bytesRead);// 注意只写入实际读取的部分}System.out.println(文件复制完成);}catch(IOExceptione){e.printStackTrace();}finally{// 6. 关闭流确保资源释放try{if(fis!null)fis.close();}catch(IOExceptione){e.printStackTrace();}try{if(fos!null)fos.close();}catch(IOExceptione){e.printStackTrace();}}}}4.2 字符流读取文本文件使用缓冲流提升效率importjava.io.BufferedReader;importjava.io.FileReader;importjava.io.IOException;publicclassCharacterStreamRead{publicstaticvoidmain(String[]args){// 使用 try-with-resources 语法自动关闭资源Java 7try(BufferedReaderbrnewBufferedReader(newFileReader(demo.txt))){Stringline;while((linebr.readLine())!null){// 逐行读取System.out.println(line);}}catch(IOExceptione){e.printStackTrace();}// 无需手动调用 close()try-with-resources 会自动处理}}4.3 字符流写入文本文件importjava.io.BufferedWriter;importjava.io.FileWriter;importjava.io.IOException;publicclassCharacterStreamWrite{publicstaticvoidmain(String[]args){// 第二个参数 true 表示追加模式false默认表示覆盖try(BufferedWriterbwnewBufferedWriter(newFileWriter(output.txt,true))){bw.write(Hello, Java I/O!);bw.newLine();// 写入系统相关的行分隔符bw.write(这是第二行。);// 缓冲流通常需要 flush() 确保数据写入但 close() 会自动调用 flush()}catch(IOExceptione){e.printStackTrace();}}}5. 高级特性与最佳实践5.1 使用 try-with-resources 自动管理资源从 Java 7 开始所有实现了AutoCloseable接口的流都可以使用try-with-resources语句确保流被正确关闭即使发生异常。// 安全、简洁的写法推荐try(FileInputStreamfisnewFileInputStream(source.dat);FileOutputStreamfosnewFileOutputStream(target.dat)){byte[]buffernewbyte[1024];intlen;while((lenfis.read(buffer))!-1){fos.write(buffer,0,len);}}catch(IOExceptione){e.printStackTrace();}// 无需 finally 块流会自动关闭5.2 缓冲流Buffered Streams大幅提升性能直接使用节点流如FileInputStream每次读写一个字节/字符效率极低。包装一层缓冲流可以将数据先攒在内存缓冲区批量读写减少实际的 I/O 操作次数。// 没有缓冲每次读写一个字节性能差FileInputStreamfisnewFileInputStream(largefile.bin);intb;while((bfis.read())!-1){// 每次 read() 都是一次系统调用// 处理 b}// 使用缓冲流性能提升数十倍甚至上百倍try(BufferedInputStreambisnewBufferedInputStream(newFileInputStream(largefile.bin))){byte[]buffernewbyte[8192];intlen;while((lenbis.read(buffer))!-1){// 一次读取最多 8KB}}经验法则处理文件 I/O 时几乎总是应该使用缓冲流。5.3 转换流桥接字节与字符世界InputStreamReader和OutputStreamWriter是字节流和字符流之间的桥梁。它们负责将字节按照指定的字符集Charset解码为字符或将字符编码为字节。// 指定 UTF-8 编码读取文本文件避免平台默认编码导致的乱码try(BufferedReaderreadernewBufferedReader(newInputStreamReader(newFileInputStream(data.txt),StandardCharsets.UTF_8))){// 明确指定字符集Stringline;while((linereader.readLine())!null){System.out.println(line);}}5.4 对象序列化流ObjectInputStream / ObjectOutputStream可以将实现了Serializable接口的 Java 对象转换为字节序列序列化保存到文件或网络也可以从字节序列恢复对象反序列化。importjava.io.*;classPersonimplementsSerializable{privateStringname;privateintage;// 构造方法、getter/setter 省略}publicclassObjectStreamDemo{publicstaticvoidmain(String[]args){PersonpersonnewPerson(张三,25);// 序列化对象到文件try(ObjectOutputStreamoosnewObjectOutputStream(newFileOutputStream(person.dat))){oos.writeObject(person);System.out.println(对象已序列化保存。);}catch(IOExceptione){e.printStackTrace();}// 从文件反序列化对象try(ObjectInputStreamoisnewObjectInputStream(newFileInputStream(person.dat))){PersonrestoredPerson(Person)ois.readObject();System.out.println(恢复的对象: restoredPerson.getName(), restoredPerson.getAge());}catch(IOException|ClassNotFoundExceptione){e.printStackTrace();}}}注意transient关键字修饰的字段不会被序列化。反序列化不会调用类的构造方法。序列化版本号serialVersionUID用于验证版本一致性建议显式声明。6. 常见问题与陷阱忘记关闭流导致资源文件句柄、网络连接泄漏。务必使用 try-with-resources。未调用flush()对于缓冲输出流在关闭前或需要立即写入时应调用flush()。字符编码问题读写文本时未指定字符集在不同平台可能产生乱码。始终明确指定字符集如StandardCharsets.UTF_8。混淆read()的返回值InputStream.read()返回int0-255不是byte文件末尾返回 -1。错误使用write(byte[])写入字节数组时如果读取长度小于数组长度应使用write(buffer, 0, len)而不是write(buffer)否则会写入无效数据。性能问题对小文件使用大缓冲区浪费内存对大文件使用小缓冲区或不用缓冲流则性能低下。通常 8KB (8192 bytes) 是一个较好的折中。7. NIO.2Java 7的增强Java 7 引入了 NIO.2java.nio.file包提供了更现代、功能更强的文件 I/O API。虽然传统的 I/O 流依然重要且常用但在某些场景下 NIO.2 更便捷Files工具类提供了读写文件的静态方法一行代码完成常见操作。// 读取所有行自动处理编码和资源关闭ListStringlinesFiles.readAllLines(Paths.get(file.txt),StandardCharsets.UTF_8);// 写入字符串Files.write(Paths.get(output.txt),content.getBytes(StandardCharsets.UTF_8));Path接口比File类更强大、更不易出错。异步 I/O支持非阻塞操作适合高并发场景。8. 总结Java I/O 流体系庞大但层次清晰。掌握它的关键是理解字节流/字符流、节点流/处理流这两组核心分类并熟练运用缓冲流、try-with-resources和明确的字符编码这三大最佳实践。对于大多数日常文件操作推荐组合读文本文件BufferedReaderInputStreamReaderFileInputStream指定字符集。写文本文件BufferedWriterOutputStreamWriterFileOutputStream指定字符集。复制二进制文件BufferedInputStreamFileInputStream和BufferedOutputStreamFileOutputStream。随着 Java 版本更新虽然出现了 NIO.2 等更现代的 API但传统的 I/O 流由于其简单、直观的特性在众多场景下依然是不可或缺的基础。理解其原理和正确用法是每一位 Java 开发者的必修课。