-
day37-IO流04
4.常用的类03
4.4节点流和处理流02
4.4.5对象处理流-ObjectInputStream和ObjectOutputStream
1.序列化和反序列化
例子1:
看一个需求
-
将
int num= 100
这个int 类型的数据保存到文件中,注意不是100 数字,而是int 100
,并且能够从文件中直接恢复int 100
-
将
Dog dog = new Dog("小黄",3)
这个Dog对象保存到文件中,并且能够从文件恢复。
上面的要求,就是能够将 基本数据类型 或者 对象 进行 序列化 和 反序列化操作
-
序列化和反序列化
- 序列化就是在保存数据时,保存数据的值和数据类型
- 反序列化就是在恢复数据时,恢复数据的值和数据类型
-
需要让某个对象支持序列化机制,则必须让其类是可序列化的,为了让这个类是可序列化,该类必须实现以下两个接口之一:
-
Serializable //这是一个标记接口,没有方法(推荐)
-
Externalizable //该接口有方法需要实现,因此推荐实现上面的Serializable接口
-
2. 对象处理流基本介绍与应用
- 功能:提供了对基本类型或对象类型的序列化和反序列化的方法
- ObjectOutputStream提供序列化功能
- ObjectInputStream 提供反序列化功能
例子1:序列化应用
使用ObjectOutputStream序列化 基本数据类型 和一个 Dog对象(name,age),并保存到data.dat文件中。
|
package li.io.outputstream_; |
|
|
|
import java.io.FileOutputStream; |
|
import java.io.ObjectOutputStream; |
|
import java.io.Serializable; |
|
|
|
//演示ObjectOutputStream的使用,完成数据的序列化 |
|
public class ObjectOutputStream_ { |
|
public static void main(String[] args) throws Exception { |
|
//序列化后,保存的文件格式不是纯文本格式,而是按照它自己的格式来保存 |
|
String filePath = "d:\\data.dat"; |
|
|
|
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream(filePath)); |
|
|
|
//存储 |
|
//序列化数据到文件d:\data.dat |
|
oos.writeInt(100);//100在底层实现了自动装箱,int ->Integer (Integer实现了Serializable接口) |
|
oos.writeBoolean(true);//boolean ->Boolean (Boolean实现了Serializable接口) |
|
oos.writeChar('a');// char ->Char (实现了Serializable接口) |
|
oos.writeDouble(9.5);//double ->Double (实现了Serializable接口) |
|
oos.writeUTF("你好火星");//String(实现了Serializable接口) |
|
//保存一个Dog对象 |
|
oos.writeObject(new Dog("旺财", 10)); |
|
|
|
//关闭外层流 |
|
oos.close(); |
|
System.out.println("数据保存完毕(序列化形式)"); |
|
|
|
} |
|
} |
|
|
|
//如果需要序列化某个对象,必须实现接口Serializable或者 Externalizable接口 |
|
class Dog implements Serializable { |
|
private String name; |
|
private int age; |
|
|
|
public Dog(String name, int age) { |
|
this.name = name; |
|
this.age = age; |
|
} |
|
|
|
|
|
public String toString() { |
|
return "Dog{" + |
|
"name='" + name + '\'' + |
|
", age=" + age + |
|
'}'; |
|
} |
|
|
|
public String getName() { |
|
return name; |
|
} |
|
} |
例子2:反序列化应用
使用ObjectInputStream读取data.dat并且反序列化恢复数据
Dog:
|
package li.io.outputstream_; |
|
|
|
import java.io.Serializable; |
|
|
|
//如果需要序列化某个对象,必须实现接口Serializable或者 Externalizable接口 |
|
public class Dog implements Serializable { |
|
private String name; |
|
private int age; |
|
|
|
public Dog(String name, int age) { |
|
this.name = name; |
|
this.age = age; |
|
} |
|
|
|
|
|
public String toString() { |
|
return "Dog{" + |
|
"name='" + name + '\'' + |
|
", age=" + age + |
|
'}'; |
|
} |
|
|
|
public String getName() { |
|
return name; |
|
} |
|
} |
ObjectInputStream_:
|
package li.io.inputstream_; |
|
|
|
import java.io.FileInputStream; |
|
import java.io.IOException; |
|
import java.io.ObjectInputStream; |
|
|
|
import li.io.outputstream_.Dog; |
|
|
|
public class ObjectInputStream_ { |
|
public static void main(String[] args) throws IOException, ClassNotFoundException { |
|
|
|
//指定反序列化的文件 |
|
String filePath = "d:\\data.dat"; |
|
ObjectInputStream ois = new ObjectInputStream(new FileInputStream(filePath)); |
|
|
|
//读取 |
|
//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 ,dou的运行类型是 Dog |
|
Object dog = ois.readObject(); |
|
System.out.println("运行类型=" + dog.getClass());//Dog |
|
System.out.println("dog信息=" + dog);//底层 Object->Dog |
|
|
|
//一些重要细节: |
|
//1. 如果我们希望调用Dog的方法,需要向下转型 |
|
//2. 需要我们将Dog类的定义拷贝到可以引用的位置然后导入类(将Dog类创建为公共类,重新序列化,再反序列化) |
|
Dog dog1 = (Dog) dog; |
|
System.out.println(dog1.getName()); |
|
|
|
//关闭外层流 |
|
ois.close(); |
|
} |
|
} |
3.对象处理流使用细节
- 读写顺序要一致
-
要求实现序列化或者反序列化的对象的类,需要实现接口
Serializable
或者Externalizable
-
序列化的类中建议添加
SerialVersionUID
,是为了提高版本的兼容性
在完成序列化操作后,如果对序列化对象进行了修改,比如增加某个字段,那么我们再进行反序列化就会抛出InvalidClassException异常,这种情况叫不兼容问题。
解决的方法是:在对象中手动添加一个 serialVersionUID 字段,用来声明一个序列化版本号,之后再怎么添加属性也能进行反序列化,凡是实现Serializable接口的类都应该有一个表示序列化版本标识符的静态变量。
-
序列化对象时,默认将里面的所有属性都进行序列化,但除了
static
或transient
修饰的成员 -
序列化对象时,要求里面属性的类型也需要实现序列化接口
例如:在(实现了Serializable接口的)Dog类中创建一个Master类型的属性master,而Master类没有实现Serializable接口,所以在序列化时,Dog类中的master属性无法序列化,也不能反序列化
- 序列化具有可继承性,也就是说如果某类已经实现序列化,则它的所有子类也已经默认实现了序列化
4.4.6标准输入输出流
不懂这些,你敢说自己知道Java标准输入输出流? - 简书 (jianshu.com)
介绍:
类型 | 默认设备 | |
---|---|---|
System.in 标准输入 | InputStream | 键盘 |
System.out标准输出 | PrintStream | 显示器 |
例子1:
|
package li.io.standard; |
|
|
|
public class InputAndOutput { |
|
public static void main(String[] args) { |
|
// System 类 的 public final static InputStream in = null; |
|
// System.in 编译类型 InputStream |
|
// System.in 运行类型 BufferedInputStream |
|
// 表示的是标准输入--用来读取键盘录入的数据 |
|
System.out.println(System.in.getClass());//class java.io.BufferedInputStream-字节处理输入流 |
|
|
|
// 1. System.out public final static PrintStream out = null; |
|
// 2.编译类型 PrintStream |
|
// 3.运行类型 PrintStream |
|
// 4.表示标准输出显示器--将数据输出到命令行 |
|
System.out.println(System.out.getClass());//class java.io.PrintStream - 字节输出流 |
|
} |
|
} |
应用案例1:
传统方法: System.out.println(" ");
是使用out对象将数据输出到显示器
应用案例2:
传统方法,Scanner是从标准输入 键盘接收数据
|
// 给Scanner扫描器传入的就是BufferedInputStream, |
|
// 表示的是标准输入--用来读取键盘录入的数据 |
|
// 因此 scanner会到键盘去 获取输入的数据 |
|
Scanner scanner = new Scanner(System.in); |
|
System.out.println("请输入内容:"); |
|
//会去键盘去得到输入流 |
|
String next = scanner.next(); |
|
System.out.println(next); |
|
scanner.close(); |
4.4.7转换流-InputStreamReader和OutputStreamWriter
1.文件乱码问题
先来看一个例子:
在d:\盘的根目录下有一个a.txt文件,内容如下:
现在想把该文件读取到程序中:
|
package li.io.transformation; |
|
|
|
import java.io.BufferedReader; |
|
import java.io.FileReader; |
|
import java.io.IOException; |
|
|
|
//一个中文乱码问题 |
|
public class CodeQuestion { |
|
public static void main(String[] args) throws IOException { |
|
//读取a.txt文件到程序中 |
|
|
|
//思路: |
|
// 1.创建一个字符输入流 这里用 BufferedRead[处理流] |
|
// 2.使用 BufferedRead 对象读取 a.txt |
|
// 3.在默认情况下,读取文件是按照 UTF-8 编码 |
|
String filePath = "d:\\a.txt"; |
|
BufferedReader br = new BufferedReader(new FileReader(filePath)); |
|
String s = br.readLine(); |
|
System.out.println("读取到的内容=" + s); |
|
br.close(); |
|
} |
|
} |
在a.txt文件编码为uft-8的时候,输出:
在a.txt文件编码为ANSI的时候,输出:
出现乱码的根本问题是:没有指定文件的编码方式。
2.基本介绍与应用
转换流也是一种处理流,它提供了字节流和字符流之间的转换。
在Java IO流中提供了两个转换流:InputStreamReader 和 OutputStreamWriter,这两个类都属于字符流。
转换流的原理是:字符流 = 字节流 + 编码表
我们需要明确的是字符编码和字符集是两个不同层面的概念。
- encoding是charset encoding的简写,即字符集编码,简称编码。
- charset是character set的简写,即字符集。
编码是依赖于字符集的,一个字符集可以有多个编码实现,就像代码中的接口实现依赖于接口一样。
在转换流中选择正确的编码非常的重要,因为指定了编码,它所对应的字符集自然就指定了,否则很容易出现乱码,所以编码才是我们最终要关心的。
转换流的特点:其是字符流和字节流之间的桥梁。
可对读取到的字节数据经过指定编码转换成字符
可对读取到的字符数据经过指定编码转换成字节
那么何时使用转换流?
当字节和字符之间有转换动作时
流操作的数据需要编码或解码时
Java IO流详解(六)----转换流(字节流和字符流之间的转换) - 唐浩荣 - 博客园 (cnblogs.com)
-
InputStreamReader:
如下图,InputStreamReader有一个重要的构造器
InputStreamReader(InputStream, Charset)
:也就是说,我们可以通过这个方法,将传入的字节流转为一个字符流并指定处理的编码方式
-
OutputStreamWriter:
如下图,OutputStreamWriter也有一个重要的构造器:
OutputStreamWriter(OutputStream, Charset)
:即我们也可以通过这个方法,将写出的字节流转为一个字符流并指定处理的编码方式
- 当处理纯文本数据时,若使用字符流效率更高,并且可以有效解决中文问题,所以建议将字节流转换成字符流
- 可以在使用时指定编码格式,比如:uft-8,gbk,gb2312,ISO8859-1等
应用案例1:
编程将字节流FileInputStream转换成(包装成)字符流InputStreamReader,对文件进行读取(按照UTFF-8格式),进而再包装成 BufferedReader
|
package li.io.transformation; |
|
|
|
import java.io.BufferedReader; |
|
import java.io.FileInputStream; |
|
|
|
import java.io.InputStreamReader; |
|
import java.io.IOException; |
|
|
|
//演示使用 InputStreamReader 转换流解决中文乱码问题 |
|
//将字节流转 FileInputStream 换成字符流 InputStreamReader,并指定编码 gbk/utf-8 |
|
public class InputStreamReader_ { |
|
public static void main(String[] args) throws IOException { |
|
|
|
String filePath = "d:\\a.txt"; |
|
// 1.将 FileInputStream流(字节流),转成了 InputStreamReader流(字符流) |
|
// 2.指定了gbk编码 |
|
InputStreamReader isr = new InputStreamReader(new FileInputStream(filePath), "UTF-8"); |
|
// 3. 把 InputStreamReader 传入 BufferedReader |
|
BufferedReader br = new BufferedReader(isr); |
|
// 4. 读取 |
|
String s = br.readLine(); |
|
System.out.println("读取内容=" + s); |
|
|
|
// 5.关闭外层流 |
|
br.close(); |
|
|
|
} |
|
} |
应用案例2:
编程将字节流FileOutputStream 包装(转换)成字符流OutputStreamWriter,对文件进行写入(按照GBK格式,可以指定其他,比如UTF-8)
|
package li.io.transformation; |
|
|
|
import java.io.FileOutputStream; |
|
import java.io.OutputStreamWriter; |
|
import java.io.IOException; |
|
|
|
/** |
|
* 演示使用 OutputStreamWriter |
|
* 将字节流转 FileOutputStream 换成字符流 OutputStreamWriter |
|
* 指定处理的编码 gbk/utf-8/utf8 |
|
*/ |
|
public class OutputStreamWriter_ { |
|
public static void main(String[] args) throws IOException { |
|
String filePath = "d:\\a.txt"; |
|
String charSet = "GBK"; |
|
|
|
//创建对象 |
|
OutputStreamWriter osw = new OutputStreamWriter(new FileOutputStream(filePath), charSet); |
|
|
|
//写入 |
|
osw.write("我只是一只小猫咪,我不懂你在说什么"); |
|
|
|
//关闭外层流 |
|
osw.close(); |
|
|
|
System.out.println("按照 " + charSet + " 保存文件成功~"); |
|
} |
|
} |
出处:https://www.cnblogs.com/liyuelian/p/16687187.html