小熊奶糖(BearCandy)
小熊奶糖(BearCandy)
发布于 2025-10-14 / 0 阅读
0
0

Java I/O流、字符流与序列化详解

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 字符流:

  • 字节流: 处理所有类型文件,如图片、视频、文本
  • 字符流: 专门处理文本文件,自动处理编码

序列化要点:

  1. 实现Serializable接口
  2. 显示声明serialVersionUID
  3. transient字段不参与序列化
  4. 静态字段不参与序列化

5.2 最佳实践

  1. 资源管理: 使用try-with-resources自动关闭流
  2. 缓冲使用: 大文件使用缓冲流提高性能
  3. 编码指定: 文本处理时明确指定字符编码
  4. 异常处理: 妥善处理IOException
  5. 版本控制: 序列化类要管理好serialVersionUID

5.3 性能考虑

  • 小文件: 普通流即可
  • 大文件: 使用缓冲流
  • 文本处理: 考虑使用字符流
  • 对象传输: 序列化比文本格式更高效

这份笔记涵盖了Java I/O的核心概念,建议结合实际代码练习来加深理解。


评论