Java I/O流概述

理解Java I/O流的基本概念和分类体系

学习目标

掌握Java I/O流的分类体系,理解字节流与字符流的区别

I/O流的核心概念

Java I/O流是处理输入/输出的核心机制,主要分为:

输入流 vs 输出流

  • 输入流:从数据源读取数据(如文件、网络)
  • 输出流:向目标写入数据(如文件、控制台)

字节流 vs 字符流

  • 字节流:以字节为单位操作数据(8位)
  • 字符流:以字符为单位操作数据(16位Unicode)

节点流 vs 处理流

  • 节点流:直接操作数据源的流
  • 处理流:对其他流进行包装,增加功能

Java I/O类层次结构

java.io ├── InputStream (字节输入流) │ ├── FileInputStream │ ├── ByteArrayInputStream │ ├── FilterInputStream │ │ ├── BufferedInputStream │ │ ├── DataInputStream │ │ └── ... │ └── ... ├── OutputStream (字节输出流) │ ├── FileOutputStream │ ├── ByteArrayOutputStream │ ├── FilterOutputStream │ │ ├── BufferedOutputStream │ │ ├── DataOutputStream │ │ └── ... │ └── ... ├── Reader (字符输入流) │ ├── InputStreamReader │ │ └── FileReader │ ├── BufferedReader │ ├── CharArrayReader │ └── ... └── Writer (字符输出流) ├── OutputStreamWriter │ └── FileWriter ├── BufferedWriter ├── CharArrayWriter └── ...

学习小贴士

字节流直接操作原始字节数据,适合处理二进制文件(如图片、音频)。字符流基于字节流,但处理文本时自动处理字符编码,适合处理文本文件。

字节流(InputStream/OutputStream)

深入理解字节流的使用方法和常见子类

学习目标

掌握InputStream/OutputStream的核心方法及其常用实现类

InputStream核心方法

方法 描述
int read() 读取一个字节,返回0-255的int值,到达结尾返回-1
int read(byte[] b) 读取数据到字节数组,返回实际读取的字节数
int read(byte[] b, int off, int len) 读取指定长度的数据到字节数组的指定位置
long skip(long n) 跳过并丢弃n个字节的数据
int available() 返回可以读取的字节数(不保证准确)
void close() 关闭流并释放系统资源

OutputStream核心方法

方法 描述
void write(int b) 写入一个字节(低8位)
void write(byte[] b) 写入整个字节数组
void write(byte[] b, int off, int len) 写入字节数组的指定部分
void flush() 刷新输出流,强制写出所有缓冲数据
void close() 关闭流并释放系统资源

字节流使用示例:文件复制

使用字节流实现文件复制功能:

