IO基础

一、IO概述

1.1 什么是IO流

想象一下,你要把A水池的水运到B水池。你需要什么?

你需要一根水管

在程序中,IO流就是这根“数据的水管”。

  • I 代表 Input输入。数据从外部(如文件、网络、键盘)“流入”你的程序。就像水从水池A通过水管流向你。
  • O 代表 Output输出。数据从你的程序“流出”到外部(如文件、网络、屏幕)。就像水从你这里通过水管流到水池B。

总结一句话:IO流是程序中用于处理数据输入输出的一个抽象概念,它像一根管道,数据像水一样在管道中顺序流动。

1.2 为什么需要IO流

程序运行在内存中,但数据通常持久化地存储在硬盘上的文件、数据库里,或者来自网络、键盘等外部设备。内存是临时的,断电后数据就消失了。因此,程序必须有一种方式与这些“外部世界”进行数据交换。IO流就提供了这种标准、统一的交换机制。

1.3 IO流分类

IO流可以根据数据单位流动方向两个维度进行分类。

1.3.1 按数据单位分类

  • 字节流:以字节(8位) 为基本单位进行数据传输。

    • 特点:能处理所有类型的数据,包括图片、视频、音频、可执行文件等二进制文件,当然也能处理文本文件。它是“万能”的,但处理文本时可能不够直观。
    • 抽象基类InputStream(字节输入流), OutputStream(字节输出流)。
  • 字符流:以字符(16位,通常是2个字节) 为基本单位进行数据传输。

    • 特点:专门为处理文本数据设计。它能自动处理字符编码(如UTF-8, GBK),避免乱码问题。不能处理图片、视频等非文本二进制文件。
    • 抽象基类Reader(字符输入流), Writer(字符输出流)。

简单选择原则

  • 非文本文件(如图片、压缩包) -> 用字节流
  • 文本文件(如.txt, .java, .xml) -> 用字符流(效率更高,无乱码风险)。

1.3.2 按流动方向分类

  • 输入流(Input Stream):数据从外部源读入到程序。对应 InputStreamReader
  • 输出流(Output Stream):数据从程序写出到外部目标。对应 OutputStreamWriter

1.3.3 IO流的家族(类结构)

字节流(处理所有类型数据)
|
|-- InputStream(抽象类:字节输入流)
|   |-- FileInputStream(从文件读取字节)
|   |-- BufferedInputStream(带缓冲的字节流,提高效率)
|   |-- ObjectInputStream(用于对象序列化)
|   `-- ...
|
|-- OutputStream(抽象类:字节输出流)
    |-- FileOutputStream(向文件写入字节)
    |-- BufferedOutputStream(带缓冲的字节流,提高效率)
    |-- ObjectOutputStream(用于对象反序列化)
    `-- ...

字符流(专门处理文本)
|
|-- Reader(抽象类:字符输入流)
|   |-- InputStreamReader(桥梁:将字节流转换为字符流)
|   |   `-- FileReader(专门用于读取字符文件的便捷类)
|   |-- BufferedReader(带缓冲的字符流,能读一行)
|   `-- ...
|
|-- Writer(抽象类:字符输出流)
    |-- OutputStreamWriter(桥梁:将字符流转换为字节流)
    |   `-- FileWriter(专门用于写入字符文件的便捷类)
    |-- BufferedWriterr(带缓冲的字符流)
    |-- PrintWriter(常用的打印流)
    `-- ...

1.3.4 关键点

  • 所有流都派生自这4个抽象基类:InputStream, OutputStream, Reader, Writer
  • FileReader本质上是 InputStreamReader的子类,而 InputStreamReader需要包裹一个 InputStream。这说明字符流底层最终还是通过字节流与硬件交互,它只是在此基础上增加了字符编码的解码工作。

二、字节输入流(InputStream)

2.1 文件字节输入流

类:FileInputStream

作用:从文件系统中的文件获取输入字节。它是操作文件的最基本、最底层的流。

使用场景:读取任何类型的文件(图片、视频、文本等),尤其是非文本的二进制文件。

示例1:逐个字节读取(低效,仅演示)

