11.Java I/O 流:文件读写与数据持久化
在 Java 编程世界里,I/O 流是实现数据输入输出的基础组件,广泛应用于文件操作、网络通信、数据持久化等场景。本文将深入剖析 Java I/O 流的核心概念,区分字节流与字符流的使用场景,结合文件读写、图片复制等实战案例,帮助开发者掌握数据存储与读取的关键技术。
一、Java I/O 流核心概念
1. 流的分类
Java I/O 流按方向分为输入流(读取数据)和输出流(写入数据),按处理单元分为字节流和字符流:
- 字节流:以字节(byte,8 位)为单位处理数据,适用于二进制文件(如图片、视频、可执行文件),能够处理任意类型的数据。
- 字符流:以字符(char,16 位 Unicode)为单位处理数据,自动处理字符编码,适用于文本文件(如.txt、.properties)。
2. 流的顶层抽象类
字节流:
- 输入流顶层类:InputStream(抽象类,定义字节读取方法)
- 输出流顶层类:OutputStream(抽象类,定义字节写入方法)
字符流:
- 输入流顶层类:Reader(抽象类,定义字符读取方法)
- 输出流顶层类:Writer(抽象类,定义字符写入方法)
3. 流的装饰器模式
Java 通过装饰器模式增强流的功能,核心思想是通过包装类为基础流添加缓冲、编码转换、数据格式处理等功能。例如:
- BufferedInputStream/BufferedOutputStream:添加缓冲功能,减少磁盘 / 网络交互次数,提升读写效率。
- InputStreamReader/OutputStreamWriter:实现字节流与字符流的桥梁,处理字符编码转换。
- DataInputStream/DataOutputStream:支持基本数据类型(如int、String)的读写。
二、字节流:处理二进制数据
1. 文件字节流:FileInputStream与FileOutputStream
使用场景:读取 / 写入二进制文件(如图片、音频、压缩包),适合处理非文本类型的数据。
示例 1:单字节读取(原理演示,性能较低)
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException; /** * 图片复制示例(单字节读取) * 演示基本字节流操作,适合理解底层原理,但不推荐用于实际项目 */
public class ImageCopyExample { public static void main(String[] args) { String sourcePath = "source.jpg"; // 源文件路径 String targetPath = "target.jpg"; // 目标文件路径 // try-with-resources自动关闭流,避免资源泄漏(Java 7+特性) try (FileInputStream fis = new FileInputStream(sourcePath); FileOutputStream fos = new FileOutputStream(targetPath)) { int byteData; // read()方法:读取一个字节,返回-1表示文件结束 while ((byteData = fis.read()) != -1) { fos.write(byteData); // 写入单个字节 } System.out.println("图片复制完成(单字节模式)!"); } catch (IOException e) { System.err.println("文件操作失败:" + e.getMessage()); e.printStackTrace(); } }
}
示例 2:缓冲区读取(推荐实现,性能提升 10 倍 +)
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException; /** * 图片复制示例(缓冲区读取) * 使用字节数组批量读写,减少系统调用次数 */
public class BufferedImageCopyExample { public static void main(String[] args) { String sourcePath = "source.jpg"; String targetPath = "target.jpg"; byte[] buffer = new byte[8192]; // 8KB缓冲区(典型值,可根据内存调整) try (FileInputStream fis = new FileInputStream(sourcePath); FileOutputStream fos = new FileOutputStream(targetPath)) { int bytesRead; // read(byte[]):读取数据到缓冲区,返回实际读取的字节数 while ((bytesRead = fis.read(buffer)) != -1) { fos.write(buffer, 0, bytesRead); // 写入缓冲区中的有效数据 } System.out.println("图片复制完成(缓冲区模式)!"); } catch (IOException e) { System.err.println("文件操作失败:" + e.getMessage()); } }
}
2. 缓冲字节流:BufferedInputStream与BufferedOutputStream
原理:内部维护一个默认大小(通常 8KB)的缓冲区,批量读取 / 写入数据,减少与底层设备(如磁盘)的交互次数,显著提升性能。
示例:缓冲流高效复制图片
import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException; public class BufferedStreamExample { public static void main(String[] args) { String sourcePath = "source.jpg"; String targetPath = "target.jpg"; // 装饰器模式:将FileInputStream包装为BufferedInputStream try (BufferedInputStream bis = new BufferedInputStream(new FileInputStream(sourcePath)); BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream(targetPath))) { byte[] buffer = new byte[8192]; int bytesRead; while ((bytesRead = bis.read(buffer)) != -1) { bos.write(buffer, 0, bytesRead); } System.out.println("使用缓冲流复制完成,性能提升约10倍!"); } catch (IOException e) { e.printStackTrace(); } }
}
三、字符流:处理文本数据
1. 文件字符流:FileReader与FileWriter
使用场景:读取 / 写入文本文件(如日志、配置文件),自动处理字符编码(默认使用系统编码,可能导致乱码)。
示例 1:单字符读取(原理演示)
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException; /** * 文本文件复制示例(单字符读取) * 注意:默认使用系统编码(如Windows的GBK),处理中文可能乱码 */
public class TextCopyExample { public static void main(String[] args) { String sourcePath = "source.txt"; String targetPath = "target.txt"; try (FileReader reader = new FileReader(sourcePath); FileWriter writer = new FileWriter(targetPath)) { int character; // read():读取一个字符(Unicode码点),返回-1表示文件结束 while ((character = reader.read()) != -1) { writer.write(character); // 写入单个字符 } System.out.println("文本复制完成(单字符模式)!"); } catch (IOException e) { e.printStackTrace(); } }
}
示例 2:显式指定 UTF-8 编码(推荐)
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter; /** * 文本文件复制(指定UTF-8编码) * 解决中文乱码问题,支持跨平台文件读写 */
public class EncodingExample { public static void main(String[] args) { String sourcePath = "source.txt"; String targetPath = "target.txt"; try (BufferedReader reader = new BufferedReader( new InputStreamReader(new FileInputStream(sourcePath), "UTF-8")); BufferedWriter writer = new BufferedWriter( new OutputStreamWriter(new FileOutputStream(targetPath), "UTF-8"))) { int character; while ((character = reader.read()) != -1) { writer.write(character); } System.out.println("UTF-8编码文本复制完成!"); } catch (IOException e) { e.printStackTrace(); } }
}
2. 缓冲字符流:BufferedReader与BufferedWriter
优势:
- BufferedReader支持按行读取(readLine()方法),无需手动处理换行符。
- BufferedWriter支持高效写入,并提供平台无关的换行符(newLine()方法)。
示例:按行读取文本文件
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException; /** * 按行处理文本文件(推荐实践) * 适用于日志分析、配置文件解析等场景 */
public class LineBasedExample { public static void main(String[] args) { String sourcePath = "access.log"; String targetPath = "clean.log"; try (BufferedReader reader = new BufferedReader(new FileReader(sourcePath)); BufferedWriter writer = new BufferedWriter(new FileWriter(targetPath))) { String line; // 逐行读取(自动过滤换行符,返回null表示文件结束) while ((line = reader.readLine()) != null) { // 处理逻辑:例如过滤空行或敏感信息 if (!line.trim().isEmpty()) { writer.write(line); writer.newLine(); // 写入平台适配的换行符(Windows为\r\n,Linux为\n) } } System.out.println("按行处理文本完成!"); } catch (IOException e) { e.printStackTrace(); } }
}
四、实战案例:综合应用
案例 1:统计文本文件单词出现次数
需求:读取文本文件,统计每个单词的出现次数,忽略大小写和标点符号。
import java.io.BufferedReader;
import java.io.FileReader;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
import java.util.TreeMap; public class WordCountTool { public static void main(String[] args) { String filePath = "input.txt"; Map<String, Integer> wordCounts = new HashMap<>(); try (BufferedReader reader = new BufferedReader(new FileReader(filePath))) { String line; while ((line = reader.readLine()) != null) { // 预处理:转小写、去除首尾空格、分割单词 String[] words = line.trim().toLowerCase() .split("[\\s\\p{Punct}]+"); // 按空格和标点分割 for (String word : words) { if (!word.isEmpty()) { wordCounts.merge(word, 1, Integer::sum); // 统计次数 } } } // 按单词字母顺序排序后输出 Map<String, Integer> sortedCounts = new TreeMap<>(wordCounts); System.out.println("单词统计结果:"); sortedCounts.forEach((word, count) -> System.out.printf("%-15s: %d%n", word, count) ); } catch (IOException e) { System.err.println("文件读取失败:" + e.getMessage()); } }
}
案例 2:图片加密 / 解密(字节流应用)
原理:使用异或运算(XOR)实现简单加密,相同密钥可加密和解密。
import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException; public class ImageEncryptTool { private static final byte ENCRYPT_KEY = 0x3B; // 自定义加密密钥(1-255之间) public static void main(String[] args) { String sourcePath = "source.jpg"; String encryptedPath = "encrypted.jpg"; String decryptedPath = "decrypted.jpg"; try { encryptFile(sourcePath, encryptedPath); System.out.println("加密成功:" + encryptedPath); encryptFile(encryptedPath, decryptedPath); System.out.println("解密成功:" + decryptedPath); } catch (IOException e) { e.printStackTrace(); } } /** * 异或加密/解密文件 * @param source 源文件路径 * @param target 目标文件路径 */ private static void encryptFile(String source, String target) throws IOException { try (BufferedInputStream bis = new BufferedInputStream(new FileInputStream(source)); BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream(target))) { byte[] buffer = new byte[8192]; int bytesRead; while ((bytesRead = bis.read(buffer)) != -1) { for (int i = 0; i < bytesRead; i++) { buffer[i] ^= ENCRYPT_KEY; // 异或运算实现加密/解密 } bos.write(buffer, 0, bytesRead); } } }
}
五、Java I/O 流最佳实践
1. 资源管理:强制使用try-with-resources
// 反例:手动关闭流(易遗漏,导致资源泄漏)
FileInputStream fis = null;
try { fis = new FileInputStream("file.txt"); // 业务逻辑
} finally { if (fis != null) { fis.close(); // 可能抛出IOException,需再次处理 }
} // 正例:自动关闭流(推荐,Java 7+)
try (FileInputStream fis = new FileInputStream("file.txt")) { // 业务逻辑(无需手动关闭,JVM自动处理)
} catch (IOException e) { e.printStackTrace();
}
2. 性能优化:永远使用缓冲流
- 字节流:优先使用BufferedInputStream/BufferedOutputStream,缓冲区大小建议 8KB(new byte[8192])。
- 字符流:优先使用BufferedReader/BufferedWriter,避免直接使用FileReader/FileWriter。
3. 编码处理:显式指定字符集
文本文件读写时,始终通过InputStreamReader/OutputStreamWriter显式指定编码(如UTF-8),避免依赖系统默认编码导致乱码。
4. 大文件处理:分段读取写入
// 大文件分段读取(例如1GB文件)
long fileSize = new File("large.file").length();
byte[] buffer = new byte[1024 * 1024]; // 1MB缓冲区
while (fis.read(buffer) != -1 && totalRead < fileSize) { fos.write(buffer, 0, (int) Math.min(buffer.length, fileSize - totalRead)); totalRead += buffer.length;
}
六、Java NIO:现代 I/O 解决方案
NIO提供了更简洁、高效的文件操作 API,基于Path和Files类,支持异步 IO、文件属性访问等高级功能。
示例:NIO快速复制文件
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.nio.file.StandardCopyOption; public class NioFileCopy { public static void main(String[] args) { Path source = Paths.get("source.txt"); Path target = Paths.get("target.txt"); try { // 快速复制文件(支持覆盖已存在文件) Files.copy(source, target, StandardCopyOption.REPLACE_EXISTING); System.out.println("NIO复制完成,性能优于传统I/O!"); // 读取所有行(适合小文件) Files.lines(source).forEach(System.out::println); } catch (IOException e) { e.printStackTrace(); } }
}
七、总结:选择合适的 I/O 方案
场景 | 推荐 API | 核心优势 |
二进制文件读写 | BufferedInputStream/BufferedOutputStream | 缓冲机制提升效率,支持任意二进制数据 |
文本文件读写(小文件) | BufferedReader/BufferedWriter | 按行处理,自动处理编码和换行符 |
文本文件读写(大文件) | NIO.2 Files.lines()/Files.write() | 流式处理,内存占用低,支持链式操作 |
对象序列化 | ObjectInputStream/ObjectOutputStream | 支持 Java 对象的持久化存储 |
掌握 Java I/O 流的核心原理与使用技巧,是实现数据持久化、文件处理和网络通信的基础。建议开发者在实际项目中:
- 根据数据类型(二进制 / 文本)选择字节流或字符流;
- 始终使用缓冲流提升性能,避免直接操作基础流;
- 显式处理字符编码,优先使用UTF-8保证跨平台兼容性;
- 大文件处理时采用分段读写或 NIO.2 的高效 API。
对于高性能场景(如分布式文件系统、海量日志处理),可进一步学习 Java NIO 的通道(Channel)和缓冲区(Buffer)机制,以及异步 I/O 操作,提升系统吞吐量和响应速度。