节点流和处理流
基本介绍
- 节点流可以从特定数据源读取数据,如FileReader、FileWriter
- 处理流:是对一个已存在的流的连接和封装,通过所封装的流的功能调用实现数据读写。如BufferedReader.处理流的构造方法总是要带一个其他的流对象做参数。一个流对象经过其他流的多次包装,称为流的链接。
- 节点流和处理流一览
以BuffredReader为例,它里面有个Reader属性,说明它可以处理一个节点流,只要是Reader的子类就可以。


同样,BufferedWriter里也封装了一个Writer,说明它可以处理一个节点流,只要是Writrer的子类就可以。


这种设计模式叫做修饰器模式
节点流和处理流的区别和联系
- 节点流是底层流/低级流,直接跟数据源连接。
- 处理流(包装流)包装节点流,既可以消除不同节点流的实现差异,也可以提供更方便的方法来完成输入和输出。
- 处理流对节点流进行包装,使用了修饰器设计模式,不会直接与数据源相连接。
处理流的功能体现
- 性能的提高:主要以增加缓冲的方式来提高输入输出的效率。
- 操作的便捷:处理流可能提供了一系列便捷的方法来一次性输入和输出大批量的数据,使用更加灵活方便。
处理流-BufferedReader和BufferedWriter
- BufferedReader和BufferedWriter属于字节流,是按照字符来读取数据的。
- 关闭处理流时,只要关闭外层流即可
应用案例1 使用BufferedReader读取文本文件,并显示在控制台
String fileName = "D:\\Test\\test.txt";
//创建BufferedReader
BufferedReader bufferedReader = new BufferedReader(new FileReader(fileName));
//读取
String line;
//readLine()按行读取文件,如果已到达流末尾,则返回 null
while ((line = bufferedReader.readLine()) != null) {System.out.println(line);
}
//关闭流,底层会自动关闭节点流
bufferedReader.close();
应用案例2 使用BufferedWriter将"Hello,world"写入文件
String fileName = "D:\\Test\\hw.txt";
String content = "Hello,world";
BufferedWriter bufferedWriter = new BufferedWriter(new FileWriter(fileName));
bufferedWriter.write(content);
bufferedWriter.newLine();//插入一个和系统相关的换行符
bufferedWriter.write(content);
bufferedWriter.close();
System.out.println("写入成功");
应用案例3 使用ufferedReader和BufferedWriter完成文本文件的拷贝
String originName = "D:\\Test\\test.txt";
String targetName = "D:\\Test\\test_copy.txt";
//创建bufferedReader
BufferedReader bufferedReader = new BufferedReader(new FileReader(originName));
//创建bufferedWriter
BufferedWriter bufferedWriter = new BufferedWriter(new FileWriter(targetName));
//按行读取文件
String line;
while((line = bufferedReader.readLine())!= null){//按行写入bufferedWriter.write(line);//换行bufferedWriter.newLine();
}
bufferedReader.close();
bufferedWriter.close();
System.out.println("拷贝完毕");
处理流-BufferedInputStream和BufferedOutputStream
介绍BufferedInputStream
BufferedInputStream
为另一个输入流添加一些功能,即缓冲输入以及支持 mark
和 reset
方法的能力。在创建 BufferedInputStream
时,会创建一个内部缓冲区数组。在读取或跳过流中的字节时,可根据需要从包含的输入流再次填充该内部缓冲区,一次填充多个字节。mark
操作记录输入流中的某个点,reset
操作使得在从包含的输入流中获取新字节之前,再次读取自最后一次 mark
操作后读取的所有字节。

介绍BufferedOutputStream
BufferedOutputStream
类实现缓冲的输出流。通过设置这种输出流,应用程序就可以将各个字节写入底层输出流中,而不必针对每次字节写入调用底层系统。

应用案例 使用BufferedInputStream和BufferedOutputStream完成图片/音乐的拷贝
String originName = "D:\\Test\\testpicture.png";
String targetName = "D:\\Test\\Test2\\testpicture.png";
//创建BufferedInputStream
BufferedInputStream bufferedInputStream = new BufferedInputStream(new FileInputStream(originName));
//创建BufferedOutputStream
BufferedOutputStream bufferedOutputStream = new BufferedOutputStream(new FileOutputStream(targetName));
byte[] bytes = new byte[1024];
int readLen;
while ((readLen = bufferedInputStream.read(bytes))!=-1){bufferedOutputStream.write(bytes,0,readLen);
}
//关闭流
bufferedInputStream.close();
bufferedOutputStream.close();System.out.println("拷贝完毕");
对象流-ObjectInputStream和ObjectOutputStream
为什么需要对象流?
比如,当我们将int num = 100这个数据直接保存到文件中,注意不是 100 数字,而是int 100,并且我们需要能够直接从文件中恢复int 100;又比如将Dog dog = new Dog(“小黄”,3)这个dog对象保存到文件中,并且能够从文件中恢复。
以上要求,就是能够将 基本数据类型 或者 对象 进行 序列化 和 反序列化操作
序列化和反序列化
- 序列化就是在保存数据时,保存数据的值和数据类型
- 反序列化就是在恢复数据时,恢复数据的值和数据类型
- 为了让对象支持序列化机制,必须让它的类是可序列化的,为了让某个类是可序列化的,该类必须实现如下两个接口之一:
Serializable
:一般推荐这个Externalizable
:继承了Serializable
,需要实现方法
ObjectInputStream
ObjectInputStream
的一个构造方法需要传入InputStream
,说明了其实它的设计也是修饰器模式。

ObjectOutputStream