// 伪代码:读取一个文件,并打印每个字节的整数值
try (InputStream is = new FileInputStream("test.txt")) {
    int data;
    // read() 每次读取一个字节,返回 int 类型(0-255),文件末尾返回 -1
    while ((data = is.read()) != -1) {
        System.out.print(data + " "); // 打印字节值,如 'A' 会打印 65
    }
} catch (IOException e) {
    e.printStackTrace();
}
// 注意:对于文本文件,这种方法会丢失字符编码信息,效率极低。

示例2:使用缓冲区读取(标准用法,高效)

// 伪代码:使用字节数组作为缓冲区高效读取文件
try (InputStream is = new FileInputStream("photo.jpg")) {
    byte[] buffer = new byte[1024]; // 创建 1KB 的缓冲区
    int bytesRead; // 实际读取到的字节数
    
    // 将数据读入缓冲区,bytesRead 是实际读到的字节数
    while ((bytesRead = is.read(buffer)) != -1) {
        // 处理 buffer 中从 0 到 bytesRead-1 的数据
        // 例如,可以在这里将数据写入另一个输出流(文件复制)
        processData(buffer, bytesRead);
    }
} catch (IOException e) {
    e.printStackTrace();
}

// 模拟处理数据的函数
void processData(byte[] data, int length) {
    System.out.println("读取到 " + length + " 字节数据");
    // 实际应用中可能是:写入输出流、网络发送、图像处理等
}

2.2 缓冲字节输入流

类:BufferedInputStream

作用:为另一个输入流添加缓冲功能。它是装饰器模式的典型应用,本身不直接连接数据源,而是"包装"另一个输入流。

优势:减少实际的物理读取次数,大幅提升IO效率。

示例:包装 FileInputStream 使用

// 伪代码:使用缓冲流读取文件
try (InputStream is = new FileInputStream("large_file.dat");
     InputStream bis = new BufferedInputStream(is)) { // 包装 FileInputStream
    
    byte[] buffer = new byte[1024];
    int bytesRead;
    
    // bis.read() 调用会先尝试从内存缓冲区读取
    // 缓冲区空了才会委托给内部的 FileInputStream 进行一次大的物理读取
    while ((bytesRead = bis.read(buffer)) != -1) {
        processData(buffer, bytesRead);
    }
} catch (IOException e) {
    e.printStackTrace();
}
// 注意:通常设置 8KB 或更大的缓冲区效果更好,默认缓冲区大小通常是 8KB

2.3 数据字节输入流

类:DataInputStream

作用:允许应用程序以与机器无关的方式从底层输入流中读取基本 Java 数据类型(int, float, double, boolean 等)。

使用场景:读取由 DataOutputStream写入的二进制数据。

示例:读取各种数据类型

// 伪代码:读取包含多种数据类型的文件
// 假设文件是由 DataOutputStream 按顺序写入的:int, double, boolean, UTF字符串
try (InputStream is = new FileInputStream("data.bin");
     DataInputStream dis = new DataInputStream(is)) {
    
    int age = dis.readInt();          // 读取 4 字节的 int
    double score = dis.readDouble();  // 读取 8 字节的 double  
    boolean passed = dis.readBoolean(); // 读取 1 字节的 boolean
    String name = dis.readUTF();      // 读取 UTF-8 编码的字符串
    
    System.out.printf("姓名:%s,年龄:%d,分数:%.2f,是否通过:%b%n", 
                     name, age, score, passed);
} catch (IOException e) {
    e.printStackTrace();
}

2.4 对象序列化输入流

类:ObjectInputStream

作用:从输入流中读取 Java 对象(反序列化)。

使用场景:读取之前通过 ObjectOutputStream序列化到文件或网络的对象。

示例:读取序列化的对象

// 伪代码:从文件读取 Person 对象
// 前提:Person 类必须实现 Serializable 接口
class Person implements Serializable {
    private String name;
    private int age;
    // ... 构造方法、getter/setter
}

