🌟 你好,我是 励志成为糕手 !
🌌 在代码的宇宙中,我是那个追逐优雅与性能的星际旅人。✨ 每一行代码都是我种下的星光,在逻辑的土壤里生长成璀璨的银河;
🛠️ 每一个算法都是我绘制的星图,指引着数据流动的最短路径;
🔍 每一次调试都是星际对话,用耐心和智慧解开宇宙的谜题。🚀 准备好开始我们的星际编码之旅了吗?
续前篇:编程语言Java——核心技术篇(四)集合类详解-CSDN博客
目录
5.4.1.1 FileInputStream vs FileReader
5.4.1.2 FileOutputStream vs FileWriter
5.4.2.1 BufferedInputStream vs BufferedReader
5.4.3.1 DataInputStream vs ObjectInputStream
5.4.3.2 PrintStream vs PrintWriter
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()); // 输出: admin
System.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);
// 读取文件到List
List<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;
}
@Override
public 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对象到方法调用的魔鬼细节》
📡 保持连接,我们下次太空见!