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):数据从外部源读入到程序。对应
InputStream和Reader。 - 输出流(Output Stream):数据从程序写出到外部目标。对应
OutputStream和Writer。
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
作用:PrintStream是 OutputStream的子类,为其他输出流添加了打印各种数据值的能力。它不会抛出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();
}

浙公网安备 33010602011771号