特殊流
学习了基本的一些流,作为 io 流的入门,今天见识一些更强大的流,比如能够高效读写的【缓冲流】,能够转换编码的【转换流】,能够持久化存储对象的【序列化流】等等
这些功能更为强大的流,都是在基本的流对象基础之上创建而来的,就像穿上铠甲的武士,相当于是对基本流对象的一种增强
缓冲流
概述
缓冲流也叫高效流,是对四个基本的 FileXXX 流的增强,所以也是四个流,按照数据类型分类:
- 字节缓冲流:BufferedInputstream,BufferedOutputStream
- 字符缓冲流:BufferedReader,BufferedWriter
缓冲流的基本原理:
在创建流对象时,会创建一个内置的默认大小的【缓冲区数组】,通过缓冲区读写,减少系统 io 次数,从而提高读写的效率
字节缓冲流
构造方法:
public BufferedInputstream(InputStream in);
- 创建一个新的缓冲输入流
public BuffeedOutputStream(OutputStream out);
- 创建一个新的缓冲输出流
字节缓冲输出流
给基本的字节输出流增加一个缓冲区【数组】,提高基本的字节输入流的读取效率
BufferedOutputStream【字节缓冲输出流】
java.io.BufferedOutputStream 继承了 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);
- 将指定的字节输出流
构造方法:
public BuffeedOutputStream(OutputStream out);
- 创建一个新的缓冲输出流,以将数据写入指定的底层输出流
public BufferedOutputStream(OutputStream out,int size);
- 创建一个新的缓冲输出流,以将具有指定缓冲区大小的数据写入指定的底层输出流
- 参数:
- Outputstream out:字节输出流
- 我们可以传递 FileOutputStream,缓冲流会给 FileOutputStream 增加一个缓冲区,提高 FileOutputStream 的写入效率
- int size:指定缓冲流内部缓冲区的大小,不指定则使用默认大小
- Outputstream out:字节输出流
使用步骤【重点】:
- 创建 FileOutputstreom 对象,构造方法中绑定要输出的目的地
- 创建 BufferedOutputStream 对象,构造方法中传递 FileOutputStream 对象,提高 FileOutputStream 对象效率
- 使用 BufferedOutputStream 对象中的 write 方法,把数据写入到内部缓冲区中
- 使用 BufferedOutputStream 对象中的 flush 方法,把内部缓冲区中的数据刷新到文件中
- 释放资源(会先调用 flush 方法刷新数据,所以第四步可以省略)
字节缓冲输入流
BufferedInputStream【字节缓冲输入流】
java.io.BufferedInputStream 继承自 InputStream 抽象类
继承自父类的成员方法:
public void close();
- 关闭此输入流,并释放与此流相关联的任何系统资源
public int read(byte[] b);
- 从输入流中读取一些字节数,并将它们存储到字节数组 b 中
public abstract int read();
- 从输入流读取数据的下一个字节
构造方法:
public BufferedInputstream(InputStream in);
- 创建一个 BufferedInputstream 并保存其参数,即输入流 in,以便将来使用
public BufferedInputStream(InputStream in, int size);
- 创建具有指定缓冲区大小的 BufferedInputStream 并保存其参数,即输入流
- 参数:
- InputStream in:字节输入流
- 我们可以传递 FileInputstream,缓冲流会给 FileInputstream 增加一个缓冲区,提高 FileInputStream 的读取效率
- int size:指定缓冲流内部缓冲区的大小,不指定则使用默认大小
- InputStream in:字节输入流
使用步骤【重点】:
- 创建 FileInputStream 对象,构造方法中绑定要读取的数据源
- 创建 BufferedInputStream 对象,构造方法中传递 FileInputStream 对象,提高 FileInputstream 对象的读取效率
- 使用 BufferedInputStream 对象中的 read 方法,读取文件
- 释放资源
字符缓冲流
构造方法
●public BufferedReader(Reader in) :创建一个新的缓冲输入流。
●public BufferedWriter(Wciter out) :创建一个新的缓冲输出流。
字符缓冲输出流
BufferedWriter【字符缓冲输出流】
java.io.BufferedWriter 继承自 Writer 抽象类
继承自父类的共性成员方法:
public void write(int c);
- 写入单个字符
public void write(char[] cbuf);
- 写入字符数组
public abstract void write(char[] cbuf, int off, int len);
- 写入字符数组的某一部分
- 参数:
- off:数组的开始索引
- len:写的字符个数
public void write(String str);
- 写入字符串
public void write(String str, int off, int len);
- 写入字符串的某一部分
- 参数:
- off:字符串的开始索引
- len:写的字符个数
public void flush();
- 刷新该流的缓冲
public void close();
- 关闭此流,但要先刷新它
构造方法:
public BufferedWriter(Writer out);
- 创建一个使用认大小输出缓冲区的缓冲字符输出流
public Bufferedvriter(Writer out, int size);
- 创建一个使用给定大小输出缓冲区的新缓冲字符输出流
- 参数:
- Writer out:字符输出流
- 我们可以传递 FileWriter,缓冲流会给 FileWriter 增加一个缓冲区,提高 FileWriter 的写入效率
- int size:指定缓冲流内部缓冲区的大小,不指定则使用默认大小
- Writer out:字符输出流
特有的成员方法【重要】:
public void newLine();
- 写入一个行分隔符,会根据不同的操作系统,获取不同的行分隔符
-
- windows:
/r/n
- linux:
/n
mac:/r
- windows:
使用步骤【重要】:
- 创建字符缓冲输出流对象,构造方法中传递字符输出流
- 调用字符缓冲输出流中的 write 方法,把数据写入到内存缓冲区中
- 调用字符缓冲输出流中的 flush 方法,把内存缓冲区中的数据,刷新到文件中
- 释放资源
字符缓冲输入流
BufferedReader 【字符缓冲输入流】
java.io.BufferedReader 类继承自 Reader 抽象类
继承自父类的共性成员方法:
public void close();
- 关闭此流,并释放与此流相关联的任何系统资源
public int read();
- 从输入流读取一个字符
public int read(char[] cbuf);
- 从输入流中读取一些字符,并将它们存储到字符数组 cbuf 中
构造方法:
public BufferedReader(Reader in);
- 创建一个使用理认大小输入缓冲区的缓冲字符输入流
public BufferedReader(Reader in, int size);
- 创建一个使用指定大小输入缓冲区的缓冲字符输入流
- 参数:
- Reader in:字符输入流
- 我们可以传递 FileReader,缓冲流会给 FileReader 增加一个缓冲区,提高 FileReader 的读取效率
- int size:指定缓冲流内部缓冲区的大小,不指定则使用默认大小
- Reader in:字符输入流
特有的成员方法【重要】:
public String readLine();
- 读取一个文本行
- 读取一行数据,通过终止符号作为判断依据
- 行的终止符号:通过下列字符之一即可认为某行已终止
- 换行
/n
、回车/r
、或回车后直接跟着换行/r/n
- 换行
- 行的终止符号:通过下列字符之一即可认为某行已终止
- 返回值:
- 包含该行内容的字符串,不包合任何行终止符,如果已到达流末尾,则返回【null】
使用步骤【重要】:
- 创建字符缓冲输入流对象,构造方法中传递字符输入流
- 使用字符缓冲输入流对象中的 read / readLine 方法读取文本
- 释放资源
注意
- 关闭缓冲流时,会将传入的流对象一同关闭,所以只关闭缓冲流即可
- 使用缓冲流会相比不使用缓冲流速度快上很多倍
转换流
字符编码和字符集
字符编码
计算机中储存的信息都是用二进制数表示的,而我们在幕上看到的数字、标点符号、汉字等字符是二进制数转换之后的结果
按照某种规则,将字符存储到计算机中,称为编码
反之,将存储在计算机中的二进制数按照某种规则解析显示出来,称为解码
比如说
按照 A 规则存储,同样按照 A 规则解析,那么就能显示正确的文本
反之,按照 A 规则存储,按照 B 规则解析,就会导致乱码现象
- 编码:字符(能看懂的)-> 字节(看不懂的)
- 解码:字节(看不懂的)-> 字符(能看懂的)
- 字符编码(Character Encoding):就是一套自然语言的字符与二进制数之间的对应规则
- 编码表:生活中文字和计算机中二进制的对应规则
字符集
字符集( Charset):也叫编码表。是一个系统支持的所有字符的集给,包括各国家字、标点符号、图形符
号数字等
计算机要准确的存储和识别各种字符集符号,需要进行字符编码,一字符集必然至少有一字符编码
常见字符有 ASCII 字符集、GBK 字符集、 Unicode 字符集等
可见,当指定了编码,它所对应的字符集自然就指定了,所以编码才是我们最终要关心的
-
ASCII 字符集:
- ASCII(American Standard Code for Information Interchange,美国信息交换标准代码)是基于拉丁字母的一电脑编码系统,用于显示现代英语,主要包括控制字符(回车键、退格、换行键等)和可显字符(英文大小写字符阿拉伯数字和西文符号)
- 基本的 ASCII 字符集,使用7位(bits )表示一个字符,共 128 字符
- ASCII的扩展字符集使用8位(bits)表示一个字符,第一位表示正负,1 就是负数,共256字符,便支持欧洲常用字符
-
IS0-8859-1 字符集:
- 拉丁码表,别名 Latin-1,用于显示欧洲使用的语言,包括荷兰、丹麦德语、意大利语、西班牙语等
- ISO-8859-1 使用单字节编码,兼容 ASCII 编码
-
GBxxx 字符集:
- GB 就是国标的意思,为了显示中文而设计的一字符集
- GB2312:简体中文码表
- 一个小于 127 的字符的意义与原来相同。但两个大于 127 的字符连在一起时,就表示一个汉字,这样大约可以组合了包含 7000 多个简体汉字
- 此外数学符号、罗马希腊的字母、日文的假名们都编进去了,连在 ASCII 里本来就有的数字、标点、字母都统统重新编了两个字节长的编码,这就是常说的 “ 全角 ” 字符,而原来在 127 号以下的那些就叫 “ 半角 ” 字符了
- GBK:最常用的中文码表。在 GB2312 标准基础上的扩展规范,使用了双字节编码方案,共收录了21003个汉字,完全兼容 GB2312 标准,同时支持繁体汉字以及日韩汉字等
- GB18030:最新的中文码表。收录汉字70244个,采用多字节编码,每个字可以由1个、2个或4个字节组
成。支持中国国内少数民族的文字,同时支持繁体汉字以及日韩汉字等
-
Unicode 字符集:
- Unicode 编码系统为表达任意语言的任意字符而设计,是业界的一种标准,也称为统一码、标准万国码
- 它最多使用4个字节的数字来表达每个字母、符号、或者文字
- 有三种编码方案,UTF-8、 UTF-16 和 UTF-32。最为常用的 UTF-8 编码
- UTF-8 编码:
- 可以用来表示 Unicode 标准中任何字符,它是电子邮件、网页及其他存储或传送文字的应用中,优先采用的编码
- 互联网工程工作小组(IETF)要所有互联网协议都必须支持 UTF-8 编码。所以,开发 Web 应用也要使用 UTF-8 编码
- 它使用一至四个字节为每个字符编码,编码规则:主要还是使用三个字节编码
- 128 个 US-ASCII 字符,只需一个字节编码
- 拉丁文等字符,需要二个字节编码
- 大部分常用字(合中文),使用三个字节编码
- 其他极少使用的 Unicode 辅助字符,使用四字节编码
编码引出的问题
在 IDEA中,使用FileReader
读取项目中的文本文件。由于 IDEA 的设置,都是默认的 UTF-8 编码,所以没有任何问题
但是,当读取 Windows 系统中创建的文本文件时,由于 Windows 系统的默认是 GBK 编码,就会出现乱码
InputStreamReader
转换流java.io.InputStreamReader
类,是 Reader 的子类
是从字节流到字符流的桥梁。它读取字节,并使用指定的字符集将其解码为字符(解码:把看不懂的变成能看懂的)
它的字符集可以由名称指定,也可以接受平台的默认字符集
继承自父类的共性成员方法:
public void close();
- 关闭此流,并释放与此流相关联的任何系统资源
public int read();
- 从输入流读取一个字符
public int read(char[] cbuf);
- 从输入流中读取一些字符,并将它们存储到字符数组 cbuf 中
构造方法
InputstreamReader(Inputstream in);
- 创建一个使用默认字符集的字符流
- IDEA 中默认使用 UTF-8
InputstreamReader(InputStream in, String charsetName);
- 上创建一个指定字符集的字符流
- 参数:
- Inputstream in:字节输入流,用来读取文件中保存的字节
- String charsetName:指定的编码表名称
- 不区分大小写,可以是
urf-8/UTF-8
,GBK/gbk
…… - 不指定使用默认字符集
- 不区分大小写,可以是
转换流原理
使用步骤【重要】:
- 创建 InputStreamReader 对象,构造方法中传递字节输入流和指定的编码表名称
- 使用 InputStreamReader 对象中的 read 方法读取文件
- 释放资源
注意事项:
构造方法中指定的【编码表名称】要和文件的【编码】相同,否则会发生乱码
OutputStreamWriter
转换流java.io.OutputStreamwriter
类,是 Writer 的子类
是从字符流到字节流的桥梁。使用指定的字符集将字符编码为字节(编码:把能看懂的变成看不懂)
它的字符集可以由名称指定,也可以接受平台的默认字符集
继续自父类的共性成员方法:
public void write(int c);
- 写入单个字符
public void write(char[] cbuf);
- 写入字符数组
public abstract void write(char[] cbuf, int off, int len);
- 写入字符数组的某一部分
- 参数:
- off:数组的开始索引
- len:写的字符个数
public void write(String str);
- 写入字符串
public void write(String str, int off, int len);
- 写入字符串的某一部分
- 参数:
- off:字符串的开始索引
- len:写的字符个数
public void flush();
- 刷新该流的缓冲
public void close();
- 关闭此流,但要先刷新它
构造方法
public OutputStreamWriter(OutputStream in);
- 创建一个使用默认字符集的字符流
- IDEA 中默认使用 UTF-8
public OutputStreamWriter(OutputStream in, String charsetName);
- 创建一个指定字符集的字符流
- 参数:
- OutputStream in:字节输出流,可以用来写转换之后的字节到文件中
- String charsetName:指定的编码表名称
- 不区分大小写,可以是
urf-8/UTF-8
,GBK/gbk
…… - 不指定使用默认字符集
- 不区分大小写,可以是
转换流原理
使用步骤【重要】:
- 创建 OutputStreamWriter 对象,构造方法中传递字节输出流和指定的编码表名称
- 使用 OutputStreamWriter 对象中的 write 方法,把字符转换为字节存储缓冲区中(编码)
- 使用 OutputStreamWriter 对象中的 flush 方法,把内存缓冲区中的字节刷新到文件中(使用字节流写字节的过程)
- 释放资源
序列化流
序列化
概述
Java 提供了一种对象序列化的机制
用一个字节序列可以示一个对象,该字节序列包含该对象的数据、对象的类型和对象中存储的属性等信息。字节【序列】写出到文件之后,相当于文件中执保存一个对象的信息
反之,该字节序列还可以从文件中读取回来,重构对象,对它进行【反序列化】
对象的数据、对象的类型和对象中存储的数据信息,都可以用来在内存中创建对象
- 对象的序列化:把对象以流的方式,写入到文件中保存,也叫写对象
- 对象中包含的不仅仅是字符,所以要使用字节流
- 对象的反序列化:把文件中保存的对象,以流的方式读取出来,也叫读对象
- 读取的文件保存的都是字节,所以要使用字节流
图理解序列化:
序列化操作
- 要进行【序列化】和【反序列化】的类必须实现
Serializable
接口,就会给类添加一个标记 - 当我们进行【序列化和反序列化】的时候,就会检测类上是否有这个标记
- 有:就可以【序列化和反序列化】
- 没有:就会抛出
NotSerializableException
异常
对象的序列化流
对象的序列化流【ObjectOutputStream】
java.io.ObjectOutputStream
类,是OutputStream
的子类
作用:把对象以流的方式写入到文件中保存
构造方法
public ObjectOutputStream(OutputStream out);
- 创建写入指定 OutputStream 的 ObjectOutputStream
- 参数:
- OutputStream out:字节输出流
特有的成员方法
public void writeObject(Object obj);
- 将指定的对象写入 ObjectOutputStream
使用步骤【重要】
- 创建 ObjectOutputStream 对象,构造方法中传递字节输出流
- 使用 ObjectOutputStream 对象中的 writeObject 方法,把对象写入到文件中
- 释放资源
序列化操作
一个对象要想序列化,必须满足两个条件:
- 该类必须实现
java.io.Serializable
接口- 拓展:
Serializable
接口也叫标记型接口,起到标记的作用,接口中并没有任何的内容 Serializable
是一个标记接口,不实现此接口的类将不会使任何状态序列化或反序列化,会抛出NotSerializableException
- 序列化和反序列化的时候,会抛出
NotSerializableException
(没有序列化异常) - 类通过实现
java.io.Serializable
接口以启用其序列化功能,未实现此接口的类将无法使其任何状态序列化或反序列化
- 拓展:
- 该类的所有属性必须是可序列化的。如果有一个属性是不需要可序列化的,则该属性必须注明是瞬态的,使用
transient
关键字修饰
对象的反序列化流
对象的反序列化流【ObjectInputStream】
java.io.ObjectInputStream
类,是InputStream
的子类
作用:把文件中保存的对象,以流的方式读取出来使用
构造方法
ObjectInputStream(InputStream in);
- 创建从指定 InputStream 读取的 ObjectInputStream
- 参数:
- InputStream in:字节输入流
特有的成员方法
public Object readObject();
- 从 ObjectInputstream 读取一个对象
- 该方法声明抛出了
ClassNotFoundException
(class 文件找不到异常),当不存在对象的 class 文件时抛出此异常
使用步骤【重要】
- 创建 ObjectInputstream 对象,构造方法中传递字节输入流
- 使用 ObjectInputstream 对象中的 readObject 方法,读取保存对象的文件
- 释放资源
- 使用读取出来的对象(打印)
反序列化操作
一个对象要想反序列化,必须满足两个条件:
- 该类必须实现
java.io.Serializable
接口- 拓展:
Serializable
接口也叫标记型接口,起到标记的作用,接口中并没有任何的内容 Serializable
是一个标记接口,不实现此接口的类将不会使任何状态序列化或反序列化,会抛出NotSerializableException
- 序列化和反序列化的时候,会抛出
NotSerializableException
(没有序列化异常) - 类通过实现
java.io.Serializable
接口以启用其序列化功能,未实现此接口的类将无法使其任何状态序列化或反序列化
- 拓展:
- 必须存在类对应的 class 文件
反序列化操作2
当 JVM 反序列化对象时,能找到 class 文件,但是 class 文件在序列化对象之后发生了修改,那么反序列化操作也会失败,抛出一个InvalidClassException
异常
发生这个异常的原因如下:
- 该类的序列版本号与从流中读取的类描述符的版本号不匹配
- 该类包含未知数据类型
- 该类没有可访问的无参数构造方法
Serializable
接口给需要序列化的类,提供了一个序列版本号——serialversionUID
该版本号的目的在于验证序列化的对象和对应类是否版本匹配
问题:
- 每次修改类的定义,都会给 class 文件生成一个新的序列号
解决方案:
- 无论是否对类的定义进行修改都不重新生成新的序列号
- 可以通过在需要进行序列化的类中手动规定序列号,保证在进行修改可以进行反序列化操作
格式在Serializable
接口规定:
- 可序列化类可以通过声明名为
serialversionUID
的字段显式声明其自己的序列号 - 该字段必须是静态(static)、最终(final)的 long 类型字段
transient 关键字
- static 关键字:静态关键字
- 静态优先于非静态加载到内存中(静态优先于对象进入到内存中)
- 被 static 修饰的成员变量不能被序列化的,序列化的都是对象
- transient 关键字:瞬态关键字
- 被 transient 修饰成员变量,不能被序列化
打印流
概述
平时我们在控制台打印输出,是调用 print 方法和 println 方法完成的
这两个方法都来自于java.io.PrintStream
类,该类能够方便地打印各种数据类型的值,是一种便捷的输出方式
打印流
打印流【PrintStream】
java.io.PrintStream
类是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);
- 将指定的字节输出流
特点
- 只负责数据的输出,不负责数据的读取
- 与其他输出流不同,
Printstream
永远不会抛出IOException
- 有特有的方法,
print
、println
void print(任意类型的值);
void println(任意类型的值并换行);
构造方法
public PrintStream(File file);
- 输出的目的地是一个文件
public PrintStream(OutputStream out);
- 输出的目的地是一个字节输出流
public PrintStream(String fileName);
- 输出的目的地是一个文件路径
改变打印流向
System.out
就是Printstream
类型的,只不过它的流向是系统规定的,打印在控制台上
不过,既然是流对象,我们就可以玩一个 “ 小把戏 ”,改变它的流向
使用System. setout
方法改变输出语句的目的地,改为参数中传递的打印流的目的地
static void setOut(PrintStream out );
- 重新分配标准 ” 输出流 “
注意
- 如果使用继承自父类的 write 方法写数据,那么查看数据的时候会查询编码表,97 -> a
- 如果使用自己特有的方法 print / println 方法写数据,写的数据原样输出,97 -> 97