Java I/O流、字符流与序列化详解
一、字节流 (Byte Streams)
1.1 基本概念
字节流用于处理二进制数据,以字节为单位进行读写操作。
1.2 主要类结构
InputStream (抽象类)
├── FileInputStream
├── ByteArrayInputStream
├── BufferedInputStream
├── ObjectInputStream
└── ...
OutputStream (抽象类)
├── FileOutputStream
├── ByteArrayOutputStream
├── BufferedOutputStream
├── ObjectOutputStream
└── ...
1.3 文件复制示例
import java.io.*;
public class ByteStreamCopy {
/**
* 基础字节流复制文件
*/
public static void basicCopy(String sourcePath, String targetPath) {
FileInputStream fis = null;
FileOutputStream fos = null;
try {
fis = new FileInputStream(sourcePath);
fos = new FileOutputStream(targetPath);
byte[] buffer = new byte[1024];
int bytesRead;
while ((bytesRead = fis.read(buffer)) != -1) {
fos.write(buffer, 0, bytesRead);
}
System.out.println("文件复制完成");
} catch (IOException e) {
e.printStackTrace();
} finally {
// 关闭资源
try {
if (fis != null) fis.close();
if (fos != null) fos.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
/**
* 使用try-with-resources自动关闭资源 (Java 7+)
*/
public static void autoCloseCopy(String sourcePath, String targetPath) {
try (FileInputStream fis = new FileInputStream(sourcePath);
FileOutputStream fos = new FileOutputStream(targetPath)) {
byte[] buffer = new byte[4096]; // 更大的缓冲区提高效率
int bytesRead;
while ((bytesRead = fis.read(buffer)) != -1) {
fos.write(buffer, 0, bytesRead);
}
System.out.println("文件复制完成");
} catch (IOException e) {
e.printStackTrace();
}
}
/**
* 使用缓冲流提高性能
*/
public static void bufferedCopy(String sourcePath, String targetPath) {
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);
}
// 刷新缓冲区,确保所有数据都写入
bos.flush();
System.out.println("缓冲流复制完成");
} catch (IOException e) {
e.printStackTrace();
}
}
}
二、字符流 (Character Streams)
2.1 基本概念
字符流用于处理文本数据,以字符为单位进行读写操作,会自动处理字符编码。
2.2 主要类结构
Reader (抽象类)
├── InputStreamReader
│ └── FileReader
├── BufferedReader
├── StringReader
└── ...
Writer (抽象类)
├── OutputStreamWriter
│ └── FileWriter
├── BufferedWriter
├── PrintWriter
├── StringWriter
└── ...
2.3 字符编码重要概念
public class EncodingExample {
public static void main(String[] args) {
// 获取系统默认编码
String defaultEncoding = System.getProperty("file.encoding");
System.out.println("系统默认编码: " + defaultEncoding);
// 常见编码格式
String[] encodings = {"UTF-8", "GBK", "ISO-8859-1", "UTF-16"};
}
}
2.4 文本文件复制示例
import java.io.*;
import java.nio.charset.StandardCharsets;
public class CharacterStreamCopy {
/**
* 基础字符流复制文本文件
*/
public static void basicCharCopy(String sourcePath, String targetPath) {
try (FileReader reader = new FileReader(sourcePath);
FileWriter writer = new FileWriter(targetPath)) {
char[] buffer = new char[1024];
int charsRead;
while ((charsRead = reader.read(buffer)) != -1) {
writer.write(buffer, 0, charsRead);
}
System.out.println("文本文件复制完成");
} catch (IOException e) {
e.printStackTrace();
}
}
/**
* 指定编码的字符流复制
*/
public static void encodingCopy(String sourcePath, String targetPath, String encoding) {
try (InputStreamReader reader = new InputStreamReader(
new FileInputStream(sourcePath), encoding);
OutputStreamWriter writer = new OutputStreamWriter(
new FileOutputStream(targetPath), encoding)) {
char[] buffer = new char[1024];
int charsRead;
while ((charsRead = reader.read(buffer)) != -1) {
writer.write(buffer, 0, charsRead);
}
System.out.println("使用编码 " + encoding + " 复制完成");
} catch (IOException e) {
e.printStackTrace();
}
}
/**
* 使用缓冲字符流提高性能
*/
public static void bufferedCharCopy(String sourcePath, String targetPath) {
try (BufferedReader reader = new BufferedReader(new FileReader(sourcePath));
BufferedWriter writer = new BufferedWriter(new FileWriter(targetPath))) {
String line;
// 逐行读取,适合处理文本文件
while ((line = reader.readLine()) != null) {
writer.write(line);
writer.newLine(); // 写入换行符
}
writer.flush();
System.out.println("缓冲字符流复制完成");
} catch (IOException e) {
e.printStackTrace();
}
}
/**
* 使用PrintWriter进行格式化输出
*/
public static void printWriterExample(String filePath) {
try (PrintWriter writer = new PrintWriter(new FileWriter(filePath))) {
writer.println("=== 学生信息 ===");
writer.printf("姓名: %s%n", "张三");
writer.printf("年龄: %d%n", 20);
writer.printf("成绩: %.2f%n", 95.5);
writer.println("==============");
System.out.println("格式化写入完成");
} catch (IOException e) {
e.printStackTrace();
}
}
}
三、序列化 (Serialization)
3.1 序列化概念
- 序列化: 将对象转换为字节序列的过程
- 反序列化: 将字节序列恢复为对象的过程
- 用途: 网络传输、持久化存储、分布式计算
3.2 序列化接口
import java.io.Serializable;
import java.util.Date;
/**
* 可序列化的学生类
* 注意事项:
* 1. 必须实现Serializable接口(标记接口)
* 2. 建议显示声明serialVersionUID
* 3. 瞬态字段(transient)不会被序列化
* 4. 静态字段不会被序列化
*/
public class Student implements Serializable {
// 序列化版本ID,用于版本控制
private static final long serialVersionUID = 1L;
private String name;
private int age;
private transient String password; // transient字段不会被序列化
private static String school = "某大学"; // 静态字段不会被序列化
private Date birthDate;
public Student(String name, int age, String password, Date birthDate) {
this.name = name;
this.age = age;
this.password = password;
this.birthDate = birthDate;
}
// getter和setter方法
public String getName() { return name; }
public void setName(String name) { this.name = name; }
public int getAge() { return age; }
public void setAge(int age) { this.age = age; }
public String getPassword() { return password; }
public void setPassword(String password) { this.password = password; }
public Date getBirthDate() { return birthDate; }
public void setBirthDate(Date birthDate) { this.birthDate = birthDate; }
@Override
public String toString() {
return "Student{" +
"name='" + name + '\'' +
", age=" + age +
", password='" + password + '\'' +
", school='" + school + '\'' +
", birthDate=" + birthDate +
'}';
}
}
3.3 序列化操作示例
import java.io.*;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
public class SerializationExample {
/**
* 序列化单个对象到文件
*/
public static void serializeSingleObject(String filePath) {
Student student = new Student("李四", 22, "123456", new Date());
try (ObjectOutputStream oos = new ObjectOutputStream(
new FileOutputStream(filePath))) {
oos.writeObject(student);
System.out.println("单个对象序列化完成: " + student);
} catch (IOException e) {
e.printStackTrace();
}
}
/**
* 反序列化单个对象
*/
public static void deserializeSingleObject(String filePath) {
try (ObjectInputStream ois = new ObjectInputStream(
new FileInputStream(filePath))) {
Student student = (Student) ois.readObject();
System.out.println("反序列化结果: " + student);
// 注意: password字段为null,因为是transient的
// school字段显示默认值,因为是静态的
} catch (IOException | ClassNotFoundException e) {
e.printStackTrace();
}
}
/**
* 序列化对象集合
*/
public static void serializeCollection(String filePath) {
List<Student> students = new ArrayList<>();
students.add(new Student("王五", 21, "pass1", new Date()));
students.add(new Student("赵六", 23, "pass2", new Date()));
students.add(new Student("钱七", 20, "pass3", new Date()));
try (ObjectOutputStream oos = new ObjectOutputStream(
new FileOutputStream(filePath))) {
oos.writeObject(students);
System.out.println("对象集合序列化完成,共" + students.size() + "个对象");
} catch (IOException e) {
e.printStackTrace();
}
}
/**
* 反序列化对象集合
*/
public static void deserializeCollection(String filePath) {
try (ObjectInputStream ois = new ObjectInputStream(
new FileInputStream(filePath))) {
@SuppressWarnings("unchecked")
List<Student> students = (List<Student>) ois.readObject();
System.out.println("反序列化对象集合:");
for (Student student : students) {
System.out.println(" " + student);
}
} catch (IOException | ClassNotFoundException e) {
e.printStackTrace();
}
}
/**
* 自定义序列化控制
*/
public static class CustomSerializableStudent extends Student {
private String customField;
public CustomSerializableStudent(String name, int age, String password,
Date birthDate, String customField) {
super(name, age, password, birthDate);
this.customField = customField;
}
// 自定义序列化逻辑
private void writeObject(ObjectOutputStream oos) throws IOException {
oos.defaultWriteObject(); // 默认序列化
oos.writeUTF(customField.toUpperCase()); // 自定义处理
}
// 自定义反序列化逻辑
private void readObject(ObjectInputStream ois) throws IOException, ClassNotFoundException {
ois.defaultReadObject(); // 默认反序列化
this.customField = ois.readUTF().toLowerCase(); // 自定义处理
}
}
}
3.4 序列化注意事项
public class SerializationNotes {
/**
* 序列化重要注意事项
*/
public static void importantNotes() {
// 1. 序列化版本控制
System.out.println("=== 序列化注意事项 ===");
// 2. 安全考虑
System.out.println("1. 序列化可能带来安全风险,不要序列化敏感信息");
System.out.println("2. 使用transient关键字排除敏感字段");
System.out.println("3. 考虑使用加密或自定义序列化保护数据");
// 3. 性能考虑
System.out.println("4. 序列化可能比文本格式更高效");
System.out.println("5. 但序列化格式是Java特定的,缺乏通用性");
// 4. 替代方案
System.out.println("6. 考虑使用JSON、XML等跨语言格式");
System.out.println("7. 或使用Protocol Buffers、Avro等高效序列化库");
}
/**
* 验证serialVersionUID的作用
*/
public static void verifySerialVersionUID() {
System.out.println("\n=== serialVersionUID验证 ===");
System.out.println("1. 如果不显式声明serialVersionUID,JVM会自动生成");
System.out.println("2. 自动生成的ID对类结构变化敏感");
System.out.println("3. 显式声明可以保持版本兼容性");
System.out.println("4. 类结构变化时可能需要更新serialVersionUID");
}
}
四、综合应用示例
4.1 文件工具类
import java.io.*;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.zip.GZIPInputStream;
import java.util.zip.GZIPOutputStream;
public class FileUtils {
/**
* 通用文件复制方法
*/
public static boolean copyFile(String sourcePath, String targetPath, boolean useBuffer) {
File sourceFile = new File(sourcePath);
File targetFile = new File(targetPath);
// 检查源文件
if (!sourceFile.exists()) {
System.out.println("源文件不存在: " + sourcePath);
return false;
}
// 创建目标目录
targetFile.getParentFile().mkdirs();
if (useBuffer) {
// 使用缓冲流
return bufferedCopy(sourcePath, targetPath);
} else {
// 使用普通流
return basicCopy(sourcePath, targetPath);
}
}
private static boolean basicCopy(String sourcePath, String targetPath) {
try (FileInputStream fis = new FileInputStream(sourcePath);
FileOutputStream fos = new FileOutputStream(targetPath)) {
byte[] buffer = new byte[8192];
int bytesRead;
while ((bytesRead = fis.read(buffer)) != -1) {
fos.write(buffer, 0, bytesRead);
}
return true;
} catch (IOException e) {
e.printStackTrace();
return false;
}
}
private static boolean bufferedCopy(String sourcePath, String targetPath) {
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);
}
bos.flush();
return true;
} catch (IOException e) {
e.printStackTrace();
return false;
}
}
/**
* 压缩并序列化对象
*/
public static void compressAndSerialize(Object obj, String filePath) {
try (ObjectOutputStream oos = new ObjectOutputStream(
new GZIPOutputStream(new FileOutputStream(filePath)))) {
oos.writeObject(obj);
System.out.println("对象压缩并序列化完成");
} catch (IOException e) {
e.printStackTrace();
}
}
/**
* 解压并反序列化对象
*/
public static Object decompressAndDeserialize(String filePath) {
try (ObjectInputStream ois = new ObjectInputStream(
new GZIPInputStream(new FileInputStream(filePath)))) {
return ois.readObject();
} catch (IOException | ClassNotFoundException e) {
e.printStackTrace();
return null;
}
}
}
4.2 测试类
public class IOTest {
public static void main(String[] args) {
// 测试字节流复制
System.out.println("=== 字节流复制测试 ===");
ByteStreamCopy.bufferedCopy("source.txt", "target_byte.txt");
// 测试字符流复制
System.out.println("\n=== 字符流复制测试 ===");
CharacterStreamCopy.bufferedCharCopy("source.txt", "target_char.txt");
// 测试序列化
System.out.println("\n=== 序列化测试 ===");
SerializationExample.serializeSingleObject("student.ser");
SerializationExample.deserializeSingleObject("student.ser");
// 测试集合序列化
System.out.println("\n=== 集合序列化测试 ===");
SerializationExample.serializeCollection("students.ser");
SerializationExample.deserializeCollection("students.ser");
// 显示注意事项
SerializationNotes.importantNotes();
SerializationNotes.verifySerialVersionUID();
}
}
五、总结笔记
5.1 核心要点
字节流 vs 字符流:
- 字节流: 处理所有类型文件,如图片、视频、文本
- 字符流: 专门处理文本文件,自动处理编码
序列化要点:
- 实现Serializable接口
- 显示声明serialVersionUID
- transient字段不参与序列化
- 静态字段不参与序列化
5.2 最佳实践
- 资源管理: 使用try-with-resources自动关闭流
- 缓冲使用: 大文件使用缓冲流提高性能
- 编码指定: 文本处理时明确指定字符编码
- 异常处理: 妥善处理IOException
- 版本控制: 序列化类要管理好serialVersionUID
5.3 性能考虑
- 小文件: 普通流即可
- 大文件: 使用缓冲流
- 文本处理: 考虑使用字符流
- 对象传输: 序列化比文本格式更高效
这份笔记涵盖了Java I/O的核心概念,建议结合实际代码练习来加深理解。