try (InputStream is = new FileInputStream("person.dat");
     ObjectInputStream ois = new ObjectInputStream(is)) {
    
    // 读取对象,需要强制类型转换
    Person person = (Person) ois.readObject();
    System.out.println("读取到的对象:" + person.getName() + ", " + person.getAge());
    
} catch (IOException | ClassNotFoundException e) {
    e.printStackTrace();
}

2.5. 字节数组输入流

类:ByteArrayInputStream

作用:将内存中的字节数组作为输入源。不需要关闭,不会抛出 IOException。

使用场景:在内存中处理数据,将数据从字节数组"假装"成流来读取。

示例:将字节数组作为流处理

// 伪代码:从字节数组读取数据
byte[] data = {72, 101, 108, 108, 111}; // "Hello" 的 ASCII 码

try (InputStream is = new ByteArrayInputStream(data)) {
    int content;
    while ((content = is.read()) != -1) {
        System.out.print((char) content); // 输出:Hello
    }
} catch (IOException e) {
    e.printStackTrace();
}
// 实际上 ByteArrayInputStream 很少需要手动关闭,这里只是演示标准写法

组合使用示例

// 典型的数据处理管道:文件 -> 缓冲 -> 数据类型解析
try (InputStream fis = new FileInputStream("data.bin");     // 1. 连接文件
     InputStream bis = new BufferedInputStream(fis, 8192);  // 2. 添加缓冲(8KB)
     DataInputStream dis = new DataInputStream(bis)) {       // 3. 添加数据类型解析
    
    // 现在可以高效地读取各种基本数据类型
    while (dis.available() > 0) {
        int id = dis.readInt();
        String name = dis.readUTF();
        double price = dis.readDouble();
        // ... 处理业务逻辑
    }
} catch (IOException e) {
    e.printStackTrace();
}

三、字节输出流(OutputStream)

3.1 文件字节输出流

类:FileOutputStream

作用:将数据写入文件系统中的文件。是写入文件的最基本、最底层的流。

构造参数

  • FileOutputStream(String name)- 覆盖写入
  • FileOutputStream(String name, boolean append)- append=true时追加写入

示例1:逐个字节写入(低效,仅演示)

// 伪代码:将字符串逐个字节写入文件
String text = "Hello, World!";
try (OutputStream os = new FileOutputStream("output.txt")) {
    byte[] bytes = text.getBytes(); // 将字符串转换为字节数组
    
    for (byte b : bytes) {
        os.write(b); // 每次写入一个字节
    }
    os.flush(); // 确保数据被写入磁盘
    System.out.println("文件写入完成!");
} catch (IOException e) {
    e.printStackTrace();
}

示例2:使用字节数组批量写入(标准用法,高效)

// 伪代码:高效写入大量数据
try (OutputStream os = new FileOutputStream("data.bin")) {
    byte[] data = generateLargeData(); // 模拟生成大量数据
    
    // 分批写入,避免内存溢出
    int batchSize = 4096; // 4KB 一批
    for (int i = 0; i < data.length; i += batchSize) {
        int length = Math.min(batchSize, data.length - i);
        os.write(data, i, length); // 写入指定范围的字节
    }
    os.flush(); // 最后刷新一次
} catch (IOException e) {
    e.printStackTrace();
}

// 模拟生成数据
byte[] generateLargeData() {
    byte[] data = new byte[1024 * 1024]; // 1MB 数据
    for (int i = 0; i < data.length; i++) {
        data[i] = (byte) (i % 256);
    }
    return data;
}

示例3:追加写入模式

// 伪代码:向现有文件追加内容,而不是覆盖
try (OutputStream os = new FileOutputStream("log.txt", true)) { // true 表示追加
    String logEntry = "2024-01-20 10:30:00 - User logged in\n";
    os.write(logEntry.getBytes());
    os.flush();
} catch (IOException e) {
    e.printStackTrace();
}

3.2 缓冲字节输出流

类:BufferedOutputStream

作用:为另一个输出流添加缓冲功能。同样是装饰器模式的应用。

优势:减少实际的物理写入次数,大幅提升IO效率。数据先写入内存缓冲区,缓冲区满了或调用 flush()时才真正写入磁盘。

示例:包装 FileOutputStream 使用