应用案例1 使用ObjectOutputStream序列化基本数据类型和一个Dog对象(name, age),并保存到data.dat文件中
//必须实现可序列化
public class Dog implements Serializable {private String name;private int age;public Dog(String name, int age) {this.name = name;this.age = age;}
}
public class ObjectOutputStream_ {public static void main(String[] args) throws IOException {//序列化后保存的文件格式不是纯文本,而是按照他的格式String fileName = "D:\\Test\\data.dat";ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream(fileName));//序列化数据到该文件中oos.write(100);//int 自动装箱成Integer,而Integer在底层是实现了Serializable的oos.writeBoolean(true);//底层是实现了Serializable的oos.writeChar('a');//底层是实现了Serializable的oos.writeDouble(9.5);//底层是实现了Serializable的oos.writeUTF("清华大学");//String//保存一个Dog对象oos.writeObject(new Dog("旺财", 10));oos.close();System.out.println("数据保存完毕(序列化形式)");}
}
应用案例2 使用ObjectInputStream读取data.dat并反序列化恢复数据
public class ObjectInputStream_ {public static void main(String[] args) throws IOException, ClassNotFoundException {//指定反序列化文件String fileName = "D:\\Test\\data.dat";ObjectInputStream ois = new ObjectInputStream(new FileInputStream(fileName));//读取(反序列化)//1. 读取的顺序需要和保存数据的顺序一致,否则会出现异常System.out.println(ois.readInt());System.out.println(ois.readBoolean());System.out.println(ois.readChar());System.out.println(ois.readDouble());System.out.println(ois.readUTF());//dog的编译类型是 Object,运行类型是DogObject dog = ois.readObject();System.out.println("运行类型 = " + dog.getClass());System.out.println("dog = " + dog);//底层Object->Dog//如果我们要使用Dog的方法,需要向下转型Dog myDog = (Dog)dog;System.out.println(myDog.getAge());//关闭流,关闭外层流即可ois.close();System.out.println("读取完毕");}
}
注意事项和相关细节
- 读写顺序要一致
- 要求实现序列化或反序列化对象,需要实现
Serializable
- 序列化的类中建议添加
SerialVersionUID
,为了提高版本的兼容性
private static final long serialVersionUID = 1L;
- 序列化对象时,默认将里面所有属性都进行序列化,但除了
static
或transient
修饰的成员 - 序列化对象时,要求里面属性的类型也需要实现序列化接口
- 序列化具备可继承性,也就是如果某类已经实现了序列化,则它的所有子类也已经默认实现了序列化
标准输入和输出流
- System.in 标准输入
- System.out 标准输出
//public static final InputStream in = null;
//System.in的编译类型是 InputStream
//System.in的运行类型是 BufferedInputStream
//表示的是标准输入:键盘
System.out.println(System.in.getClass());//public static final PrintStream out = null;
//System.out的编译类型是 PrintStream
//System.out的运行类型是 PrintStream
//表示的是标准输出:屏幕
System.out.println(System.out.getClass());System.out.println("尝试System.out.println()");
Scanner scanner = new Scanner(System.in);
System.out.println("请输入内容:");
String next = scanner.next();
System.out.println("next = " + next);
转换流-InputStreamReader和OutputStreamWriter

String fileName = "D:\\Test\\b.txt";//1. 创建输入流
BufferedReader br = new BufferedReader(new FileReader(fileName));
//2. 读取,默认情况下按照UTF-8
String s = br.readLine();
System.out.println("读取的内容: " + s);
br.close();
默认情况下文本文件存储按照UTF-8。
现在另存b.txt为ANSI国标码(每个国家不一样,在我的系统中为GBK)
此时在运行发现乱码了
因为我们没有指定读取文件的编码方式!
有转换流就可以乱码问题。
InputStreamReader类图
能够发现有一个构造器
能够传入一个InputStream对象,同时可以指定编码方式!!!
OutputStreamWriter类图
应用案例1 演示InputStreamReader
演示使用 InputStreamReader 转换流解决中文乱码问题,将字节流 FileInputStream 转成字符流 InputStreamReader
指定编码 gbk或者utf-8
String fileName = "D:\\Test\\b2.txt";FileInputStream fileInputStream = new FileInputStream(fileName);
InputStreamReader inputStreamReader = new InputStreamReader(fileInputStream, "gbk");
char[] chars = new char[8];
int readLen;
while ((readLen = inputStreamReader.read(chars)) != -1){System.out.println(new String(chars, 0, readLen));
}
inputStreamReader.close();
System.out.println("-------------------------------------");
System.out.println("读取完毕");
此时正常输出
应用案例1 演示OutputStreamWriter
String content = "我要上清华!";
String fileName = "D:\\Test\\b3.txt";
FileOutputStream fileOutputStream = new FileOutputStream(fileName);
OutputStreamWriter outputStreamWriter = new OutputStreamWriter(fileOutputStream, "gbk");
outputStreamWriter.write(content);
outputStreamWriter.close();
System.out.println("-------------------------------------");
System.out.println("以gbk编码写入完毕");
打印流-PrintStream和PrintWriter
打印流只有输出流,没有输入流
PrintStream类图
PrintWriter类图

应用案例1 演示PrintStream
PrintStream out = System.out;
// PrintStream输出数据的位置是 标准输出,即显示器
out.print("我爱学JAVA,");
out.write("我要上清华".getBytes());out.close();
我们还可以修改打印输出的位置/设备
//我们可以修改打印流的输出位置/设备
System.setOut(new PrintStream("D:\\Test\\f1.txt"));
System.out.println("我要上清华!");
应用案例2 演示PrintWriter
打印到显示器
PrintWriter printWriter = new PrintWriter(System.out);
printWriter.print("我爱Java!");
printWriter.close();
打印到文件
PrintWriter printWriter = new PrintWriter(new FileWriter("E:\\Test\\t2.txt"));
printWriter.print("我爱Java!");
printWriter.close();