第59章

Java字符流

深入学习Java字符流的使用方法,掌握Reader和Writer类及其子类的特点和应用场景

学习目标

字符流概述

什么是字符流?

字符流是Java I/O系统中专门用于处理字符数据的流。与字节流不同,字符流以字符为单位进行读写操作,能够自动处理字符编码转换,特别适合处理文本文件和字符串数据。

字符流 vs 字节流

特性 字符流 字节流
处理单位 字符(char) 字节(byte)
适用场景 文本文件 所有文件类型
编码处理 自动处理 需要手动处理
中文支持 原生支持 需要编码转换
性能 文本处理更高效 二进制数据更高效

主要字符流类型

FileReader/FileWriter

  • 直接操作文件的字符流
  • 使用系统默认编码
  • 适合简单的文本文件操作
  • 自动处理字符编码转换

BufferedReader/BufferedWriter

  • 带缓冲区的字符流
  • 提供readLine()方法
  • 显著提高I/O性能
  • 减少系统调用次数

InputStreamReader/OutputStreamWriter

  • 字节流到字符流的桥梁
  • 可以指定字符编码
  • 处理不同编码的文件
  • 国际化应用的基础

PrintWriter

  • 提供便捷的打印方法
  • 支持格式化输出
  • 类似System.out的使用方式
  • 自动刷新功能

FileReader和FileWriter基础

FileReader - 文件读取

FileReader基本用法
import java.io.*;

public class FileReaderExample {
    public static void main(String[] args) {
        // 使用try-with-resources自动关闭流
        try (FileReader reader = new FileReader("test.txt")) {
            int character;
            System.out.print("文件内容: ");
            
            // 逐个字符读取
            while ((character = reader.read()) != -1) {
                System.out.print((char) character);
            }
            System.out.println();
            
        } catch (FileNotFoundException e) {
            System.out.println("文件未找到: " + e.getMessage());
        } catch (IOException e) {
            System.out.println("读取文件时发生错误: " + e.getMessage());
        }
    }
}

FileWriter - 文件写入

FileWriter基本用法
import java.io.*;

public class FileWriterExample {
    public static void main(String[] args) {
        try (FileWriter writer = new FileWriter("output.txt")) {
            String content = "Hello, 这是字符流写入的内容!\n";
            content += "支持中文字符的写入。\n";
            content += "FileWriter会自动处理字符编码。";
            
            writer.write(content);
            System.out.println("内容已成功写入到 output.txt 文件");
            
        } catch (IOException e) {
            System.out.println("写入文件时发生错误: " + e.getMessage());
        }
    }
}

缓冲字符流 - 提升性能

缓冲流的优势

缓冲字符流通过内置缓冲区减少系统调用次数,显著提高I/O性能。BufferedReader还提供了readLine()方法,可以方便地按行读取文件内容。

BufferedReader - 按行读取

BufferedReader逐行读取示例
import java.io.*;

public class BufferedReaderExample {
    public static void main(String[] args) {
        try (BufferedReader reader = new BufferedReader(new FileReader("poem.txt"))) {
            String line;
            int lineNumber = 1;
            
            System.out.println("逐行读取文件内容:");
            while ((line = reader.readLine()) != null) {
                System.out.println(lineNumber + ": " + line);
                lineNumber++;
            }
            
        } catch (IOException e) {
            System.out.println("读取文件时发生错误: " + e.getMessage());
        }
    }
}

BufferedWriter - 高效写入

BufferedWriter高效写入示例
import java.io.*;

public class BufferedWriterExample {
    public static void main(String[] args) {
        try (BufferedWriter writer = new BufferedWriter(new FileWriter("log.txt"))) {
            // 写入多行日志
            writer.write("[INFO] 应用程序启动");
            writer.newLine(); // 写入换行符
            
            writer.write("[DEBUG] 初始化配置文件");
            writer.newLine();
            
            writer.write("[INFO] 数据库连接成功");
            writer.newLine();
            
            // 强制刷新缓冲区
            writer.flush();
            
            System.out.println("日志已写入到 log.txt 文件");
            
        } catch (IOException e) {
            System.out.println("写入文件时发生错误: " + e.getMessage());
        }
    }
}

字符编码处理

编码问题的重要性

在处理包含中文或其他非ASCII字符的文件时,正确处理字符编码至关重要。使用InputStreamReader和OutputStreamWriter可以明确指定字符编码,避免乱码问题。

InputStreamReader - 指定编码读取

指定UTF-8编码读取文件
import java.io.*;
import java.nio.charset.StandardCharsets;