// 伪代码:使用缓冲流高效写入文件
try (OutputStream fos = new FileOutputStream("large_file.dat");
     OutputStream bos = new BufferedOutputStream(fos, 8192)) { // 8KB 缓冲区
    
    for (int i = 0; i < 100000; i++) {
        String record = "Record " + i + ": This is some data\n";
        byte[] bytes = record.getBytes();
        bos.write(bytes); // 写入缓冲流,不是立即写磁盘
        
        // 每1000条记录手动刷新一次
        if (i % 1000 == 0) {
            bos.flush(); // 强制将缓冲区数据写入磁盘
        }
    }
    // try-with-resources 会自动调用 bos.flush() 和 close()
} catch (IOException e) {
    e.printStackTrace();
}

3.3 数据字节输出流

类:DataOutputStream

作用:允许应用程序以与机器无关的方式向底层输出流写入基本 Java 数据类型。

使用场景:生成可由 DataInputStream读取的二进制数据文件。

示例:写入各种数据类型

// 伪代码:写入包含多种数据类型的二进制文件
try (OutputStream fos = new FileOutputStream("student.dat");
     DataOutputStream dos = new DataOutputStream(fos)) {
    
    // 按顺序写入不同类型的数据
    dos.writeInt(1001);          // 写入学号(4字节)
    dos.writeUTF("张三");        // 写入姓名(UTF-8编码)
    dos.writeDouble(95.5);       // 写入分数(8字节)
    dos.writeBoolean(true);      // 写入是否及格(1字节)
    dos.writeChar('A');          // 写入等级(2字节)
    
    dos.flush();
    System.out.println("学生数据写入完成!");
} catch (IOException e) {
    e.printStackTrace();
}

3.4 对象序列化输出流

类:ObjectOutputStream

作用:将 Java 对象写入输出流(序列化)。

使用场景:将对象保存到文件或通过网络传输。

示例:序列化对象到文件

// 伪代码:将 Person 对象序列化到文件
class Person implements Serializable {
    private String name;
    private int age;
    private transient String password; // transient 修饰的字段不会被序列化
    
    public Person(String name, int age, String password) {
        this.name = name;
        this.age = age;
        this.password = password;
    }
    // ... getter/setter
}

try (OutputStream fos = new FileOutputStream("person.ser");
     ObjectOutputStream oos = new ObjectOutputStream(fos)) {
    
    Person person = new Person("李四", 25, "secret123");
    oos.writeObject(person); // 序列化对象到文件
    oos.flush();
    System.out.println("对象序列化完成!");
    
} catch (IOException e) {
    e.printStackTrace();
}

3.5 字节打印流

类:PrintStream

作用PrintStreamOutputStream的子类,为其他输出流添加了打印各种数据值的能力。它不会抛出IOException,而是通过checkError()方法检查错误。

特点

  • 自动处理数据类型转换
  • 提供丰富的print()/println()/printf()方法
  • 自动调用flush(),或可配置自动刷新
  • 不会抛出IOException,需要主动检查错误

示例1:基本使用

// 伪代码:使用PrintStream的各种打印方法
try (OutputStream fos = new FileOutputStream("output.txt");
     PrintStream ps = new PrintStream(fos)) {
    
    // 各种数据类型的打印方法
    ps.print("字符串: ");
    ps.println("Hello World"); // println自动换行
    
    ps.print("整数: ");
    ps.println(100);
    
    ps.print("浮点数: ");
    ps.println(3.14159);
    
    ps.print("布尔值: ");
    ps.println(true);
    
    ps.print("字符: ");
    ps.println('A');
    
    // 格式化输出
    ps.printf("姓名: %s, 年龄: %d, 成绩: %.2f%n", "张三", 20, 95.5);
    
    // 对象输出(调用toString方法)
    ps.print("当前时间: ");
    ps.println(java.time.LocalDateTime.now());
    
    // 检查是否有错误发生
    if (ps.checkError()) {
        System.err.println("写入过程中发生错误");
    }
    
} catch (IOException e) {
    e.printStackTrace();
}

示例2:自动刷新模式

