🌟 你好,我是 励志成为糕手 !
🌌 在代码的宇宙中,我是那个追逐优雅与性能的星际旅人。✨ 每一行代码都是我种下的星光,在逻辑的土壤里生长成璀璨的银河;
🛠️ 每一个算法都是我绘制的星图,指引着数据流动的最短路径;
🔍 每一次调试都是星际对话,用耐心和智慧解开宇宙的谜题。🚀 准备好开始我们的星际编码之旅了吗?
续前篇:编程语言Java——核心技术篇(四)集合类详解-CSDN博客
目录
5. IO流
5.1 IO流基本概述
5.1.1 IO流概述
5.1.2 IO流体系
5.2 常用I/O流详解
5.2.1 基础文件流
5.2.2 缓冲流
5.2.3 字符流
5.2.4 高级功能流
5.3 常用I/O流示例
5.3.1 文本文件读取示例
5.3.2 缓冲字符流完整示例(文本处理)
5.3.3 对象序列化与反序列化
5.4 常用I/O流对比
5.4.1 文件读写流对比
5.4.1.1 FileInputStream vs FileReader
5.4.1.2 FileOutputStream vs FileWriter
5.4.2 缓冲流对比
5.4.2.1 BufferedInputStream vs BufferedReader
5.4.2.2 缓冲流性能测试
5.4.3 高级功能流对比
5.4.3.1 DataInputStream vs ObjectInputStream
5.4.3.2 PrintStream vs PrintWriter
5.5 流的选择决策树
5. IO流
5.1 IO流基本概述
5.1.1 IO流概述
I/O流(Input/Output Stream)是Java中用于处理输入输出的抽象概念,它代表数据的流动:
-
流的方向:
-
输入流(InputStream/Reader):从数据源读取数据到程序
-
输出流(OutputStream/Writer):从程序写出数据到目的地
-
-
数据类型:
-
字节流(Byte Stream):以字节(8bit)为单位,处理二进制数据
-
InputStream/OutputStream及其子类
-
-
字符流(Character Stream):以字符(16bit)为单位,处理文本数据
-
Reader/Writer及其子类
-
-
5.1.2 IO流体系
这里直接上图吧,这个图里面也画得很清楚了。我们可以看到Java在设计IO流的时候主要考虑了两个维度,一个是流的方向,而另一个就是流的属性了。如果从流的方向来考虑就是输入流和输出流;从流的属性上考虑就是字节流和字符流了。
另外,在每一个不同的流的实现类中从取名上来讲也很好分辨——我们可以把字节流想象成一个类似输出的框架负责输入和输出,in和out分别代表不同方向;字节流就看成字符串,只不过可是多了一层方向的属性,刚好输入就是reader(读),输出就是writer(写)。至于再往下细分可能不同的小类侧重点又有不同,适合不同的数据类型,这个需要在后续写代码的过程中不断积累。
1. 字节流体系
(1) 输入流(InputStream)
java.io.InputStream
├── FileInputStream // 文件输入流
├── ByteArrayInputStream // 字节数组输入流
├── PipedInputStream // 管道输入流
├── FilterInputStream // 过滤流(装饰器基类)
│ ├── BufferedInputStream // 缓冲输入流
│ ├── DataInputStream // 数据输入流(读取基本数据类型)
│ └── PushbackInputStream // 回退输入流
├── ObjectInputStream // 对象反序列化流
└── SequenceInputStream // 序列输入流(合并多个流)
(2) 输出流(OutputStream)
java.io.OutputStream
├── FileOutputStream // 文件输出流
├── ByteArrayOutputStream // 字节数组输出流
├── PipedOutputStream // 管道输出流
├── FilterOutputStream // 过滤流(装饰器基类)
│ ├── BufferedOutputStream // 缓冲输出流
│ ├── DataOutputStream // 数据输出流(写入基本数据类型)
│ └── PrintStream // 打印流
└── ObjectOutputStream // 对象序列化流
2. 字符流体系
(1) 输入流(Reader)
java.io.Reader
├── InputStreamReader // 字节到字符的桥接流
│ └── FileReader // 文件字符输入流(常用)
├── CharArrayReader // 字符数组输入流
├── PipedReader // 管道字符输入流
├── FilterReader // 过滤字符流
│ └── PushbackReader // 回退字符流
├── BufferedReader // 缓冲字符输入流(常用)
└── StringReader // 字符串输入流
(2) 输出流(Writer)
java.io.Writer
├── OutputStreamWriter // 字符到字节的桥接流
│ └── FileWriter // 文件字符输出流(常用)
├── CharArrayWriter // 字符数组输出流
├── PipedWriter // 管道字符输出流
├── FilterWriter // 过滤字符流
├── BufferedWriter // 缓冲字符输出流(常用)
├── PrintWriter // 打印字符流(常用)
└── StringWriter // 字符串输出流
这里只是做一个简单的解释,如果想看详细的解析可以直接尝试看看源码。
5.2 常用I/O流详解
需要注意的是,因为流的运行需要涉及到输入和输出,所以一般需要用try-catch的形式来捕获异常,或者在写代码前就将异常抛出,不然代码会报错。
5.2.1 基础文件流
1. FileInputStream(文件字节输入流)
特点:
-
用于读取二进制文件
-
每次读取一个字节(8位)
-
适合处理图片、音频、视频等非文本文件
构造方法:
FileInputStream(String name)
FileInputStream(File file)
常用方法:
int read() // 读取单个字节
int read(byte[] b) // 读取到字节数组
int read(byte[] b, int off, int len) // 读取到数组指定位置
示例代码:
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;public class FileInputStreamExample {public static void main(String[] args) {FileInputStream fis = null;try {File file = new File("example.txt");fis = new FileInputStream(file);System.out.println("文件大小: " + file.length() + " bytes");int content;while ((content = fis.read()) != -1) {System.out.print((char) content);}} catch (IOException e) {System.err.println("读取文件时发生错误: " + e.getMessage());e.printStackTrace();} finally {if (fis != null) {try {fis.close();} catch (IOException e) {System.err.println("关闭文件流时发生错误: " + e.getMessage());}}}}
}
2. FileOutputStream(文件字节输出流)
特点:
-
用于写入二进制文件
-
支持追加模式
-
必须手动调用flush()或close()确保数据写入
构造方法:
FileOutputStream(String name)
FileOutputStream(String name, boolean append) // append为true表示追加
FileOutputStream(File file)
常用方法:
void write(int b) // 写入单个字节
void write(byte[] b)
void write(byte[] b, int off, int len)
示例代码:
import java.io.FileOutputStream;
import java.io.IOException;
import java.nio.charset.StandardCharsets;public class FileOutputStreamExample {public static void main(String[] args) {// 使用try-with-resources自动关闭资源try (FileOutputStream fos = new FileOutputStream("output.log", true)) { // true表示追加模式String logEntry = "新的日志条目 - " + System.currentTimeMillis() + "\n";// 转换为字节数组写入byte[] bytes = logEntry.getBytes(StandardCharsets.UTF_8);fos.write(bytes);System.out.println("日志写入成功");} catch (IOException e) {System.err.println("写入文件时发生错误: " + e.getMessage());e.printStackTrace();}}
}
5.2.2 缓冲流
1. BufferedInputStream(缓冲字节输入流)
优势:
-
默认8KB缓冲区(可配置)
-
减少实际磁盘I/O次数
-
特别适合大文件读取
典型用法:缓冲流文件复制
import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;public class BufferedFileCopy {public static void main(String[] args) {if (args.length < 2) {System.out.println("用法: java BufferedFileCopy <源文件> <目标文件>");return;}String sourceFile = args[0];String destFile = args[1];try (BufferedInputStream bis = new BufferedInputStream(new FileInputStream(sourceFile));BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream(destFile))) {byte[] buffer = new byte[8192]; // 8KB缓冲区int bytesRead;while ((bytesRead = bis.read(buffer)) != -1) {bos.write(buffer, 0, bytesRead);}System.out.println("文件复制完成");} catch (IOException e) {System.err.println("文件复制过程中发生错误: " + e.getMessage());e.printStackTrace();// 删除可能不完整的输出文件try {new java.io.File(destFile).delete();} catch (SecurityException se) {System.err.println("无法删除不完整的输出文件: " + se.getMessage());}}}
}
2. BufferedOutputStream(缓冲字节输出流)
注意事项:
-
必须调用flush()或close()确保数据写出
-
缓冲区满时自动flush
-
适合频繁的小数据写入
性能对比:
// 无缓冲写入1万次(耗时约1200ms)
try (FileOutputStream fos = new FileOutputStream("no_buffer.txt")) {for (int i = 0; i < 10_000; i++) {fos.write("line\n".getBytes());}
}// 有缓冲写入1万次(耗时约50ms)
try (BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream("with_buffer.txt"))) {for (int i = 0; i < 10_000; i++) {bos.write("line\n".getBytes());}bos.flush(); // 确保所有数据写出
}
5.2.3 字符流
1. InputStreamReader(字节到字符的桥梁)
核心作用:
-
将字节流转换为字符流
-
可指定字符编码(避免乱码)
编码处理示例:
// 读取GBK编码的文本文件
try (InputStreamReader isr = new InputStreamReader(new FileInputStream("gbk_file.txt"), "GBK")) {char[] buffer = new char[1024];int charsRead;while ((charsRead = isr.read(buffer)) != -1) {String text = new String(buffer, 0, charsRead);System.out.print(text);}
}catch{...
}
2. FileReader(文件字符输入流)
本质:
-
InputStreamReader的子类
-
使用系统默认编码(容易导致跨平台问题)
最佳实践:
// 不推荐(使用默认编码)
FileReader reader = new FileReader("file.txt");// 推荐做法(明确指定编码)
Reader reader = new InputStreamReader(new FileInputStream("file.txt"), StandardCharsets.UTF_8);
3. BufferedReader(带缓冲的字符流)
特有方法:
String readLine() // 读取整行(去掉换行符)
经典用法:
// 统计文本文件行数
try (BufferedReader br = new BufferedReader(new FileReader("big_text.txt"))) {int lineCount = 0;while (br.readLine() != null) {lineCount++;}System.out.println("总行数: " + lineCount);
}catch{...
}
5.2.4 高级功能流
1. DataInputStream/DataOutputStream(基本数据类型处理)
支持的数据类型:
-
所有Java基本类型(int, double, boolean等)
-
String(UTF格式)
二进制文件读写示例:
// 写入结构化数据
try (DataOutputStream dos = new DataOutputStream(new FileOutputStream("settings.dat"))) {dos.writeUTF("系统配置"); // 字符串dos.writeInt(1024); // 整数dos.writeDouble(3.1415); // 浮点数dos.writeBoolean(true); // 布尔值
}// 读取结构化数据
try (DataInputStream dis = new DataInputStream(new FileInputStream("settings.dat"))) {String title = dis.readUTF();int size = dis.readInt();double value = dis.readDouble();boolean enabled = dis.readBoolean();System.out.printf("%s: size=%d, value=%.2f, enabled=%b",title, size, value, enabled);
}
2. ObjectInputStream/ObjectOutputStream(对象序列化)
序列化要求:
-
实现Serializable接口
-
建议添加serialVersionUID
-
transient修饰不序列化的字段
完整示例:
class User implements Serializable {private static final long serialVersionUID = 1L;private String username;private transient String password; // 不被序列化// 构造方法、getter/setter省略
}// 序列化对象
try (ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("user.dat"))) {User user = new User("admin", "123456");oos.writeObject(user);
}// 反序列化对象
try (ObjectInputStream ois = new ObjectInputStream(new FileInputStream("user.dat"))) {User user = (User) ois.readObject();System.out.println(user.getUsername()); // 输出: adminSystem.out.println(user.getPassword()); // 输出: null(transient字段)
}
5.3 常用I/O流示例
5.3.1 文本文件读取示例
import java.io.BufferedReader;
import java.io.FileReader;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;public class TextFileReader {public static void main(String[] args) {Path filePath = Paths.get("poem.txt");// 方法1:传统方式(JDK1.7之前)System.out.println("=== 传统读取方式 ===");BufferedReader br = null;try {br = new BufferedReader(new FileReader(filePath.toFile()));String line;while ((line = br.readLine()) != null) {System.out.println(line);}} catch (IOException e) {System.err.println("读取文件时发生错误: " + e.getMessage());} finally {if (br != null) {try {br.close();} catch (IOException e) {System.err.println("关闭流时发生错误: " + e.getMessage());}}}// 方法2:try-with-resources方式(推荐)System.out.println("\n=== 现代读取方式 ===");try (BufferedReader reader = Files.newBufferedReader(filePath, StandardCharsets.UTF_8)) {reader.lines().forEach(System.out::println);} catch (IOException e) {System.err.println("读取文件时发生错误: " + e.getMessage());e.printStackTrace();}}
}
5.3.2 缓冲字符流完整示例(文本处理)
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.List;public class BufferedTextProcessor {public static void main(String[] args) {Path inputFile = Paths.get("input.txt");Path outputFile = Paths.get("output_uppercase.txt");// 处理文本文件(转换为大写)processTextFile(inputFile, outputFile);// 读取文件到ListList<String> lines = readLinesFromFile(inputFile);System.out.println("读取到 " + lines.size() + " 行文本");}/*** 使用缓冲字符流处理文本文件*/private static void processTextFile(Path input, Path output) {try (BufferedReader reader = Files.newBufferedReader(input, StandardCharsets.UTF_8);BufferedWriter writer = Files.newBufferedWriter(output, StandardCharsets.UTF_8)) {String line;int lineCount = 0;while ((line = reader.readLine()) != null) {// 处理每一行(这里转换为大写)String processedLine = line.toUpperCase();// 写入处理后的行writer.write(processedLine);writer.newLine(); // 写入系统相关的换行符lineCount++;// 每处理100行输出进度if (lineCount % 100 == 0) {System.out.println("已处理 " + lineCount + " 行");}}// 确保所有数据写出writer.flush();System.out.println("文件处理完成,共处理 " + lineCount + " 行");} catch (IOException e) {System.err.println("文本处理失败: " + e.getMessage());e.printStackTrace();}}/*** 使用缓冲流读取文件到List*/private static List<String> readLinesFromFile(Path file) {List<String> lines = new ArrayList<>();try (BufferedReader reader = Files.newBufferedReader(file, StandardCharsets.UTF_8)) {String line;while ((line = reader.readLine()) != null) {lines.add(line);}} catch (IOException e) {System.err.println("读取文件失败: " + e.getMessage());e.printStackTrace();}return lines;}
}
5.3.3 对象序列化与反序列化
import java.io.*;
import java.util.ArrayList;
import java.util.List;class Student implements Serializable {private static final long serialVersionUID = 1L;private String name;private transient String password; // 不被序列化private int age;public Student(String name, String password, int age) {this.name = name;this.password = password;this.age = age;}@Overridepublic String toString() {return "Student{" +"name='" + name + '\'' +", password='" + password + '\'' +", age=" + age +'}';}
}public class ObjectSerializationDemo {public static void main(String[] args) {List<Student> students = new ArrayList<>();students.add(new Student("张三", "zhang123", 20));students.add(new Student("李四", "li456", 22));// 序列化String filename = "students.dat";try (ObjectOutputStream oos = new ObjectOutputStream(new BufferedOutputStream(new FileOutputStream(filename)))) {oos.writeObject(students);System.out.println("对象序列化完成");} catch (IOException e) {System.err.println("序列化过程中发生错误: " + e.getMessage());e.printStackTrace();}// 反序列化try (ObjectInputStream ois = new ObjectInputStream(new BufferedInputStream(new FileInputStream(filename)))) {@SuppressWarnings("unchecked")List<Student> deserialized = (List<Student>) ois.readObject();System.out.println("\n反序列化结果:");deserialized.forEach(System.out::println);} catch (IOException | ClassNotFoundException e) {System.err.println("反序列化过程中发生错误: " + e.getMessage());e.printStackTrace();}}
}
ps:这里做一个补充,可能很多人会不明白序列化和反序列化是什么意思。
序列化(Serialization):
将对象的状态信息转换为可存储(如文件、数据库)或可传输(如网络通信)的标准化格式,常见形式包括二进制字节流、JSON、XML等。其本质是解决对象在跨平台、跨语言或持久化场景下的数据交换问题。
反序列化(Deserialization):
将序列化后的数据重新解析并恢复为内存中的对象,确保程序能继续操作原始数据结构。例如从网络接收的JSON数据还原为程序中的类实例。
5.4 常用I/O流对比
5.4.1 文件读写流对比
5.4.1.1 FileInputStream vs FileReader
相同点:
-
都用于文件读取
-
都是低级流(直接连接数据源)
不同点:
特性 | FileInputStream | FileReader |
---|---|---|
数据类型 | 字节(8bit) | 字符(16bit) |
处理内容 | 二进制文件(图片、视频等) | 文本文件 |
编码处理 | 无编码转换 | 自动按系统默认编码/指定编码转换 |
读取方法 | read()返回0-255的int | read()返回0-65535的int(Unicode) |
5.4.1.2 FileOutputStream vs FileWriter
相同点:
-
都用于文件写入
-
都是低级流
不同点:
特性 | FileOutputStream | FileWriter |
---|---|---|
数据类型 | 字节 | 字符 |
写入内容 | 二进制数据 | 文本数据 |
编码处理 | 直接写入字节 | 字符→字节转换(可指定编码) |
构造参数 | 可追加写入(append=true) | 可追加写入(append=true) |
5.4.2 缓冲流对比
5.4.2.1 BufferedInputStream vs BufferedReader
性能对比:
-
缓冲流比非缓冲流快8-10倍(减少实际I/O操作次数)
-
BufferedReader特有readLine()方法
示例对比:
// 使用BufferedInputStream读取
try (InputStream is = new BufferedInputStream(new FileInputStream("largefile.bin"), 8192)) { // 8KB缓冲区byte[] buffer = new byte[1024];int bytesRead;while ((bytesRead = is.read(buffer)) != -1) {// 处理数据}
}// 使用BufferedReader读取文本
try (BufferedReader br = new BufferedReader(new FileReader("text.txt"), 16384)) { // 16KB缓冲区String line;while ((line = br.readLine()) != null) { // 按行读取System.out.println(line);}
}
5.4.2.2 缓冲流性能测试
// 测试无缓冲 vs 有缓冲的复制速度
long start = System.nanoTime();
try (InputStream is = new FileInputStream("1GBfile.zip");OutputStream os = new FileOutputStream("copy.zip")) {byte[] buffer = new byte[8192];int bytesRead;while ((bytesRead = is.read(buffer)) != -1) {os.write(buffer, 0, bytesRead);}
}
long end = System.nanoTime();
System.out.println("无缓冲耗时: " + (end-start)/1_000_000 + "ms");start = System.nanoTime();
try (InputStream is = new BufferedInputStream(new FileInputStream("1GBfile.zip"));OutputStream os = new BufferedOutputStream(new FileOutputStream("copy.zip"))) {byte[] buffer = new byte[8192];int bytesRead;while ((bytesRead = is.read(buffer)) != -1) {os.write(buffer, 0, bytesRead);}
}
end = System.nanoTime();
System.out.println("缓冲流耗时: " + (end-start)/1_000_000 + "ms");
典型结果:
无缓冲耗时: 4523ms
缓冲流耗时: 587ms
5.4.3 高级功能流对比
5.4.3.1 DataInputStream vs ObjectInputStream
特性 | DataInputStream | ObjectInputStream |
---|---|---|
主要用途 | 读取基本数据类型 | 对象反序列化 |
读取方法 | readInt(), readDouble()等 | readObject() |
数据格式 | 简单二进制格式 | Java序列化协议 |
版本兼容 | 无版本概念 | 使用serialVersionUID |
典型用途 | 自定义二进制协议 | Java对象持久化 |
示例:
// DataInputStream读取结构化二进制数据
try (DataInputStream dis = new DataInputStream(new FileInputStream("data.bin"))) {int version = dis.readInt();String name = dis.readUTF();double price = dis.readDouble();boolean inStock = dis.readBoolean();
}// ObjectInputStream读取对象
try (ObjectInputStream ois = new ObjectInputStream(new FileInputStream("product.ser"))) {Product p = (Product) ois.readObject();System.out.println(p);
}
5.4.3.2 PrintStream vs PrintWriter
特性 | PrintStream | PrintWriter |
---|---|---|
继承体系 | FilterOutputStream子类 | Writer子类 |
输出目标 | 字节流 | 字符流 |
自动刷新 | 可配置(autoFlush) | 可配置(autoFlush) |
异常处理 | 设置错误标志(无异常抛出) | 可获取IO异常 |
方法 | print(), println(), printf() | 相同方法集 |
示例:
// PrintStream使用(System.out就是PrintStream)
try (PrintStream ps = new PrintStream(new FileOutputStream("log.txt"), true, "UTF-8")) {ps.println("错误日志:");ps.printf("时间: %tF %<tT%n", new Date());ps.println("温度: " + 25.6);
}// PrintWriter使用
try (PrintWriter pw = new PrintWriter(new BufferedWriter(new FileWriter("report.txt")))) {pw.println("=== 测试报告 ===");pw.println("通过用例: " + 42);pw.println("失败用例: " + 3);pw.printf("通过率: %.2f%%%n", 42.0/45*100);
}
这个就当一个扩展吧,因为感觉这个流和平时用的System.out.print()挺像的。
5.5 流的选择决策树
一样的,建议放大看,几乎涵盖了所有的应用场景。对于不同场景的敏感性是在不断地敲代码中提升的,与其去死记硬背,不如先面对问题,然后针对不同的问题再去寻求答案,找到最适配的方法;这样效率才会大大提升!
🌟 我是 励志成为糕手 ,感谢你与我共度这段技术时光!
✨ 如果这篇文章为你带来了启发:
✅ 【收藏】关键知识点,打造你的技术武器库
💡 【评论】留下思考轨迹,与同行者碰撞智慧火花
🚀 【关注】持续获取前沿技术解析与实战干货🌌 技术探索永无止境,让我们继续在代码的宇宙中:
• 用优雅的算法绘制星图
• 以严谨的逻辑搭建桥梁
• 让创新的思维照亮前路🛰️ 下期预告:《编程语言Java——核心技术篇(六)解剖Java反射:从Class对象到方法调用的魔鬼细节》
📡 保持连接,我们下次太空见!