import java.io.*; public class FileCopyExample { public static void main(String[] args) { // 使用try-with-resources确保流正确关闭 try (InputStream in = new FileInputStream("source.jpg"); OutputStream out = new FileOutputStream("copy.jpg")) { byte[] buffer = new byte[8192]; // 8KB缓冲区 int bytesRead; // 循环读取直到文件末尾 while ((bytesRead = in.read(buffer)) != -1) { out.write(buffer, 0, bytesRead); } System.out.println("文件复制成功!"); } catch (IOException e) { System.err.println("文件复制出错: " + e.getMessage()); } } }

性能优化提示

使用缓冲区(如BufferedInputStream)可以显著提高I/O性能。在示例中,我们使用了8KB的缓冲区,这是经过实践检验的较优大小。

字符流(Reader/Writer)

掌握字符流的使用及其与字节流的区别

学习目标

理解字符编码的重要性,掌握Reader/Writer处理文本数据

字符流的核心优势

  • 自动处理字符编码(如UTF-8、GBK)
  • 直接操作字符而非字节,简化文本处理
  • 提供便捷的逐行读写方法

Reader核心方法

方法 描述
int read() 读取单个字符,返回0-65535的int值,结尾返回-1
int read(char[] cbuf) 读取字符到数组,返回实际读取字符数
int read(char[] cbuf, int off, int len) 读取指定长度到字符数组的指定位置
long skip(long n) 跳过n个字符
void close() 关闭流

Writer核心方法

方法 描述
void write(int c) 写入单个字符
void write(char[] cbuf) 写入字符数组
void write(char[] cbuf, int off, int len) 写入字符数组的指定部分
void write(String str) 写入字符串
void write(String str, int off, int len) 写入字符串的指定部分
void flush() 刷新缓冲区
void close() 关闭流

字符流使用示例:读写文本文件

使用BufferedReader和BufferedWriter读写文本文件:

import java.io.*; public class TextFileExample { public static void main(String[] args) { String inputFile = "source.txt"; String outputFile = "output.txt"; // 读取文本文件 try (BufferedReader reader = new BufferedReader(new FileReader(inputFile)); BufferedWriter writer = new BufferedWriter(new FileWriter(outputFile))) { String line; int lineNumber = 1; // 逐行读取并处理 while ((line = reader.readLine()) != null) { // 添加行号并写入新文件 String outputLine = lineNumber + ": " + line; writer.write(outputLine); writer.newLine(); // 写入换行符 lineNumber++; } System.out.println("文件处理完成!"); } catch (IOException e) { System.err.println("处理文件时出错: " + e.getMessage()); } } }

字符编码提示

在使用字符流时,始终指定字符编码以避免跨平台问题。例如:

// 指定UTF-8编码 Reader reader = new InputStreamReader(new FileInputStream("file.txt"), StandardCharsets.UTF_8);

字节流与字符流的转换

掌握InputStreamReader和OutputStreamWriter的使用

学习目标

理解适配器模式在I/O流中的应用,掌握流转换方法

转换流的工作原理

转换流(InputStreamReader/OutputStreamWriter)充当字节流和字符流之间的桥梁:

字节流 (InputStream)
InputStreamReader
字符流 (Reader)
字符流 (Writer)
OutputStreamWriter
字节流 (OutputStream)

转换流使用示例

将字节流转换为字符流并指定字符编码:

import java.io.*; import java.nio.charset.StandardCharsets; public class StreamConversionExample { public static void main(String[] args) { File file = new File("data.txt"); try (// 创建字节输出流 FileOutputStream fos = new FileOutputStream(file); // 转换为字符输出流并指定UTF-8编码 OutputStreamWriter osw = new OutputStreamWriter(fos, StandardCharsets.UTF_8); BufferedWriter writer = new BufferedWriter(osw)) { // 使用字符流写入文本 writer.write("你好,世界!"); writer.newLine(); writer.write("Hello, World!"); } catch (IOException e) { e.printStackTrace(); } // 读取文件 try (FileInputStream fis = new FileInputStream(file); InputStreamReader isr = new InputStreamReader(fis, StandardCharsets.UTF_8); BufferedReader reader = new BufferedReader(isr)) { System.out.println("文件内容:"); String line; while ((line = reader.readLine()) != null) { System.out.println(line); } } catch (IOException e) { e.printStackTrace(); } } }

设计模式应用

InputStreamReader/OutputStreamWriter是适配器模式的经典实现,它们将字节流接口适配为字符流接口,使不同接口的类可以一起工作。

I/O流使用最佳实践

编写高效、安全的I/O操作代码

学习目标

掌握资源管理、异常处理和性能优化的最佳实践

关键最佳实践

1. 使用try-with-resources

确保资源(如文件流)被正确关闭,即使在发生异常的情况下:

// Java 7+ 自动资源管理 try (InputStream in = new FileInputStream("file.txt")) { // 使用流 } catch (IOException e) { // 异常处理 }

2. 使用缓冲区

包装节点流以提高I/O性能:

// 使用缓冲流 try (BufferedReader reader = new BufferedReader(new FileReader("file.txt"))) { // 高效读取 }

3. 指定字符编码

始终明确指定字符编码,避免平台依赖问题:

Reader reader = new InputStreamReader( new FileInputStream("file.txt"), StandardCharsets.UTF_8);

字节流 vs 字符流选择指南

场景 推荐使用 原因
二进制文件(图片、视频、压缩包) 字节流 直接操作原始字节,不涉及字符编码
文本文件(UTF-8、GBK等编码) 字符流 自动处理字符编码,提供文本处理便捷方法
网络数据传输 字节流 网络协议基于字节传输
逐行处理文本 字符流(BufferedReader) 提供readLine()方法

高级技巧

使用NIO(New I/O)的Files类和Paths类可以简化文件操作:

// 读取文件所有行 List lines = Files.readAllLines(Paths.get("file.txt"));

常见错误与解决方案

识别并解决I/O操作中的典型问题

学习目标

避免常见陷阱,掌握调试I/O问题的方法

常见错误及解决方案

错误1:未关闭资源

症状: 文件句柄泄漏,最终导致"Too many open files"错误

解决方案: 始终使用try-with-resources语句确保流被关闭

错误2:字符编码问题

症状: 读取文本文件时出现乱码

解决方案: 创建Reader/Writer时明确指定字符编码

错误3:忘记调用flush()

症状: 数据未完全写入文件,缓冲区未清空

解决方案: 重要操作后调用flush(),或使用自动刷新的流

文件操作错误处理

public void safeFileCopy(File source, File dest) throws IOException { // 检查源文件是否存在且可读 if (!source.exists() || !source.isFile() || !source.canRead()) { throw new IOException("源文件无效或不可读: " + source.getPath()); } // 检查目标文件目录是否存在 File parentDir = dest.getParentFile(); if (parentDir != null && !parentDir.exists()) { if (!parentDir.mkdirs()) { throw new IOException("无法创建目录: " + parentDir.getPath()); } } // 执行文件复制 try (InputStream in = new FileInputStream(source); OutputStream out = new FileOutputStream(dest)) { byte[] buffer = new byte[8192]; int length; while ((length = in.read(buffer)) > 0) { out.write(buffer, 0, length); } } }

调试技巧

使用Java NIO的Files类进行简单文件操作:

// 复制文件(Java 7+) Files.copy(Paths.get("source.txt"), Paths.get("dest.txt"));

动手练习

巩固字节流与字符流的知识

学习目标

通过实际编码练习掌握I/O流操作

练习1:文件加密解密

使用字节流实现简单的文件加密解密功能:

  • 编写加密方法:读取文件,对每个字节执行异或操作(XOR)
  • 编写解密方法:对加密文件再次执行相同异或操作
  • 密钥使用简单的字节值(如0x55)
public class FileEncryptor { public static void encryptFile(File input, File output, byte key) throws IOException { try (InputStream in = new FileInputStream(input); OutputStream out = new FileOutputStream(output)) { int data; while ((data = in.read()) != -1) { out.write(data ^ key); // 异或加密 } } } // 解密使用相同方法 public static void decryptFile(File input, File output, byte key) throws IOException { encryptFile(input, output, key); // 异或特性:两次异或恢复原数据 } }

练习2:日志文件分析

使用字符流分析日志文件:

  • 读取日志文件(每行包含时间戳、日志级别、消息)
  • 统计每种日志级别(INFO, WARN, ERROR)的出现次数
  • 找出包含特定关键字(如"Exception")的所有行
  • 将结果写入新的分析报告文件

扩展挑战

使用对象序列化(ObjectInputStream/ObjectOutputStream)保存和加载复杂对象结构:

// 保存对象 try (ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("data.obj"))) { oos.writeObject(myObject); } // 加载对象 try (ObjectInputStream ois = new ObjectInputStream(new FileInputStream("data.obj"))) { MyClass obj = (MyClass) ois.readObject(); }