// 伪代码:启用自动刷新(每次println后自动flush)
try (OutputStream fos = new FileOutputStream("log.txt");
     PrintStream ps = new PrintStream(fos, true)) { // true表示自动刷新
    
    for (int i = 1; i <= 5; i++) {
        ps.println("日志条目 " + i + " - " + java.time.LocalTime.now());
        // 每次println后自动调用flush(),确保日志立即写入
        Thread.sleep(1000); // 模拟耗时操作
    }
    
} catch (Exception e) {
    e.printStackTrace();
}

示例3:指定字符编码

// 伪代码:使用指定编码创建PrintStream
try (OutputStream fos = new FileOutputStream("utf8_output.txt");
     PrintStream ps = new PrintStream(fos, true, "UTF-8")) { // 指定UTF-8编码
    
    ps.println("UTF-8编码测试: Hello 世界!🎉");
    ps.println("中文内容正常显示");
    ps.printf("格式化输出: %s - %.2f%n", "测试", 3.14);
    
} catch (IOException e) {
    e.printStackTrace();
}

示例4:系统输出的重定向

// 伪代码:将System.out重定向到文件
try (OutputStream fos = new FileOutputStream("console_output.txt");
     PrintStream filePrintStream = new PrintStream(fos)) {
    
    // 保存原来的System.out
    PrintStream originalOut = System.out;
    
    // 重定向System.out到文件
    System.setOut(filePrintStream);
    
    // 这些输出将写入文件而不是控制台
    System.out.println("=== 程序开始执行 ===");
    System.out.println("当前时间: " + java.time.LocalDateTime.now());
    
    // 执行一些操作
    for (int i = 0; i < 3; i++) {
        System.out.println("迭代 " + i + " 完成");
    }
    
    System.out.println("=== 程序执行结束 ===");
    
    // 恢复原来的System.out
    System.setOut(originalOut);
    System.out.println("输出已重定向到文件,这条消息显示在控制台");
    
} catch (IOException e) {
    e.printStackTrace();
}

示例5:错误处理

// 伪代码:PrintStream的错误处理
try (OutputStream fos = new FileOutputStream("test.txt");
     PrintStream ps = new PrintStream(fos)) {
    
    // 正常写入
    ps.println("第一行内容");
    
    // 模拟一些操作...
    
    ps.println("另一行内容");
    
    // 主动检查错误
    if (ps.checkError()) {
        System.err.println("写入过程中发生I/O错误");
        // 可以在这里进行错误恢复操作
    }
    
    // 继续写入
    ps.println("最后一行内容");
    
} catch (IOException e) {
    // 注意:PrintStream的构造函数可能抛出IOException
    e.printStackTrace();
}

3.6 字节数组输出流

类:ByteArrayOutputStream

作用:将数据写入内存中的字节数组。不需要关闭,不会抛出 IOException。

使用场景:在内存中构建数据,最后一次性获取完整的字节数组。

示例:在内存中构建数据

// 伪代码:使用字节数组流在内存中构建数据
try (ByteArrayOutputStream baos = new ByteArrayOutputStream()) {
    // 写入各种数据
    baos.write("Header: ".getBytes());
    baos.write(65); // 写入字节 'A'
    baos.write("\nData: ".getBytes());
    
    byte[] data = {1, 2, 3, 4, 5};
    baos.write(data);
    
    // 获取完整的字节数组
    byte[] result = baos.toByteArray();
    System.out.println("生成的数据长度: " + result.length);
    
    // 或者转换为字符串查看
    String content = baos.toString("UTF-8");
    System.out.println("内容: " + content);
} catch (IOException e) {
    e.printStackTrace();
}

四、字符输入流(Reader)

4.1 文件字符输入流

类:FileReader

作用:用于读取字符文件的便捷类。继承自 InputStreamReader,使用平台默认编码

使用场景:读取文本文件,如 .txt、.java、.xml 等。

示例1:逐个字符读取(低效,仅演示)

// 伪代码:逐个字符读取文本文件
try (Reader reader = new FileReader("test.txt")) {
    int charValue;
    // read() 每次读取一个字符(2字节)
    while ((charValue = reader.read()) != -1) {
        char ch = (char) charValue; // 转换为 char 类型
        System.out.print(ch); // 正常显示文本内容,不会乱码
    }
} catch (IOException e) {
    e.printStackTrace();
}
// 注意:FileReader 使用平台默认编码,可能在不同系统上表现不同

