一、一句话总结
IO流 = 数据流动的管道,分字节流(8位)和字符流(16位),用于文件读写、网络通信等数据交换
二、核心分类体系
按数据类型:
├── 字节流 (byte, 8位)
│ ├── InputStream (抽象类)
│ │ ├── FileInputStream (文件)
│ │ ├── ByteArrayInputStream (内存)
│ │ └── BufferedInputStream (缓冲)
│ │
│ └── OutputStream (抽象类)
│ ├── FileOutputStream (文件)
│ ├── ByteArrayOutputStream (内存)
│ └── BufferedOutputStream (缓冲)
│
└── 字符流 (char, 16位, 处理文本)
├── Reader (抽象类)
│ ├── FileReader (文件)
│ ├── InputStreamReader (字节→字符)
│ └── BufferedReader (缓冲)
│
└── Writer (抽象类)
├── FileWriter (文件)
├── OutputStreamWriter (字符→字节)
└── BufferedWriter (缓冲)
三、字节流(处理所有类型文件)
1. FileInputStream / FileOutputStream
// 1. 读取文件(字节流)
try (FileInputStream fis = new FileInputStream("input.txt")) {
int byteData;
while ((byteData = fis.read()) != -1) { // 一次读1字节
System.out.print((char) byteData);
}
}
// 2. 写入文件(字节流)
try (FileOutputStream fos = new FileOutputStream("output.txt")) {
String text = "Hello World";
fos.write(text.getBytes()); // 字符串转字节数组写入
}
// 3. 文件复制(字节流)
try (FileInputStream fis = new FileInputStream("source.jpg");
FileOutputStream fos = new FileOutputStream("copy.jpg")) {
byte[] buffer = new byte[1024]; // 1KB缓冲区
int bytesRead;
while ((bytesRead = fis.read(buffer)) != -1) {
fos.write(buffer, 0, bytesRead); // 写入实际读取的字节数
}
}
2. BufferedInputStream / BufferedOutputStream
// 缓冲流(性能更好)
try (BufferedInputStream bis = new BufferedInputStream(
new FileInputStream("largefile.dat"));
BufferedOutputStream bos = new BufferedOutputStream(
new FileOutputStream("copy.dat"))) {
byte[] buffer = new byte[8192]; // 8KB缓冲区
int bytesRead;
while ((bytesRead = bis.read(buffer)) != -1) {
bos.write(buffer, 0, bytesRead);
}
}
四、字符流(处理文本文件)
1. FileReader / FileWriter
// 1. 读取文本文件
try (FileReader reader = new FileReader("text.txt")) {
int charData;
while ((charData = reader.read()) != -1) { // 一次读1字符
System.out.print((char) charData);
}
}
// 2. 写入文本文件
try (FileWriter writer = new FileWriter("output.txt")) {
writer.write("Hello Java IO\n");
writer.write("第二行内容");
}
// 3. 追加写入
try (FileWriter writer = new FileWriter("log.txt", true)) { // true=追加
writer.write("新的日志记录\n");
}
2. BufferedReader / BufferedWriter
// 1. 缓冲读取(高效)
try (BufferedReader br = new BufferedReader(
new FileReader("largefile.txt"))) {
String line;
while ((line = br.readLine()) != null) { // 按行读取
System.out.println(line);
}
}
// 2. 缓冲写入
try (BufferedWriter bw = new BufferedWriter(
new FileWriter("output.txt"))) {
bw.write("第一行");
bw.newLine(); // 换行
bw.write("第二行");
bw.flush(); // 刷新缓冲区
}
五、转换流(字节↔字符)
1. InputStreamReader / OutputStreamWriter
// 1. 指定编码读取(解决中文乱码)
try (InputStreamReader isr = new InputStreamReader(
new FileInputStream("gbk.txt"), "GBK")) { // 指定GBK编码
int charData;
while ((charData = isr.read()) != -1) {
System.out.print((char) charData);
}
}
// 2. 指定编码写入
try (OutputStreamWriter osw = new OutputStreamWriter(
new FileOutputStream("utf8.txt"), StandardCharsets.UTF_8)) {
osw.write("中文内容");
}
// 3. 控制台输入(System.in是字节流)
try (BufferedReader console = new BufferedReader(
new InputStreamReader(System.in))) {
System.out.print("请输入: ");
String input = console.readLine();
System.out.println("你输入了: " + input);
}
六、数据流(处理基本数据类型)
1. DataInputStream / DataOutputStream
// 写入基本数据类型
try (DataOutputStream dos = new DataOutputStream(
new FileOutputStream("data.bin"))) {
dos.writeInt(100); // 4字节
dos.writeDouble(3.14); // 8字节
dos.writeBoolean(true); // 1字节
dos.writeUTF("字符串"); // UTF-8编码
}
// 读取基本数据类型(必须按写入顺序读)
try (DataInputStream dis = new DataInputStream(
new FileInputStream("data.bin"))) {
int intValue = dis.readInt(); // 100
double doubleValue = dis.readDouble(); // 3.14
boolean boolValue = dis.readBoolean(); // true
String strValue = dis.readUTF(); // "字符串"
}
七、对象流(序列化)
1. ObjectInputStream / ObjectOutputStream
// 对象必须实现Serializable接口
class Person implements Serializable {
private static final long serialVersionUID = 1L;
private String name;
private int age;
// transient字段不会被序列化
private transient String password;
// 构造器、getter、setter
}
// 序列化对象到文件
try (ObjectOutputStream oos = new ObjectOutputStream(
new FileOutputStream("person.dat"))) {
Person person = new Person("张三", 25);
oos.writeObject(person);
}
// 反序列化
try (ObjectInputStream ois = new ObjectInputStream(
new FileInputStream("person.dat"))) {
Person person = (Person) ois.readObject();
System.out.println(person.getName()); // 张三
}
八、NIO(New IO,Java 1.4+)
1. Path 和 Files 工具类
import java.nio.file.*;
// 1. 文件操作(更简单的方式)
Path path = Paths.get("test.txt");
// 读取所有行
List<String> lines = Files.readAllLines(path, StandardCharsets.UTF_8);
// 写入文件
Files.write(path, "内容".getBytes(), StandardOpenOption.CREATE);
// 复制文件
Files.copy(Paths.get("source.txt"), Paths.get("dest.txt"));
// 判断文件是否存在
boolean exists = Files.exists(path);
// 2. 遍历目录
try (Stream<Path> paths = Files.walk(Paths.get("."), 3)) { // 深度3层
paths.filter(Files::isRegularFile)
.filter(p -> p.toString().endsWith(".java"))
.forEach(System.out::println);
}
2. Files 常用方法
// 文件属性
long size = Files.size(path);
FileTime lastModified = Files.getLastModifiedTime(path);
boolean isDirectory = Files.isDirectory(path);
// 创建目录
Files.createDirectories(Paths.get("a/b/c")); // 创建多级目录
// 移动/重命名
Files.move(Paths.get("old.txt"), Paths.get("new.txt"));
// 删除
Files.deleteIfExists(path); // 存在才删除,不会抛异常
九、实际应用示例
1. 配置文件读取
public class ConfigReader {
private Properties props = new Properties();
public void loadConfig(String filename) {
try (InputStream is = new FileInputStream(filename)) {
props.load(is); // Properties自动处理编码
} catch (IOException e) {
// 使用类路径资源作为后备
try (InputStream is = getClass().getClassLoader()
.getResourceAsStream("default.properties")) {
props.load(is);
}
}
}
public String getProperty(String key) {
return props.getProperty(key);
}
}
// 使用
ConfigReader config = new ConfigReader();
config.loadConfig("app.properties");
String dbUrl = config.getProperty("database.url");
2. 日志记录器
public class FileLogger {
private BufferedWriter writer;
public FileLogger(String logFile) throws IOException {
// 追加模式,自动刷新
writer = new BufferedWriter(
new OutputStreamWriter(
new FileOutputStream(logFile, true),
StandardCharsets.UTF_8
)
);
}
public void log(String level, String message) {
try {
String timestamp = LocalDateTime.now().toString();
writer.write(String.format("[%s] %s: %s",
timestamp, level, message));
writer.newLine();
writer.flush();
} catch (IOException e) {
System.err.println("日志写入失败: " + e.getMessage());
}
}
public void close() {
try {
writer.close();
} catch (IOException e) {
// 忽略关闭异常
}
}
}
3. CSV文件处理
public class CSVProcessor {
public List<Map<String, String>> readCSV(String filename) {
List<Map<String, String>> records = new ArrayList<>();
try (BufferedReader br = new BufferedReader(
new FileReader(filename))) {
String headerLine = br.readLine();
if (headerLine == null) return records;
String[] headers = headerLine.split(",");
String line;
while ((line = br.readLine()) != null) {
String[] values = line.split(",");
Map<String, String> record = new LinkedHashMap<>();
for (int i = 0; i < headers.length && i < values.length; i++) {
record.put(headers[i].trim(), values[i].trim());
}
records.add(record);
}
}
return records;
}
public void writeCSV(String filename, List<Map<String, String>> data) {
if (data.isEmpty()) return;
try (BufferedWriter bw = new BufferedWriter(
new FileWriter(filename))) {
// 写入表头
Set<String> headers = data.get(0).keySet();
bw.write(String.join(",", headers));
bw.newLine();
// 写入数据
for (Map<String, String> record : data) {
List<String> values = new ArrayList<>();
for (String header : headers) {
values.add(record.getOrDefault(header, ""));
}
bw.write(String.join(",", values));
bw.newLine();
}
}
}
}
十、最佳实践和注意事项
1. 资源自动关闭(try-with-resources)
// ✅ 推荐:自动关闭资源
try (FileInputStream fis = new FileInputStream("file.txt");
BufferedReader br = new BufferedReader(new InputStreamReader(fis))) {
// 使用资源
String line = br.readLine();
}
// ❌ 不要:手动关闭容易遗漏
FileInputStream fis = null;
try {
fis = new FileInputStream("file.txt");
// 使用资源
} finally {
if (fis != null) {
try { fis.close(); } catch (IOException e) { /* 忽略 */ }
}
}
2. 处理大文件
// 大文件处理:使用缓冲,分块读取
try (BufferedInputStream bis = new BufferedInputStream(
new FileInputStream("largefile.bin"), 8192)) { // 8KB缓冲
byte[] buffer = new byte[8192];
int bytesRead;
long totalBytes = 0;
while ((bytesRead = bis.read(buffer)) != -1) {
totalBytes += bytesRead;
// 处理buffer中的前bytesRead个字节
processChunk(buffer, bytesRead);
// 显示进度(可选)
if (totalBytes % (1024 * 1024) == 0) { // 每MB显示一次
System.out.printf("已处理: %.2f MB%n", totalBytes / 1024.0 / 1024.0);
}
}
}
3. 编码问题
// 明确指定编码,避免乱码
String content = "中文内容";
// 写入时指定编码
try (OutputStreamWriter osw = new OutputStreamWriter(
new FileOutputStream("file.txt"),
StandardCharsets.UTF_8)) {
osw.write(content);
}
// 读取时指定编码
try (InputStreamReader isr = new InputStreamReader(
new FileInputStream("file.txt"),
StandardCharsets.UTF_8)) {
char[] buffer = new char[1024];
int charsRead;
StringBuilder sb = new StringBuilder();
while ((charsRead = isr.read(buffer)) != -1) {
sb.append(buffer, 0, charsRead);
}
System.out.println(sb.toString());
}
4. 性能对比
// 性能比较(从快到慢):
// 1. NIO.2 (Files.readAllBytes/readAllLines)
// 2. BufferedInputStream/BufferedOutputStream
// 3. FileInputStream/FileOutputStream (无缓冲)
// 4. Scanner (方便但慢)
// 选择建议:
// - 小文件:Files.readAllBytes()
// - 大文件:BufferedInputStream/OutputStream
// - 文本文件:BufferedReader/BufferedWriter
// - 需要行处理:Files.lines() + Stream API
十一、常见问题解决
1. 文件锁问题
// 检查文件是否被其他进程占用
public boolean isFileLocked(Path path) {
try (FileChannel channel = FileChannel.open(path,
StandardOpenOption.WRITE)) {
// 尝试获取排他锁
FileLock lock = channel.tryLock();
if (lock != null) {
lock.release();
return false; // 文件未被锁定
}
} catch (Exception e) {
return true; // 无法获取锁,可能被占用
}
return true;
}
2. 临时文件处理
// 创建临时文件
Path tempFile = Files.createTempFile("prefix", ".tmp");
try {
// 使用临时文件
Files.write(tempFile, "临时内容".getBytes());
// 处理完成后自动删除
} finally {
Files.deleteIfExists(tempFile); // 确保删除
}
// 或者使用deleteOnExit
File temp = File.createTempFile("temp", ".txt");
temp.deleteOnExit(); // JVM退出时删除
总结要点
核心选择:
- 字节流:处理图片、视频、exe等所有文件
- 字符流:处理文本文件(.txt, .java, .html)
- 缓冲流:总是使用,提高性能
- 转换流:处理编码问题
最佳实践:
- 总是用try-with-resources
- 明确指定字符编码
- 大文件用缓冲和分块处理
- NIO.2的Files类更简洁
记忆口诀:
- 字节流:InputStream/OutputStream(处理一切)
- 字符流:Reader/Writer(处理文本)
- 缓冲流:BufferedXXX(性能必备)
- 转换流:指定编码(解决乱码)