上篇介绍了IO基础概念,IO流的基类、文件流等。此篇将介绍一些更强大的流,比如能够高效读写的缓冲流、能够转换编码的转换流、能够持久化存储对象的序列化流等等。
一、缓冲流
1.1 概述
缓冲流,也叫高效流。它的基本原理是在创建流对象时,会创建一个内置的默认大小的缓冲区数组,通过缓冲区读写,减少系统IO次数,从而提高读写的效率。
按数据类型分类:
-
-
-
字符缓冲流:
BufferedReader
,
-
1.2 字节缓冲流
构造方法
-
-
-
public BufferedOutputStream(OutputStream out)
-
1.3 字符缓冲流
构造方法
-
-
public BufferedReader(Reader in)
:创建一个新的缓冲输入流。 -
public BufferedWriter(Writer out)
: 创建一个新的缓冲输出流。
-
特有方法
字符缓冲流的基本方法与普通字符流调用方式一致,不再阐述,我们来看它们具备的特有方法。
-
-
BufferedReader:
public String readLine()
: 读取一个文本行。
-
public class BufferedReaderDemo { public static void main(String[] args) throws IOException { // 创建流对象 BufferedReader br = new BufferedReader(new FileReader("in.txt")); // 定义字符串,保存读取的一行文字 String line = null; // 循环读取,读取到最后返回null while ((line = br.readLine()) != null) { System.out.print(line); System.out.println("------"); } // 释放资源 br.close(); } }
-
-
BufferedWriter:
public void newLine()
: 写入一个行分隔符。行分隔符字符串由系统属性 line.separator 定义,并且不一定是单个新行 (' ') 符。
-
public class BufferedWriterDemo throws IOException { public static void main(String[] args) throws IOException { // 创建流对象 BufferedWriter bw = new BufferedWriter(new FileWriter("out.txt")); // 写出数据 bw.write("你好啊"); // 写出换行 bw.newLine(); bw.write("老铁"); bw.newLine(); // 释放资源 bw.close(); } }
二、转换流
2.1 字符编码和字符集
2.1.1 字符编码 Character Encoding
概述:就是一套自然语言的字符与二进制数之间的对应规则。
-
- 编码:按照某种规则,将字符转换成二进制数存储到计算机中。字符到字节的过程。
- 解码:将存储在计算机中的二进制数按照某种规则显示出来。字节到字符的过程。
2.1.2 字符集 Charset
概述:字符集也叫编码表,是一个系统支持的所有字符的集合,包括各国家文字、标点符号、图形符号、数字等。
编码表:生活中文字和计算机中二进制的对应规则。
计算机要准确的存储和识别各种字符集符号,需要进行字符编码,一套字符集必然至少有一套字符编码。常见字符集有ASCII字符集、GBK字符集、Unicode字符集等。
-
-
ASCII(American Standard Code for Information Interchange,美国信息交换标准代码)是基于拉丁字母的一套电脑编码系统,用于显示现代英语,主要包括控制字符(回车键、退格、换行键等)和可显示字符(英文大小写字符、阿拉伯数字和西文符号)。
-
基本的ASCII字符集,使用7位(bits)表示一个字符,共128字符。ASCII的扩展字符集使用8位(bits)表示一个字符,共256字符,方便支持欧洲常用字符。
-
-
ISO-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字符,只需一个字节编码。
-
拉丁文等字符,需要二个字节编码。
-
大部分常用字(含中文),使用三个字节编码。
-
-
-
2.2
2.2.1 概述
2.2.2 构造方法
-
-
-
InputStreamReader(InputStream in, String charsetName)
-
2.3
2.3.1 概述
2.3.2 构造方法
-
-
-
OutputStreamWriter(OutputStream in, String charsetName)
-
三、序列化流
3.1 概述
Java 提供了一种对象序列化的机制。用一个字节序列可以表示一个对象,该字节序列包含该对象的数据
、对象的类型
和对象中存储的属性
等信息。字节序列写出到文件之后,相当于文件中持久保存了一个对象的信息。反之,该字节序列还可以从文件中读取回来,重构对象,对它进行反序列化。对象的数据
、对象的类型
和对象中存储的数据
信息,都可以用来在内存中创建对象。
序列化:把对象转换为字节序列,写入到文件中的过程称为对象的序列化,也叫写对象。也就是将对象写入到IO流中,是一种用来处理对象流的机制。(所谓对象流也就是将对象的内容进行流化。)
序列化的作用:是为了解决在对对象流进行读写操作时所引发的问题。序列化机制允许将实现序列化的Java对象转换为字节序列,这些字节序列可以保存在磁盘上,或通过网络传输,以达到以后恢复成原来的对象。序列化机制使得对象可以脱离程序的运行而独立存在。
实现序列化的步骤:
1. 相关对象实现Serializable接口;
2. 创建一个
实现对象序列化需要满足的条件以及注意点:
- 要对一个对象序列化,这个对象就需要实现Serializable接口,如果这个对象中有一个变量是另一个对象的引用,则引用的对象也要实现Serializable接口,这个过程是递归的。Serializable接口中没有定义任何方法,只是作为一个标记来指示实现该接口的类可以进行序列化。
该类的所有属性必须是可序列化的。如果有一个属性不需要可序列化,或者因为其数据的特性决定了它会经常变化而不具有可持久性,则该属性必须注明是瞬态的,使用
transient
反序列化:将序列化过程中所生成的字节序列,读取出来转换成对象的过程成为对象的反序列化,也叫读对象。
实现反序列化的步骤:
1. 创建一个
3.2
-
-
public ObjectOutputStream(OutputStream out)
: 创建一个指定输出流的序列化流。
-
写出对象方法
3.3 ObjectInputStream类
ObjectInputStream反序列化流,将之前使用ObjectOutputStream序列化的原始数据恢复为对象。
构造方法
读取对象方法
注意:1. 对于JVM可以反序列化对象,它必须是能够找到class文件的类。如果找不到该类的class文件,则抛出一个
ClassNotFoundException
异常。2. 当JVM反序列化对象时,能找到class文件,但是class文件在序列化对象之后发生了修改,那么反序列化操作也会失败,抛出一个
InvalidClassException
异常(序列化冲突异常)。发生InvalidClassException异常的原因如下:
该类包含未知数据类型
如何解决InvalidClassException异常,来保证对象前后的兼容性?
3.4 序列化版本号serialVersionUID
java序列化提供了一个private static final long serialVersionUID 的序列化版本号,只有版本号相同,即使更改了序列化属性,对象也可以正确被反序列化回来。
注意:序列化版本号可自由指定。
如果不指定
① JVM会根据类信息自己计算一个版本号,这样随着class的升级,就无法正确反序列化;
② 不利于jvm间的移植,可能class文件没有更改,但不同jvm可能计算的规则不一样,这样也会导致无法反序列化。
什么情况下需要修改serialVersionUID呢?
-
- 如果只是修改了方法,反序列化不受影响,则无需修改版本号;
- 如果只是修改了静态变量,瞬态变量(transient修饰的变量),反序列化不受影响,无需修改版本号;
- 如果修改了非瞬态变量,或者如果新类中实例变量的类型与序列化时类的类型不一致,则会反序列化失败,这时候需要更改serialVersionUID。如果只是新增了实例变量,则反序列化回来新增的是默认值;如果减少了实例变量,反序列化时会忽略掉减少的实例变量。
序列化应用场景与注意点
-
- 所有需要网络传输的对象都需要实现序列化接口,通过建议所有的javaBean都实现Serializable接口。
- 对象的类名、实例变量(包括基本类型,数组,对其他对象的引用)都会被序列化;方法、类变量、transient实例变量都不会被序列化。
- 如果想让某个变量不被序列化,使用transient修饰。
- 序列化对象的引用类型成员变量,也必须是可序列化的,否则,会报错。
- 反序列化时必须有序列化对象的class文件。
- 当通过文件、网络来读取序列化后的对象时,必须按照实际写入的顺序读取。
- 单例类序列化,需要重写readResolve()方法;否则会破坏单例原则。
- 同一对象序列化多次,只有第一次序列化为二进制流,以后都只是保存序列化编号,不会重复序列化。
- 建议所有可序列化的类加上serialVersionUID 版本号,方便项目升级。
小贴士:序列化流和反序列化流不同于字节流、字符流等,后者是把字符串写入/读取文件,前者可以将对象写入/读取文本文件中。
四、打印流(了解)
4.1 概述
System.out
就是PrintStream
类型的,只不过它的流向是系统规定的,打印在控制台上。不过,既然是流对象,我们就可以改变它的流向。
方法:setOut(PrintStream out); //改变输出的目的地
比如,将设置系统的打印流流向,内容打印到文本文件中:
public class PrintDemo { public static void main(String[] args) throws IOException { // 调用系统的打印流,控制台直接输出97 System.out.println(97); // 创建打印流,指定文件的名称 PrintStream ps = new PrintStream("ps.txt"); // 设置系统的打印流流向,输出到ps.txt System.setOut(ps); // 调用系统的打印流,ps.txt中输出97 System.out.println(97); } }