public class EncodingReaderExample {
    public static void main(String[] args) {
        try {
            // 使用UTF-8编码读取文件
            FileInputStream fis = new FileInputStream("chinese.txt");
            InputStreamReader reader = new InputStreamReader(fis, StandardCharsets.UTF_8);
            BufferedReader bufferedReader = new BufferedReader(reader);
            
            String line;
            System.out.println("使用UTF-8编码读取中文文件:");
            while ((line = bufferedReader.readLine()) != null) {
                System.out.println(line);
            }
            
            bufferedReader.close();
            
        } catch (IOException e) {
            System.out.println("读取文件时发生错误: " + e.getMessage());
        }
    }
}

OutputStreamWriter - 指定编码写入

指定UTF-8编码写入文件
import java.io.*;
import java.nio.charset.StandardCharsets;

public class EncodingWriterExample {
    public static void main(String[] args) {
        try {
            // 使用UTF-8编码写入文件
            FileOutputStream fos = new FileOutputStream("output_utf8.txt");
            OutputStreamWriter writer = new OutputStreamWriter(fos, StandardCharsets.UTF_8);
            BufferedWriter bufferedWriter = new BufferedWriter(writer);
            
            bufferedWriter.write("这是使用UTF-8编码写入的中文内容。\n");
            bufferedWriter.write("支持各种Unicode字符: 😀 🎉 ⭐\n");
            bufferedWriter.write("English text is also supported.\n");
            bufferedWriter.write("日本語も大丈夫です。\n");
            
            bufferedWriter.close();
            System.out.println("内容已使用UTF-8编码写入到 output_utf8.txt");
            
        } catch (IOException e) {
            System.out.println("写入文件时发生错误: " + e.getMessage());
        }
    }
}

PrintWriter - 便捷打印

PrintWriter的特点

PrintWriter提供了类似System.out的打印方法,包括print、println和printf,使得文件输出更加便捷。它还具有自动刷新功能和简化的异常处理。

PrintWriter格式化输出示例
import java.io.*;

public class PrintWriterExample {
    public static void main(String[] args) {
        try (PrintWriter writer = new PrintWriter(new FileWriter("report.txt"))) {
            // 使用print方法
            writer.print("学生姓名: ");
            writer.print("张三");
            writer.println(); // 换行
            
            // 使用println方法
            writer.println("学号: 2024001");
            writer.println("年龄: 20");
            
            // 使用printf格式化输出
            writer.printf("成绩: %.2f分\n", 95.5);
            writer.printf("排名: 第%d名\n", 1);
            
            // 表格格式输出
            writer.println("\n商品销售报表");
            writer.println("===================");
            writer.printf("%-10s %-8s %-8s %-10s\n", 
                         "商品名称", "单价", "数量", "总金额");
            writer.println("------------------------------------------");
            writer.printf("%-10s %8.2f %8d %10.2f\n", 
                         "苹果", 5.5, 10, 55.0);
            writer.printf("%-10s %8.2f %8d %10.2f\n", 
                         "香蕉", 3.2, 15, 48.0);
            
            System.out.println("报告已写入到 report.txt 文件");
            
        } catch (IOException e) {
            System.out.println("写入文件时发生错误: " + e.getMessage());
        }
    }
}

完整代码示例

字符流综合应用示例
import java.io.*;
import java.nio.charset.StandardCharsets;

/**
 * 字符流综合应用示例
 * 演示各种字符流的使用方法和最佳实践
 */
public class CharacterStreamDemo {
    
    public static void main(String[] args) {
        // 创建测试文件
        createTestFile();
        
        // 演示各种字符流操作
        demonstrateFileReader();
        demonstrateBufferedReader();
        demonstrateEncodingHandling();
        demonstratePrintWriter();
    }
    
    /**
     * 创建测试文件
     */
    private static void createTestFile() {
        try (PrintWriter writer = new PrintWriter(
                new OutputStreamWriter(
                    new FileOutputStream("test.txt"), 
                    StandardCharsets.UTF_8))) {
            writer.println("Java字符流学习");
            writer.println("这是一个测试文件");
            writer.println("包含中文字符: 你好世界!");
            writer.println("Unicode字符: 😊 🌟 ⚡");
            System.out.println("测试文件创建完成");
        } catch (IOException e) {
            System.out.println("创建测试文件失败: " + e.getMessage());
        }
    }
    