示例2:使用字符数组缓冲区读取(标准用法)

// 伪代码:高效读取文本文件
try (Reader reader = new FileReader("novel.txt")) {
    char[] buffer = new char[1024]; // 字符缓冲区
    int charsRead;
    StringBuilder content = new StringBuilder();
    
    while ((charsRead = reader.read(buffer)) != -1) {
        // 将读取的字符追加到 StringBuilder
        content.append(buffer, 0, charsRead);
    }
    
    System.out.println("文件内容长度: " + content.length() + " 字符");
    System.out.println("前100个字符: " + content.substring(0, Math.min(100, content.length())));
} catch (IOException e) {
    e.printStackTrace();
}

4.2 缓冲字符输入流

类:BufferedReader

作用:为其他字符输入流添加缓冲功能,特别是提供了 readLine()方法,可以方便地按行读取。

优势:提高读取效率,提供方便的按行读取功能。

示例1:使用 readLine() 按行读取

// 伪代码:按行读取文本文件(最常用的文本处理方式)
try (Reader fr = new FileReader("log.txt");
     BufferedReader br = new BufferedReader(fr)) {
    
    String line;
    int lineNumber = 1;
    
    // readLine() 读取一行内容(不包含换行符),末尾返回 null
    while ((line = br.readLine()) != null) {
        System.out.println("第" + lineNumber + "行: " + line);
        lineNumber++;
        
        // 可以在这里进行行级处理,如解析CSV、分析日志等
        if (line.contains("ERROR")) {
            System.out.println("发现错误日志: " + line);
        }
    }
} catch (IOException e) {
    e.printStackTrace();
}

示例2:使用更大的缓冲区

// 伪代码:处理大文本文件时使用更大的缓冲区
try (Reader fr = new FileReader("large_text.txt");
     BufferedReader br = new BufferedReader(fr, 16384)) { // 16KB 缓冲区
    
    String line;
    while ((line = br.readLine()) != null) {
        processLine(line); // 处理每一行
    }
} catch (IOException e) {
    e.printStackTrace();
}

void processLine(String line) {
    // 模拟行处理逻辑
    if (!line.trim().isEmpty()) {
        System.out.println("处理: " + line);
    }
}

4.3 字符编码转换流

类:InputStreamReader

作用字节流通向字符流的桥梁。它将字节流转换为字符流,并可以指定字符编码。

使用场景:当需要明确指定文件编码时,或者需要将网络字节流转换为字符流时。

示例:指定UTF-8编码读取文件

// 伪代码:明确指定字符编码读取文件(避免乱码的最佳实践)
try (InputStream is = new FileInputStream("utf8_file.txt");
     Reader isr = new InputStreamReader(is, "UTF-8"); // 明确指定UTF-8编码
     BufferedReader br = new BufferedReader(isr)) {
    
    String line;
    while ((line = br.readLine()) != null) {
        System.out.println(line); // 正确显示中文字符
    }
} catch (IOException e) {
    e.printStackTrace();
}

注意FileReader实际上是 InputStreamReader的便捷类:

// 这两行代码基本等价:
Reader reader1 = new FileReader("file.txt");
Reader reader2 = new InputStreamReader(new FileInputStream("file.txt"));
// 但 FileReader 不能指定编码,而 InputStreamReader 可以

4.4 字符串字符输入流

类:StringReader

作用:将字符串作为字符输入源。

使用场景:在内存中处理字符串数据,将字符串"假装"成流来读取。

示例:从字符串读取

// 伪代码:将字符串作为流处理
String text = "Hello 世界!\n这是第二行。\n这是第三行。";

try (Reader reader = new StringReader(text);
     BufferedReader br = new BufferedReader(reader)) {
    
    String line;
    while ((line = br.readLine()) != null) {
        System.out.println("读取到: " + line);
    }
} catch (IOException e) {
    e.printStackTrace();
}

4.5 字符数组输入流

