节点流和处理流
一、基本介绍
1. 节点流
节点流可以从一个特定的数据源读写数据,如 FileReader、FileWriter
2. 处理流
处理流 (也叫包装流) 是 “连接” 在已存在的流(节点流或处理流)之上,为程序提供更为强大的读写功能,也更加灵活,如 BufferedReader、BufferedWriter

小结
- 节点流的类型
- 文件
- 数组
- 管道
- 字符串
- 处理流的类型
3. 区别与联系
节点流是底层流 / 低级流,直接跟数据源相接。
处理流 (包装流) 包装节点流,既可以消除不同节点流的实现差异,也可以提供更方便的方法来完成输入输出。
处理流 (也叫包装流) 对节点流进行包装,使用了修饰器设计模式,不会直接与数据源相连
4. 处理流的优势
性能的提高:主要以增加缓冲的方式来提高输入输出的效率
操作的便捷:处理流可能提供了一系列便捷的方法来一次输入输出大批量的数据,使用更加灵活方便
二、字符处理流
1. BufferReader
- 说明
- 首先是 Reader 的子类,读取的是字符流
- 这是一个处理流/ 包装流,需要在构造器中传入子类对象
- 相关方法
readline():读取行,提高了读取效率
读取规则
- 返回的是字符串,需要用字符串接收并打印内容才可以看到读取的信息
- 只要返回的不为空,则还没有读取完成
注意:reaLine() 方法只会读取内容,不会读取换行符
- 资源关闭
只需要关闭外层即可,底层会自动实现内层的关闭
代码示例
public class newFile {
public static void main(String[] args) throws IOException {
String filePath = "C:\\Users\\jackson\\Desktop\\file.txt"; // 源文件路径
BufferedReader bufferedReader = new BufferedReader(new FileReader(filePath));
String line;
// 读取规则,当readLine()方法返回的为空,则读取完成
while((line = bufferedReader.readLine()) != null){
System.out.println(line);
}
bufferedReader.close();
}
}代码分析:调用 readLine() 方法,只要不返回空,就一直读取,把读取的内容输出
2. BufferWriter
相关方法
- newLine():插入换行符
- 如果要以追加的方式写入,需要在 FileWriter 中指定 true 参数,BufferWriter 没有提供该构造器
代码示例
public class newFile {
public static void main(String[] args) throws IOException {
String filePath = "C:\\Users\\jackson\\Desktop\\file.txt"; // 源文件路径
BufferedWriter bufferedWriter = new BufferedWriter(new FileWriter(filePath,true));
bufferedWriter.write("明天会更好");
bufferedWriter.newLine();
bufferedWriter.write("继续努力向前");
bufferedWriter.close();
}
}3. 应用:文件拷贝
代码示例
题目要求:给定源文件路径和目标文件路径,实现文件内容的拷贝
public class newFile {
public static void main(String[] args) {
String srcPath = "C:\\Users\\jackson\\Desktop\\file.txt";
String dirPath = "C:\\Users\\jackson\\Desktop\\file2.txt";
BufferedReader bufferedReader = null;
BufferedWriter bufferedWriter = null;
String line;
try {
bufferedReader = new BufferedReader(new FileReader(srcPath));
bufferedWriter = new BufferedWriter(new FileWriter(dirPath));
// 注意:reaLine() 方法只会读取内容,不会读取换行符,需要手动处理
while((line = bufferedReader.readLine()) != null){
bufferedWriter.write(line);
bufferedWriter.newLine();
}
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
if(bufferedReader!=null){
bufferedReader.close();
}
if(bufferedWriter != null){
bufferedWriter.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
}代码分析
- 使用输入流读取信息,使用输出流把信息写入文件实现拷贝操作
- 注意点:readLine() 方法只会读取内容,不会读取换行符,在拷贝操作时需要使用 newLine() 方法手动插入换行符
小结
BufferWriter 和 BufferReader 都是处理字符流的,不要去操作字节流文件(即二进制文件:视频,声音,doc,pdf)
三、字节处理流
1. BufferedInputStream
方法:read() 方法,和 FileInputpuStream 中的 read() 方法 一样

2. BufferedOutputStream
方法:write() 方法,和 FileOutpuStream 中的 write() 方法 一样
注意:该类提供 flush()方法,即通过 flush 或者 close()方法才可以把内容写入成功

应用:文件拷贝
代码示例
public class newFile {
public static void main(String[] args) {
String srcPath = "C:\\Users\\jackson\\Desktop\\file.txt";
String dirPath = "C:\\Users\\jackson\\Desktop\\file2.txt";
BufferedInputStream bufferedInputStream = null;
BufferedOutputStream bufferedOutputStream = null;
int data = 0;
try {
bufferedInputStream = new BufferedInputStream(new FileInputStream(srcPath));
bufferedOutputStream = new BufferedOutputStream(new FileOutputStream(dirPath));
while((data = bufferedInputStream.read()) != -1){
bufferedOutputStream.write(data);
}
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
if(bufferedInputStream != null){
bufferedInputStream.close();
}
if (bufferedOutputStream != null){
bufferedOutputStream.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
}总结
字节流既可以操作二进制文件,又可以操作文本文件(本质还是二进制文件)
四、对象处理流
1. 基本介绍
- 应用场景:保存文件时需要保存值和类型
- 序列化和反序列化
- 序列化就是在保存数据时,保存数据的值和数据类型
- 反序列化就是在恢复数据时,恢复数据的值和数据类型
- 需要让某个对象支持序列化机制,则必须让其类是可序列化的,为了让某个类是可序列化的,必须实现下列接口
- Serializable:这是一个标记接口,没有方法
- Externalizable:该接口有方法需要实现,因此我们一般实现上面的 Serializable 接口
2. ObjectOutputStream
(1)相关方法
| 方法 | 描述 |
|---|---|
| writeUTF() | 写入字符串 |
| writeInt() | 写入整数 |
| writeChar() | 写入字符 |
| writeDouble() | 写入浮点数 |
| writeBoolean() | 写入布尔值 |
| writeObject() | 写入一个对象 |
(2)注意点
保存的内容不是纯文本,而是按照他的类型来保存的,打开保存后的文件可以看到文件内容是乱码的(但是在反序列化的时候能够准确的输出值和类型)
(3)代码示例
public class newFile {
public static void main(String[] args) {
String srcPath = "C:\\Users\\jackson\\Desktop\\file.txt";
ObjectOutputStream objectOutputStream = null;
try { // 序列化,保存文件内容
objectOutputStream = new ObjectOutputStream(new FileOutputStream(srcPath));
// 由于类型是 Object,在底层会自动装箱
objectOutputStream.writeUTF("你好"); // 写入字符串
objectOutputStream.writeInt(10); // 写入整数
objectOutputStream.writeChar('a'); // 写入字符
objectOutputStream.writeDouble(1.25); // 写入浮点数
objectOutputStream.writeBoolean(true); // 写入布尔值
objectOutputStream.writeObject(new dog("jack", 18)); // 写入一个对象
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
objectOutputStream.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
class dog implements Serializable{
String name;
int age;
public dog(String name, int age) {
this.name = name;
this.age = age;
}
@Override
public String toString() {
return "dog{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
}代码分析
- 首先要实现序列化,类都必须实现 Serializable 接口或者是 Externalizable 接口
- 对于方法的理解:由于该类是 Object 的,所以调用的方法都是包装类,底层会实现自动装箱机制
3. ObjectInputStream
(1)相关方法
| 方法 | 描述 |
|---|---|
| readUTF() | 写入字符串 |
| readInt() | 写入整数 |
| readChar() | 写入字符 |
| readDouble() | 写入浮点数 |
| readBoolean() | 写入布尔值 |
| readObject() | 写入一个对象 |
(2)注意点
读取(反序列化)的顺序要和保存的数许(序列化)的顺序一致,否则会抛出异常
readObject() 方法会有异常,这里需要抛出或者捕获
如果希望读取 dog 对象或者调用 dog 的方法,需要向下转型,同时需要将 dog 类的定义拷贝到可以引用的位置
(3)代码示例
public class newFile {
public static void main(String[] args) throws ClassNotFoundException {
String srcPath = "C:\\Users\\jackson\\Desktop\\file.txt";
ObjectInputStream objectInputStream = null;
try { // 序列化,保存文件内容
objectInputStream = new ObjectInputStream(new FileInputStream(srcPath));
// 由于类型是 Object,在底层会自动装箱
System.out.println(objectInputStream .readUTF()); // 写入字符串
System.out.println(objectInputStream .readInt()); // 写入整数
System.out.println(objectInputStream .readChar()); // 写入字符
System.out.println(objectInputStream .readDouble()); // 写入浮点数
System.out.println(objectInputStream .readBoolean()); // 写入布尔值
System.out.println(objectInputStream .readObject()); // 写入一个对象
// 读取对象或者调用对象的方法需要-->向下转型
dog tom = new dog("tom", 5);
dog dog_ = (dog)objectInputStream.readObject();
String name = dog_.getName();
int age = dog_.getAge();
System.out.println("dog_的信息是\nname:" + name + "\nage:" + age);
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
objectInputStream.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
class dog implements Serializable {
String name;
int age;
public dog(String name, int age) {
this.name = name;
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
@Override
public String toString() {
return "dog{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
}4. 注意事项
- 读写顺序要一致
- 要求实现序列化或反序列化对象,需要实现 Serializable 接口
- 序列化的类中建议添加 SerialVersionUID, 为了提高版本的兼容性(修改对象的内容后,就不会认为是不同的对象而报错)
- 序列化对象时,默认将里面所有属性都进行序列化,但是static 或 transient 修饰的成员不会被序列化
- 序列化对象时,要求里面属性的类型也需要实现序列化接口
- 序列化具备可继承性,也就是如果某类已经实现了序列化,则它的所有子类也已经默认实现了序列化
代码示例:添加 SerialVersionUID 属性
class dog implements Serializable {
private name;
private age;
// 序列化的版本号,可以提高兼容性
private static final long serialVersionUID = 1L;
...
}