    /**
     * 演示FileReader基本用法
     */
    private static void demonstrateFileReader() {
        System.out.println("\n=== FileReader示例 ===");
        try (FileReader reader = new FileReader("test.txt")) {
            char[] buffer = new char[1024];
            int charsRead = reader.read(buffer);
            
            if (charsRead > 0) {
                String content = new String(buffer, 0, charsRead);
                System.out.println("读取内容:\n" + content);
            }
        } catch (IOException e) {
            System.out.println("读取失败: " + e.getMessage());
        }
    }
    
    /**
     * 演示BufferedReader按行读取
     */
    private static void demonstrateBufferedReader() {
        System.out.println("\n=== BufferedReader示例 ===");
        try (BufferedReader reader = new BufferedReader(
                new FileReader("test.txt"))) {
            String line;
            int lineNumber = 1;
            
            while ((line = reader.readLine()) != null) {
                System.out.println("第" + lineNumber + "行: " + line);
                lineNumber++;
            }
        } catch (IOException e) {
            System.out.println("读取失败: " + e.getMessage());
        }
    }
    
    /**
     * 演示字符编码处理
     */
    private static void demonstrateEncodingHandling() {
        System.out.println("\n=== 字符编码处理示例 ===");
        try (BufferedReader reader = new BufferedReader(
                new InputStreamReader(
                    new FileInputStream("test.txt"), 
                    StandardCharsets.UTF_8))) {
            String line;
            System.out.println("使用UTF-8编码读取:");
            while ((line = reader.readLine()) != null) {
                System.out.println(line);
            }
        } catch (IOException e) {
            System.out.println("读取失败: " + e.getMessage());
        }
    }
    
    /**
     * 演示PrintWriter格式化输出
     */
    private static void demonstratePrintWriter() {
        System.out.println("\n=== PrintWriter示例 ===");
        try (PrintWriter writer = new PrintWriter(
                new FileWriter("output.txt"))) {
            writer.println("字符流学习总结");
            writer.println("=================");
            writer.printf("学习时间: %s\n", "2024-01-05");
            writer.printf("完成度: %.1f%%\n", 100.0);
            writer.println("\n主要收获:");
            writer.println("1. 掌握了字符流的基本概念");
            writer.println("2. 学会了处理字符编码");
            writer.println("3. 了解了缓冲流的优势");
            
            System.out.println("学习总结已写入 output.txt");
        } catch (IOException e) {
            System.out.println("写入失败: " + e.getMessage());
        }
    }
}
💻 查看完整代码 - 在线IDE体验

字符流最佳实践

使用建议

推荐做法

// 1. 使用try-with-resources自动关闭流
try (FileReader reader = new FileReader("file.txt")) {
    // 读取操作
} // 自动关闭

// 2. 大文件使用缓冲流
try (BufferedReader reader = 
     new BufferedReader(new FileReader("large.txt"))) {
    // 高效读取
}

// 3. 明确指定字符编码
try (InputStreamReader reader = 
     new InputStreamReader(
         new FileInputStream("file.txt"), 
         StandardCharsets.UTF_8)) {
    // 明确编码
}

// 4. 及时刷新缓冲区
writer.write("重要数据");
writer.flush(); // 确保写入
  • 使用try-with-resources确保资源释放
  • 大文件操作使用缓冲流提高性能
  • 明确指定字符编码避免乱码
  • 重要数据及时刷新缓冲区

避免的做法

// 1. 忘记关闭流
FileReader reader = new FileReader("file.txt");
// ... 使用reader
// 忘记关闭,可能导致资源泄露

// 2. 不处理编码问题
FileReader reader = new FileReader("chinese.txt");
// 可能出现乱码

// 3. 频繁小量写入不使用缓冲
FileWriter writer = new FileWriter("output.txt");
for (int i = 0; i < 10000; i++) {
    writer.write("line " + i + "\n");
    // 性能较差
}
  • 忘记关闭流导致资源泄露
  • 不指定编码可能出现乱码
  • 频繁小量写入影响性能
  • 不使用缓冲流处理大文件

常见问题解答

Q: 什么时候使用字符流,什么时候使用字节流?

A: 处理文本文件时使用字符流,处理图片、音频、视频等二进制文件时使用字节流。字符流能自动处理字符编码,更适合文本操作。

Q: 如何处理中文乱码问题?

A: 使用InputStreamReader/OutputStreamWriter并明确指定UTF-8编码,或者确保文件编码与读取编码一致。

Q: BufferedReader的缓冲区大小是多少?

A: 默认8192个字符,可以通过构造函数指定大小。合理的缓冲区大小能显著提高I/O性能。

本章小结