类:CharArrayReader

作用:将字符数组作为字符输入源。

使用场景:在内存中处理字符数组数据。

示例:从字符数组读取

// 伪代码:从字符数组读取
char[] charArray = {'H', 'e', 'l', 'l', 'o', ' ', '世', '界', '!'};

try (Reader reader = new CharArrayReader(charArray)) {
    int charValue;
    while ((charValue = reader.read()) != -1) {
        System.out.print((char) charValue);
    }
} catch (IOException e) {
    e.printStackTrace();
}
// 输出: Hello 世界!

五、字符输出流(Writer)

5.1 文件字符输出流

类:FileWriter

作用:用于写入字符文件的便捷类。继承自 OutputStreamWriter,使用平台默认编码

构造参数

  • FileWriter(String fileName)- 覆盖写入
  • FileWriter(String fileName, boolean append)- 追加写入

示例1:基本文件写入

// 伪代码:向文件写入文本内容
try (Writer writer = new FileWriter("output.txt")) {
    writer.write("Hello, World!\n");
    writer.write("这是第二行内容。\n");
    
    char[] chars = {'字', '符', '数', '组'};
    writer.write(chars); // 写入字符数组
    writer.write("\n");
    
    writer.write("字符串部分写入示例", 0, 3); // 只写入前3个字符
    writer.flush(); // 确保数据写入磁盘
    
    System.out.println("文件写入完成!");
} catch (IOException e) {
    e.printStackTrace();
}

示例2:追加模式写入日志

// 伪代码:以追加模式写入日志文件
try (Writer writer = new FileWriter("app.log", true)) { // true 表示追加模式
    String timestamp = java.time.LocalDateTime.now().toString();
    String logEntry = "[" + timestamp + "] INFO - 用户登录成功\n";
    
    writer.write(logEntry);
    writer.flush(); // 重要:日志需要立即持久化
} catch (IOException e) {
    e.printStackTrace();
}

5.2 缓冲字符输出流

类:BufferedWriter

作用:为其他字符输出流添加缓冲功能,提供 newLine()方法写入换行符。

优势:提高写入效率,提供方便的换行处理。

示例1:高效写入大量文本

// 伪代码:使用缓冲流高效写入文本
try (Writer fw = new FileWriter("large_file.txt");
     BufferedWriter bw = new BufferedWriter(fw, 8192)) { // 8KB 缓冲区
    
    for (int i = 1; i <= 10000; i++) {
        bw.write("这是第 " + i + " 行数据。");
        bw.newLine(); // 写入平台相关的换行符,比写 "\n" 更规范
        
        // 每1000行手动刷新一次
        if (i % 1000 == 0) {
            bw.flush();
            System.out.println("已写入 " + i + " 行");
        }
    }
    // 自动调用 flush() 和 close()
} catch (IOException e) {
    e.printStackTrace();
}

示例2:生成CSV文件

// 伪代码:生成CSV格式的文件
try (Writer fw = new FileWriter("employees.csv");
     BufferedWriter bw = new BufferedWriter(fw)) {
    
    // 写入标题行
    bw.write("姓名,年龄,工资,部门");
    bw.newLine();
    
    // 写入数据行
    writeEmployeeRecord(bw, "张三", 25, 8000.0, "技术部");
    writeEmployeeRecord(bw, "李四", 30, 12000.5, "销售部");
    writeEmployeeRecord(bw, "王五", 28, 9500.0, "技术部");
    
    bw.flush();
    System.out.println("CSV文件生成完成!");
} catch (IOException e) {
    e.printStackTrace();
}

void writeEmployeeRecord(BufferedWriter bw, String name, int age, 
                        double salary, String department) throws IOException {
    bw.write(name + "," + age + "," + salary + "," + department);
    bw.newLine();
}

5.3 字符编码转换输出流

类:OutputStreamWriter

作用字符流通向字节流的桥梁。它将字符流转换为字节流,并可以指定字符编码。

使用场景:当需要明确指定文件编码时,或者需要将字符流转换为网络字节流时。

示例:指定UTF-8编码写入文件

