4 缓冲流
4.1 目标
1、能够使用 BufferedInputStream字节缓冲流读取数据到程序
2、能够使用字节缓冲流写出数据到文件
3、能够明确字符缓冲流的作用和基本用法
4、能够使用缓冲流的特殊功能
4.2 缓冲流概述
原来的字节/字符 输入输出流,每次之恶能读取一个字节/字符,效率比较低,增加缓冲流的目的就是为了给基本的字节/字符流增加一个缓冲区,以此提高字节/字符读取文件的效率。
4.3 字节缓冲流
4.3.1 BufferedInputStream 字节缓冲流
java.io.BufferedInputStream 字节缓冲输入流 extends InputStream字节输出流
继承父类的成员方法:
- int read() 一次读取一个字节并返回
- int read(byte[] b) 使用数组缓冲,一次读取多个字节
- void close() 关闭此输入流并释放与该流关联的所有系统资源
构造方法:
- BufferedInputStream(InputStream in) 创建一个具有默认缓冲区大小的BufferedInputStream对象
- BufferedInputStream(InputStream in , int size) 创建具有指定缓冲区大小的BufferedInputStream对象
构造方法参数:
- InputStream in : 传递字节输入流,可以传递InputStream的任意子对象,比如FileInputStream对象,这样就会给FileInputStream对象增加一个缓冲区
- int size : 指定缓冲区的大小,不写使用默认值8192 ,private static int DEFAULT_BUFFER_SIZE = 8192; 即8k
使用步骤:
- 1、创建BufferedInputStream对象,构造方法中传递FileInputStream对象;
- 2、使用BufferedInputStream 对象中的方法read,以字节的方式读取文件;
- 3、释放资源
4.3.2 BufferedOutputStream 字节缓冲输出流
java.io.BufferedOutputStream 字节缓冲输出流 extends OutputStream字节输出流
继承父类的成员方法:
- public void close() 关闭此输出流并释放与此流相关联的任何系统资源;
- public void flush() 刷新此输出流并强制任何缓冲的输出字节被写出;
- public void write(byte[] b) 将b.length()字节从指定的字节数组中写入到此输出流;
- public void write(byte[] b , int off, int len) 从指定的字节数组写入len字节,从偏移量off开始输出到此输出流;
- public abstract void write(int b) 将指定的字节输出流
构造方法:
- BufferedOutputStream(OutputStream out) 创建一个新的缓冲输出流,具有默认缓冲区大小
- BufferedOutputStream(OutputStream out, int size) 创建一个新的缓冲输出流,具有指定缓冲区大小
参数:
- OutputStream out 传递字节输出流,可以传递OutputStream 的任意子类对象,比如 FileOutputStream对象,这样就可以给FileOutputStream对象增加一个缓冲区
- int size 指定缓冲区的大小,不写使用默认值
使用步骤:
- 1、创建BufferedOutputStream对象,构造方法中传递FileOutputStream对象
- 2、使用BufferedOutputStream对象中的方法write,把数据写入到内存缓冲区中
- 3、使用BufferedOutputStream对象中的方法flush,把内存缓冲区中数据刷新到文件
- 4、释放资源(会先调用flush方法刷新数据到文件)
4.4 字符缓冲流
4.4.1 BufferedReader 字符缓冲输入流
java.io.BufferedReader:字符缓冲输入流 extends Reader字符输入流
继承父类的成员方法:
- int read() 读取单个字符
- int read(char[] cdbuf) 将字符读入数组
- void close() 关闭流并释放与之有关的所有资源
构造方法:
- BufferedReader(Reader in) 创建一个使用默认大小缓冲区的缓冲字符输入流
- BufferedReader(Reader in, int size) 创建一个指定大小缓冲区的缓冲字符输入流
参数:
- Reader in : 传递字符输入流,可以传递Reader 的任意子类对象,比如FileReader,缓冲流就会给 FileReader 增加一个缓冲区,以此提高FileReader读取文件的效率
- int size : 指定缓冲区的大小,也就是数组的长度,不指定则使用默认值
特有成员方法:
- String readLine() 读取一个文本行,一次可以读取一行数据,通过这些字符之一来判定某行是否终止: 换行\n 回车\r 或者回车加换行 \r\n ,返回包含该行内容的字符串,不包含任何终止符,如果已经到了流的末尾,则返回null
使用步骤:
- 1、创建BufferedReader对象,构造方法中传递FileReader对象
- 2、使用BufferedReader对象中的方法read|readLine,以字符的方式读取文件
- 3、释放资源
4.4.2 BufferedWriter 字符缓冲输出流
java.io.BufferedWriter:字符缓冲输出流 extends Writer:字符输出流
继承父类的成员方法:
- public abstract void close() :关闭此输出流并释放与此流相关联的任何系统资源
- public abstract void flush() :刷新此输出流并强制任何缓冲的输出字符被写出
- public void write(int c) :写出一个字符
- public void write(char[] cbuf) :将 b.length字符从指定的字符数组写出此输出流
- public abstract void write(char[] b, int off, int len) :从指定的字符数组写出 len字符,从偏移量 off开始输出到此输出流
- public void write(String str) :写出一个字符串
- public void write(String str, int off, int len) 写入字符串的某一部分
构造方法:
- BufferedWriter(Writer out) 创建一个使用默认大小输出缓冲区的缓冲字符输出流
- BufferedWriter(Writer out, int sz) 创建一个使用给定大小输出缓冲区的新缓冲字符输出流
参数:
- Writer out:传递字符输出流,可以传递Writer的任意的子类对象,我们可以FileWriter,缓冲流就会给FileWriter增加一个缓冲区,以此提高FileWriter写入文件的效率
- int sz:指定缓冲区的大小(数组长度),不指定使用默认值
特有成员方法:
- void newLine() 写入一个行分隔符。写一个换行符号,根据系统不同,而写不同的换行符号
- Windows系统里,每行结尾是 回车+换行 ,即 \r\n
- linux,Unix系统里,每行结尾只有 换行 ,即 \n
- Mac系统里,每行结尾是 回车 ,即 \r 。从 Mac OS X开始与Linux统一
使用步骤:
-
创建BufferedWriter对象,构造方法中传递FileWriter对象
-
使用BufferedWriter对象中的方法write,把数据写入到内存缓冲区中
-
使用BufferedWriter对象中的方法flush,把内存缓冲区中的数据刷新到文件中
-
释放资源(会先调用flush方法刷新数据)
5 转换流
5.1 目标
1、能够阐述编码表的意义
2、能够使用转换流读取指定编码的文本文件
3、能够使用转换流写入指定编码的文本文件
5.2 编码表
编码表就是生活中的文字和计算机中文字的对应关系。比如:
a0-> 97 -> 01100001 -> 存储到计算机中
中 -> 20013 -> 0010000000001011 -> 存储到计算机中
编码 :把能看懂的文字,转换成看不懂的文字,即字符-》字节
解吗: 把看不懂的文字,转换成能看懂的文字,即字节-》字符
常见的编码表:
- ASCⅡ 字符集:英文、数字、标点符号、计算机中文字的对应关系
- 0–>48 A–>65 a–>97
- ISO-8859-1 字符集:拉丁码表
- 拉丁码表,别名Latin-1,用于显示欧洲使用的语言,包括荷兰、丹麦、德语、意大利语、西班牙语等。
- ISO-8859-1使用单字节编码,兼容ASCII编码。不支持中文
- GBxxx字符集:国际
- GB就是国标的意思,是为了显示中文而设计的一套字符集。兼容ASCII表
- GB2312:简体中文码表。 7000多个简体汉字
- GBK:目前操作系统默认中文码表(简体,繁体),存储一个中文使用2个字节,21003个汉字
- GB18030:最新的中文码表。包含的文字最全(简体,繁体,少数民族,日韩文字)
- Unicode字符集 :万国码
- UTF-8:最常用的万国表,兼容所有国家的文字
- 编码规则:
- 128个US-ASCII字符,只需一个字节编码。
- 拉丁文等字符,需要二个字节编码。
- 大部分常用字(含中文),使用三个字节编码。
- 其他极少使用的Unicode辅助字符,使用四字节编码。
5.3 使用编码引出的问题
比如,使用FileReader 读取GBK编码的文件,会出现乱码,这是因为编码和解码不一致导致的;详细原因是:
FileReader 只能读取IDEA默认的编码文件 utf-8 编码,如果读取了GBK编码的文件,就不能正常解码,也就导致了编码和解码不一致
5.4 转换流的原理 -面试
在读取文件中的字节时,如果使用文件字符输入流FileReader 就只能查询IDEA默认的编码表utf-8,会出现乱码,而使用InputStreamReader,则会查询指定编码表,是不会出现乱码的;
同理,使用文件字符输出流FileWriter,只能将数据以IDEA默认的编码方式utf-8写出,而使用OutputStreamWriter可以查询指定的编码表,以指定的编码表写出;
5.5 InputStreamReader: 字符转换输入流
java.io.InputStreamReader:字符转换输入流 extends Reader:字符输出流
作用:
InputStreamReader 是字节流通向字符流的桥梁:它使用指定的 charset 读取字节并将其解码为字符。解码:字节==>字符
父类的共性成员方法:
- int read() 读取单个字符。
- int read(char[] cbuf) 将字符读入数组。
- void close() 关闭该流并释放与之关联的所有资源
构造方法:
- InputStreamReader(InputStream in)创建一个使用默认字符集的 InputStreamReader
- InputStreamReader(InputStream in, String charsetName) 创建使用指定字符集的 InputStreamReader
参数:
- InputStream in:传递字节输入流,可以传递InputStream的任意的子类对象(读取文件中的字节)
- String charsetName:传递编码表名称,不区分大小写的可以传递GBK(gbk),UTF-8(utf-8)…,不写默认使用IDEA设置的编码(UTF-8)
使用步骤(重点):
- 创建InputStreamReader对象,构造方法中传递FileInputStream对象和指定的编码表名称
- 使用InputStreamReader对象中的方法read读取文件
- 释放资源
注意:
InputStreamReader构造方法中指定的编码表名称必须和文件的编码一致,否则会出现乱码
5.6 OutputStreamWriter: 字符转换输出流
java.io.OutputStreamWriter:字符转换输出流 extends Writer:字符输出流
作用: 搞清楚输入输出对字符和字节是如何操作的,不要搞反了
OutputStreamWriter 是字符流通向字节流的桥梁:可使用指定的 charset 将要写入流中的字符编码成字节.编码:字符==>字节
继承自父类共性的成员方法:
- public abstract void close() :关闭此输出流并释放与此流相关联的任何系统资源
- public abstract void flush() :刷新此输出流并强制任何缓冲的输出字符被写出
- public void write(int c) :写出一个字符
- public void write(char[] cbuf) :将 b.length字符从指定的字符数组写出此输出流
- public abstract void write(char[] b, int off, int len) :从指定的字符数组写出 len字符,从偏移量 off开始输出到此输出流
- public void write(String str) :写出一个字符串
- public void write(String str, int off, int len) 写入字符串的某一部分
构造方法:
- OutputStreamWriter(OutputStream out)创建使用默认字符编码的 OutputStreamWriter
- OutputStreamWriter(OutputStream out, String charsetName) 创建使用指定字符集的 OutputStreamWriter
参数:
- OutputStream out:传递字节输出流,可以传递OutputStream的任意子类对象(把字符转换之后的字节写入到文件中)
- String charsetName:传递编码表名称,不区分大小写的可以传递GBK(gbk),UTF-8(utf-8)…,不写默认使用IDEA设置的编码(UTF-8)
使用步骤(重点):
- 创建OutputStreamWriter对象,构造方法中传递FileOutputStream对象和指定的编码名称
- 使用OutputStreamWriter对象中的方法write,把数据写入到内存缓冲区中(字符==>字节)
- 使用OutputStreamWriter对象中的方法flush,把内存缓冲区中的数据,刷新到文件中(使用字节输出流把字节写入到文件)
- 释放资源(会先调用flush方法刷新数据)
5.7 实例:转换文件的编码
实例 : 将GBK编码的文本文件,转换为UTF-8编码的文本文件
分析步骤:
1、创建InputStreamReader对象,构造方法中传递FileInputStream对象和GBK编码名称
2、创建OutputStreamWriter对象,构造方法中传递FIleOutputStream对象和UTF-8编码名称
3、使用InputStreamReader对象中的方法read,读取GBK编码的文件
4、使用OutputStreamWriter对象中的方法write,把读取到的数据,以UTF-8编码,写入到内存缓冲区中
5、释放资源(会先调用flush方法刷新数据到文件中)
public class Demo04Test {
public static void main(String[] args) throws IOException {
//1.创建InputStreamReader对象,构造方法中传递FileInputStream对象和GBK编码名称
InputStreamReader isr = new InputStreamReader(new FileInputStream("day22\\gbk.txt"),"GBK");
//2.创建OutputStreamWriter对象,构造方法中传递FIleOutputStream对象和UTF-8编码名称
//OutputStreamWriter osw = new OutputStreamWriter(new FileOutputStream("day22\\utf-82.txt"),"UTF-8");
//OutputStreamWriter osw = new OutputStreamWriter(new FileOutputStream("day22\\utf-83.txt"));//不写编码名称默认UTF-8
FileWriter osw = new FileWriter("day22\\utf-84.txt");
//3.使用InputStreamReader对象中的方法read,读取GBK编码的文件
int len = 0;
while ((len = isr.read())!=-1){
//4.使用OutputStreamWriter对象中的方法write,把读取到的数据,以UTF-8编码,写入到内存缓冲区中
osw.write(len);
}
//5.释放资源(会先调用flush方法刷新数据到文件中)
osw.close();
isr.close();
}
}
5.8 总结
什么时候使用FileReader和FileWriter(读取字符的便捷类): 读写的文件都是IDEA默认编码utf-8的文件;
什么时候使用InputStreamReader和OutputStreamWriter: 读写的文件不是IDEA默认编码utf-8的文件;
6 序列化流
6.1 目标
1、能够使用序列化流写出对象到文件
2、能够使用反序列化流读取文件到程序中
6.2 序列化流和反序列化流的概述
序列化流:
把对象以流的方式写入到文件中保存,叫写对象,也叫对象的序列化,写入的不仅仅包含字符,还包含一些其他内容,所以得使用字节流,一般使用ObjectOutputStream 对象的输出流,也叫对象的序列化流;
反序列化流:
把文件中保存的对象,以流的方式读取出来使用,叫读对象,也叫对象的反序列化,读取的数据不仅仅是字符,还有一些其他的内容,所以得使用字节流,一般使用ObjectInputStream 对象的输入流,也叫对象的反序列化流;
我们将对象序列化到文件中,这个时候文件保存的都是字节,如果直接打开,可能会出现乱码,这事因为没有以对应的编码方式打开,也就是解码的方式不对;
6.3 ObjectOutputStream 对象的序列化流
java.io.ObjectOutputStream:对象的序列化流 extends OutputStream:字节输出流
作用: 把对象以流的方式写入到文件中保存
构造方法:
- ObjectOutputStream(OutputStream out) 创建写入指定 OutputStream 的 ObjectOutputStream
- 参数: OutputStream out:传递字节输出流,可以传递OutputStream的任意子类对象
特有的成员方法:
- void writeObject(Object obj) 将指定的对象写入 ObjectOutputStream
使用步骤:
- 1、创建ObjectOutputStream对象,构造方法中传递FileOutputStream对象
- 2、使用ObjectOutputStream对象中的方法writeObejct,把对象写入到文件中
- 3、释放资源
注意:NotSerializableException
在进行序列化|反序列化的时候,会抛出NotSerializableException:没有序列化异常
类通过实现 java.io.Serializable 接口以启用其序列化功能。未实现此接口的类将无法使其任何状态序列化或反序列化。
Serializable接口是一个标记型接口,类实现了Serializable接口,接口就会给类添加上一个标记
当我们进行序列化和反序列化的时候,会检查类上是否有标记,有则序列化反序列化成功,没有会抛出异常
比如:
去市场买肉=>肉上会有一个检查合格的章=>放心购买=>买回来肉怎么吃=>随意:炖,炒,刷…
类=>添加上了标记=>放心进行序列化和反序列化了=>类做不做其他的用途没有影响
6.4 ObjectInputStream 对象的反序列化流
java.io.ObjectInputStream:对象的反序列化流 extends InputStream:字节输入流
作用: 把文件中保存的对象以流的方式读取出来使用
构造方法:
- ObjectInputStream(InputStream in) 创建从指定 InputStream 读取的 ObjectInputStream
- 参数: InputStream in:传递字节输入流,我们可以传递InputStream的任意子类对象
特有的成员方法:
- Object readObject() 从 ObjectInputStream 读取对象,readObject方法声明抛出了两个异常对象:IOException, ClassNotFoundException
使用步骤:
- 1、创建ObjectInputStream对象,构造方法中传递FileInputStream对象
- 2、使用ObjectInputStream对象中的方法readObject,读取文件中保存的对象
- 3、释放资源
注意:
- 反序列化有两个前提:
- 1.类实现Serializable接口
- 2.类有对应的class文件(Person.class)
public class Demo02ObjectInputStream {
public static void main(String[] args) throws IOException, ClassNotFoundException {
//1.创建ObjectInputStream对象,构造方法中传递FileInputStream对象
ObjectInputStream ois = new ObjectInputStream(new FileInputStream("day22\\person.txt"));
//2.使用ObjectInputStream对象中的方法readObject,读取文件中保存的对象
Object obj = ois.readObject();
System.out.println(obj);
Person p = (Person)obj;
p.setAge(20);
System.out.println(p);
//3.释放资源
ois.close();
}
}
6.5 瞬态关键字 transient
transient:瞬态关键字
被transient关键字修饰的变量不能被序列化
static:静态关键字
被static修饰的成员属于类,不属于某一个对象,被所有的对象所共享使用
被static修饰的成员,也不能序列化,序列化的是对象
6.6 序列号冲突异常-面试
编译器javac.exe会把自定义类文件,比如Person.java ,编译生成Person.class文件,Person类实现了 Serializable 接口,就会根据类的定义给Person.class文件添加一个序列号,但是每次修改了类的定义,那么就会给Person.class文件,重新编译生成一个新的序列号,反序列化的时候,就会使用Person.class文件中的序列号和Person.txt文件中的序列号进行比较,如果一样,反序列化成功,如果不一样,则抛出序列化冲突异常:InvalidClassException
解决方案:无论是否对类的定义进行了修改,都不重新生成新的序列号,我们可以手动的给类添加一个序列号来实现,这样无论类是否修改, 序列号都是固定写死的常量值, 值是不会改变的, 也就不会抛出异常了
实现步骤:
-
1、该类实现了Serializable接口,这是前提;
-
2、在类的成员变量位置,新增一个 serialVersionUID 字段,并使用final static修饰,数据类型使用long,以此来显式声明其字节的serialVersionUID;
6.7 实例:序列化集合
如果要往文件中存储多个对象, 不建议使用多次writeObject方法写多个对象(读取多次),建议创建一个集合, 把对象保存到集合中, 对集合对象进行序列化和反序列化(读写一次就行了)
import java.io.*;
import java.util.ArrayList;
public class Demo03Test {
public static void main(String[] args) throws IOException, ClassNotFoundException {
show02();
}
/*
序列化多个对象,反序列化多个对象,使用集合
*/
private static void show02() throws IOException, ClassNotFoundException {
ArrayList<Person> list = new ArrayList<>();
list.add(new Person("张三",18));
list.add(new Person("李四",19));
list.add(new Person("王五",20));
list.add(new Person("赵六",21));
list.add(new Person("田七",22));
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("day22\\list02.txt"));
//序列化集合对象
oos.writeObject(list);
oos.close();
ObjectInputStream ois = new ObjectInputStream(new FileInputStream("day22\\list02.txt"));
//读取文件中保存的集合对象,就一个,读取一次
Object obj = ois.readObject();
//遍历集合==>向下转型==>把Object类型转换为ArrayList<Person>
ArrayList<Person> list02 = (ArrayList<Person>)obj;
for (Person p : list02) {
System.out.println(p);
}
}
}
7 打印流
java.io.PrintStream:字节打印流 extends OutputStream:字节输出流
特点:
- 1.PrintStream 为其他输出流添加了功能,使它们能够方便地打印各种数据值表示形式。PrintStream流中有两个特有的方法:print,println
- 2.与其他输出流不同,PrintStream 永远不会抛出 IOException,创建对象的时候,传递路径不存在,可能会抛出文件找不到异常
- 3.PrintStream叫打印流,只负责输出(打印),不能读取
构造方法:
- PrintStream(File file) 打印流的目的地是一个文件
- PrintStream(OutputStream out) 打印流的目的地是一个字节输出流
- PrintStream(String fileName) 打印流的目的地是一个文件路径
成员方法:
- 1、继承自父类OutputStream的共性的成员方法
- write:使用write写数据,查看的时候,会查询编码表
- public void close() :关闭此输出流并释放与此流相关联的任何系统资源
- public void flush() :刷新此输出流并强制任何缓冲的输出字节被写出
- public void write(byte[] b) :将 b.length字节从指定的字节数组写入此输出流
- public void write(byte[] b, int off, int len) :从指定的字节数组写入 len字节,从偏移量 off开始输出到此输出流
- public abstract void write(int b) :将指定的字节输出流
- 自己特有的成员方法: print, println写数据,原样输出
- void print(Object x) 可以写任意类型数据,不换行
- void println(Object x) 可以写任意类型的数据,换行
使用步骤(重点)::
- 1.创建PrintStream对象,构造方法中绑定要输出的目的地
- 2.使用PrintStream对象中的方法write,print,println,把数据写入到文件中
- 3.释放资源
public class Demo01PrintStream {
public static void main(String[] args) throws FileNotFoundException {
//1.创建PrintStream对象,构造方法中绑定要输出的目的地
PrintStream ps = new PrintStream("day22\\print1.txt");
//2.使用PrintStream对象中的方法write,print,println,把数据写入到文件中
//继承自父类OutputStream的共性的成员方法write:使用write写数据,查看的时候,会查询编码表
ps.write(97);//a
//自己特有的成员方法:print,println写数据,原样输出
ps.println(97);//97
ps.println('@');//@
ps.println(true);//true
ps.println(1.1);//1.1
ps.println("aaa");//aaa
//3.释放资源
ps.close();
//打印字符数组不是地址值,是数组中的元素 void println(char[] x) 打印字符数组(字符串底层就是字符数组)
char[] chars = {'a','b','c'};
System.out.println(chars);//abc
System.out.println("abc");//abc
}
}
修改输出语句的目的地为打印流的目的地
import java.io.FileNotFoundException;
import java.io.PrintStream;
/*
修改输出语句的目的地为打印流的目的地
使用System类中的方法setOut,修改输出语句的目的地(控制台)为打印流的目的地
static void setOut(PrintStream out) 重新分配“标准”输出流。
作用:不让输出语句在控制台输出了,在打印流的目的地文件中输出
*/
public class Demo02PrintStream {
public static void main(String[] args) throws FileNotFoundException {
System.out.println("输出语句的目的地默认是在控制台输出!");
PrintStream ps = new PrintStream("day22\\print2.txt");
System.setOut(ps);//修改输出语句的目的地(控制台)为打印流的目的地
System.out.println(1);
System.out.println(2);
System.out.println(3);
System.out.println("输出语句的目的地不是控制台了,是print2.txt文件中");
}
}