大致介绍:
Java I/O (Input/Output) streams
文件流:文件在程序中是以流的形式来操作的
Drawing 2025-07-13 22.46.19.excalidraw
⚠ Switch to EXCALIDRAW VIEW in the MORE OPTIONS menu of this document. ⚠ You can decompress Drawing data with the command palette: ‘Decompress current Excalidraw file’. For more info check in plugin settings under ‘Saving’
Excalidraw Data
Text Elements
文件(磁盘)
java程序
(内存)输入流
输出流
Link to original
流:数据在数据源(文件)和程序(内存)之间经历的路径
输入流:数据从数据源(文件)到程序(内存)的路径
输出流:数据从程序(内存)到数据源(文件)的路径
流的分类
- 按操作数据单位不同分为:字节流(8bit)二进制文件,字符流(按字符)文本文件
- 按数据流的流向不同分为:输入流,输出流
- 按流的角色的不同分为:节点流,处理流/包装流
| (抽象基类) | 字节流 | 字符流 |
|---|---|---|
| 输入流 | InputStream | Reader |
| 输出流 | OutputStream | Writer |
Java的IO流共涉及40多个类,实际上非常规整,都是从如上4个抽象基类派生的。
由这四个类派生出来的子类名称都是以其父类名作为子类名后缀。
FileInputStream
String 构造方法用法示范
// 用法示范
byte[] bytes = "hello, world".getBytes(); // 使用平台默认编码生成 byte[]
String str = new String(bytes, 7, 5); // 从第7个字节开始,取5个字节构建字符串
System.out.println(str); // 输出: worldDrawing 2025-07-16 02.43.53.excalidraw
⚠ Switch to EXCALIDRAW VIEW in the MORE OPTIONS menu of this document. ⚠ You can decompress Drawing data with the command palette: ‘Decompress current Excalidraw file’. For more info check in plugin settings under ‘Saving’
Excalidraw Data
Text Elements
h
e
l
l
,
w
o
r
l
d
space
0
1
2
3
4
5
o
6
7
8
9
10
11
Link to original
/**
* @ Author: ferry
* @ Date 16/07/2025 12:53 am
*/
public class FileInputStreamTest {
int readData = 0;
FileInputStream fileInputStream = null;
@Test
public void readFile1() throws FileNotFoundException {
fileInputStream = new FileInputStream("test.txt");
/*
* Reads a byte of data from this input stream. This method blocks if no input is yet available.
* Returns: the next byte of data, or -1 if the end of the file is reached.
*/
try {
while ((readData = fileInputStream.read()) != -1){
// System.out.print(readData); // 104101108108111443211911111410810033
// System.out.println();
System.out.print((char)readData); // hello, world!
}
} catch (IOException ioException){
ioException.printStackTrace();
} finally {
// 关闭文件流,释放资源.
try {
fileInputStream.close();
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}
@Test
public void readFileArr() throws FileNotFoundException {
fileInputStream = new FileInputStream("test.txt");
byte[] buffer = new byte[8]; // 一次读取 8bit
/**
* @return the total number of bytes read into the buffer, or
* {@code -1} if there is no more data because the end of
* the file has been reached.
*/
// hello, world!
// fileInputStream 第一次 读取 8个字符
// 之后 自动读取 5 个字符
try {
while ((readData = fileInputStream.read(buffer)) != -1){
// buffer: [104, 101, 108, 108, 111, 44, 32, 119] h e l l o , {space} w
System.out.print(new String(buffer, 0 , readData)); // hello, world!
// 用byte[]数组构建一个String
// offset - The index of the first byte to decode
// length – The number of bytes to decode
}
} catch (IOException ioException){
ioException.printStackTrace();
} finally {
// 关闭文件流,释放资源.
try {
fileInputStream.close();
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}
}
Drawing 2025-07-16 02.43.53.excalidraw 1
⚠ Switch to EXCALIDRAW VIEW in the MORE OPTIONS menu of this document. ⚠ You can decompress Drawing data with the command palette: ‘Decompress current Excalidraw file’. For more info check in plugin settings under ‘Saving’
Excalidraw Data
Text Elements
h
e
,
w
l
l
o
r
l
d
space
1
2
3
4
5
o
6
7
8
9
10
11
!
12
13
第二次 readData = 5
Link to original
FileOutputStream
//创建 FileOutputStream对象
String filePath = "e:\\a.txt";
FileOutputStream fileOutputStream = null;
try {
//1. new FileOutputStream(filePath) 创建方式: 当写入内容是,会覆盖原来的内容
//2. new FileOutputStream(filePath, true) 创建方式: 当写入内容是,是追加到文件后面
fileOutputStream = new FileOutputStream(filePath, true);
//写入一个字节
//fileOutputStream.write('H');
//写入字符串
String str = "xyy,world!";
//str.getBytes() 可以把 字符串-> 字节数组
//fileOutputStream.write(str.getBytes());
/*
write(byte[] b, int off, int len) 将 len字节从位于偏移量 off的指定字节数组写入此文件输出流
*/
fileOutputStream.write(str.getBytes(), 0, 3); // xyy
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
fileOutputStream.close();
} catch (IOException e) {
e.printStackTrace();
}
}具体查看源码
FileReader
// 单个字符读取文件
String filePath = "e:\\story.txt";
FileReader fileReader = null;
int data = 0;
//1. 创建FileReader对象
try {
fileReader = new FileReader(filePath);
//循环读取 使用read, 单个字符读取
while ((data = fileReader.read()) != -1) {
System.out.print((char) data);
}
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
if (fileReader != null) {
fileReader.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}// 字符数组读取文件
System.out.println("~~~readFile02 ~~~");
String filePath = "e:\\story.txt";
FileReader fileReader = null;
int readLen = 0;
char[] buf = new char[8];
//1. 创建FileReader对象
try {
fileReader = new FileReader(filePath);
//循环读取 使用read(buf), 返回的是实际读取到的字符数
//如果返回-1, 说明到文件结束
while ((readLen = fileReader.read(buf)) != -1) {
System.out.print(new String(buf, 0, readLen));
}
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
if (fileReader != null) {
fileReader.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}FileWriter
String filePath = "e:\\note.txt";
FileWriter fileWriter = null;
char[] chars = {'a', 'b', 'c'};
try {
fileWriter = new FileWriter(filePath);
fileWriter.write(chars);
}catch (IOException e){
e.printStackTrace();
} finally{
try {
//fileWriter.flush();
fileWriter.close(); // 也可用 flush() 方法
} catch (IOException e) {
e.printStackTrace();
}
}注意使用 FileWriter 一定要关闭
在调用了fileWriter.close();后 底层使用了 FileOutputStream的 write()方法,自己debug追源码
节点流和处理流
节点流可以从一个特定的数据源读写数据,如FileReader、FileWriter
Drawing 2025-07-18 17.45.58.excalidraw
⚠ Switch to EXCALIDRAW VIEW in the MORE OPTIONS menu of this document. ⚠ You can decompress Drawing data with the command palette: ‘Decompress current Excalidraw file’. For more info check in plugin settings under ‘Saving’
Excalidraw Data
Text Elements
program
file
节点流
Link to original
处理流(也叫包装流)是“连接”在已存在的流(节点流或处理流)之上,为程序提供更为强大的读写功能,也更加灵活,如BufferedReader、BufferedWriter
Drawing 2025-07-18 17.45.58.excalidraw 1
⚠ Switch to EXCALIDRAW VIEW in the MORE OPTIONS menu of this document. ⚠ You can decompress Drawing data with the command palette: ‘Decompress current Excalidraw file’. For more info check in plugin settings under ‘Saving’
Excalidraw Data
Text Elements
program
文件/
数组/
数据源…处理流
通过一个类的包装来同时支持这些类型
BufferedReader、BufferedWriter
Link to original
- 节点流是底层流/低级流,直接跟数据源相接。
- 处理流(包装流)包装节点流,既可以消除不同节点流的实现差异,也可以提供更方便的方法来完成输入输出。
// 源码
public class BufferedReader extends Reader{
private Reader in;
// ...
}public class BufferedWriter extends Writer{
private Writer out;
// ...
}- 处理流(也叫包装流)对节点流进行包装,使用了
修饰器设计模式,不会直接与数据源相连
模拟一下:
public abstract class Reader { //抽象类
public void readFile() {}
public void readString() {}
//在Reader_ 抽象类,使用read方法统一管理.
//后面在调用时,利于对象动态绑定机制, 绑定到对应的实现子类即可.
//public abstract void read();
}
public class StringReader extends Reader {
public void readString() {
System.out.println("读取字符串..");
}
}
public class FileReader extends Reader {
public void readFile() {
System.out.println("对文件进行读取...");
}
}
public class BufferedReader extends Reader{
private Reader reader; //属性是 Reader 类型
//接收Reader 子类对象
public BufferedReader(Reader reader) {
this.reader = reader;
}
public void readFile() { //封装一层
reader.readFile();
}
//让方法更加灵活, 多次读取文件, 或者加缓冲byte[] ....
public void readFiles(int num) {
for(int i = 0; i < num; i++) {
reader.readFile();
}
}
//扩展 readString, 批量处理字符串数据
public void readStrings(int num) {
for(int i = 0; i <num; i++) {
reader.readString();
}
}
}
public static void main(String[] args) {
BufferedReader bufferedReader = new BufferedReader(new FileReader());
bufferedReader.readFiles(10);
//bufferedReader.readFile();
//Serializable
//Externalizable
//ObjectInputStream
//ObjectOutputStream
//这次希望通过 BufferedReader 多次读取字符串
BufferedReader bufferedReader_2 = new BufferedReader(new StringReader());
bufferedReader_2.readStrings(5);
}节点流
| 分类 | 字节输入流 | 字节输出流 | 字符输入流 | 字符输出流 |
|---|---|---|---|---|
| 抽象基类 | InputStream | OutputStream | Reader | Writer |
| 访问文件 | FileInputStream | FileOutputStream | FileReader | FileWriter |
| 访问数组 | ByteArrayInputStream | ByteArrayOutputStream | CharArrayReader | CharArrayWriter |
| 访问管道 | PipedInputStream | PipedOutputStream | PipedReader | PipedWriter |
| 访问字符串 | StringReader | StringWriter |
处理流
| 分类 | 字节输入流 | 字节输出流 | 字符输入流 | 字符输出流 |
|---|---|---|---|---|
| 缓冲流 | BufferdInputStream | BufferdOutputStream | BufferdReader | BufferdWriter |
| 转换流 | InputStreamReader | OutputStreamWriter | ||
| 对象流 | ObjectInputStream | ObjectOutputStream | ||
| 抽象基类 | FilterInputStream | FilterOutputStream | FilterReader | FilterWriter |
| 打印流 | PrintStream | PrintWriter | ||
| 推回输入流 | PushbackInputStream | PushbackReader | ||
| 特殊流 | DatalnputStream | DataOutputStream |
ObjectInputStream 与 ObjectOutputStream
序列化和反序列化
- 序列化就是在保存数据时,保存数据的值和数据类型
- 反序列化就是在恢复数据时,恢复数据的值和数据类型
- 需要让某个对象支持序列化机制,则必须让其类是可序列化的,为了让某个类是可序列化的,该类必须实现如下两个接口之一:
Serializable
Externalizable
对象流
功能:提供了对基本类型或对象类型的序列化和反序列化的方法
ObjectOutputStream 提供 序列化功能
ObjectInputStream 提供 反序列化功能
上代码
class Student implements Serializable {
// 要求序列化或反序列化的对象,需要是实现 Serializable 接口
@Getter
String name;
@Getter
transient int age;
//序列化对象时,默认将里面所有属性都进行序列化,
//但除了statici或transient修饰的成员
// serialVersionUID序列化的版本号,提高兼容性
private static final long serialVersionUID = 1L;
public Student(String name, int age) {
this.name = name;
this.age = age;
}
@Override
public String toString() {
return "Student{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
}OutputStream 输出对象流到文件“object.dat”
ObjectOutputStream oos = new ObjectOutputStream(new BufferedOutputStream(new FileOutputStream("object.dat")));
oos.writeInt(100);
oos.writeBoolean(true);
oos.writeObject(new Student("rain", 17));
oos.flush();读取
ObjectInputStream ois = new ObjectInputStream(new FileInputStream("object.dat"));
// 读取(反序列化)的顺序需要和你保存数据(序列化)的顺序一致
System.out.println(ois.readInt()); // 100
System.out.println(ois.readBoolean()); // true
Object obj = ois.readObject();
System.out.println("ois.readObject()的运行类型: " + obj.getClass()); // ois.readObject()的运行类型: class com.laohei.io.objectstream.output.Student
System.out.println(obj); // Student{name='rain', age=17}
Student student = (Student) obj;
student.getAge(); // 17
ois.close();// 关闭外层流即可,底层还会关闭 FileInputStream 流- 序列化对象时,要求里面属性的类型也需要实现序列化接口
- 序列化具备可继承性,也就是如果某类已经实现了序列化,则它的所有子类也已经默认实现了序列化