// 伪代码:明确指定字符编码写入文件(避免乱码的最佳实践)
try (OutputStream os = new FileOutputStream("utf8_output.txt");
     Writer osw = new OutputStreamWriter(os, "UTF-8"); // 明确指定UTF-8编码
     BufferedWriter bw = new BufferedWriter(osw)) {
    
    bw.write("UTF-8编码测试:Hello 世界!🎉");
    bw.newLine();
    bw.write("这是第二行内容,确保中文正常显示。");
    
    bw.flush();
    System.out.println("UTF-8编码文件写入完成!");
} catch (IOException e) {
    e.printStackTrace();
}

注意FileWriter实际上是 OutputStreamWriter的便捷类:

// 这两行代码基本等价:
Writer writer1 = new FileWriter("file.txt");
Writer writer2 = new OutputStreamWriter(new FileOutputStream("file.txt"));
// 但 FileWriter 不能指定编码,而 OutputStreamWriter 可以

5.4 打印字符输出流

类:PrintWriter

作用:提供丰富的打印方法,可以方便地输出各种数据类型。

优势:自动处理数据类型转换,支持格式化的输出。

示例:使用PrintWriter的便利方法

// 伪代码:使用PrintWriter的丰富输出方法
try (Writer fw = new FileWriter("data.txt");
     PrintWriter pw = new PrintWriter(fw)) {
    
    // 各种便利的输出方法
    pw.print("普通字符串:");
    pw.println("Hello World"); // println 自动换行
    
    pw.print("整数:");
    pw.println(100);
    
    pw.print("浮点数:");
    pw.println(3.14159);
    
    pw.print("布尔值:");
    pw.println(true);
    
    // 格式化输出
    pw.printf("姓名:%s,年龄:%d,工资:%.2f%n", "张三", 25, 8500.50);
    
    // 对象输出(调用对象的toString方法)
    pw.print("当前时间:");
    pw.println(java.time.LocalDateTime.now());
    
    pw.flush();
    System.out.println("数据写入完成!");
} catch (IOException e) {
    e.printStackTrace();
}

5.5 字符串字符输出流

类:StringWriter

作用:将输出写入到字符串缓冲区中。

使用场景:在内存中构建字符串内容,最后一次性获取完整的字符串。

示例:在内存中构建字符串

// 伪代码:使用StringWriter在内存中构建复杂字符串
try (StringWriter sw = new StringWriter();
     PrintWriter pw = new PrintWriter(sw)) {
    
    // 构建复杂的字符串内容
    pw.println("=== 系统报告 ===");
    pw.printf("生成时间:%s%n", java.time.LocalDateTime.now());
    pw.println();
    
    pw.println("统计信息:");
    pw.printf("用户数量:%d%n", 1500);
    pw.printf("订单数量:%d%n", 28470);
    pw.printf("总金额:%.2f%n", 18456789.50);
    
    pw.println();
    pw.println("=== 报告结束 ===");
    
    pw.flush();
    
    // 获取完整的字符串内容
    String report = sw.toString();
    System.out.println("生成的报告:");
    System.out.println(report);
    
    // 也可以将内容写入文件
    try (Writer fw = new FileWriter("report.txt")) {
        fw.write(report);
    }
} catch (IOException e) {
    e.printStackTrace();
}

5.6 字符数组输出流

类:CharArrayWriter

作用:将输出写入到字符数组缓冲区中。

使用场景:在内存中处理字符数组数据。

示例:写入字符数组

// 伪代码:使用CharArrayWriter构建字符数组
try (CharArrayWriter caw = new CharArrayWriter()) {
    caw.write("Hello ");
    caw.write(new char[]{'W', 'o', 'r', 'l', 'd'});
    caw.write("! 测试中文。");
    
    // 获取字符数组
    char[] charArray = caw.toCharArray();
    System.out.println("生成的字符数组: " + new String(charArray));
    
    // 或者获取字符串
    String content = caw.toString();
    System.out.println("字符串内容: " + content);
} catch (IOException e) {
    e.printStackTrace();
}
posted @ 2025-11-10 18:48  蓝眼琪莎拉  阅读(1)  评论(0)    收藏  举报