一、问题的引入:
程序运行过程中,所创建的对象都是保存在内存中的,当程序运行结束时,对象的生命周期就结束了。
如果能够将这些对象信息保存下来,下次程序再启动的时候,能够读取这些对象信息,还原这些对象的话,使他们与上次结束时持有相同的状态,那该多好。
可是怎么能够做到呢?
方法一:逐一记录每隔对象的所有属性(方法),然后逐一写入文件中,等下次使用某个对象时,再从文件中遍历调用这个对象。这样做,尽管实现了对象信息的保存和获取,但是无形中带来下面2个突出的弊端:
A、必须对对象的结构非常的熟悉,稍有疏忽就可能出错,同时需要编写大量的重复代码;
B、每次增加配置信息都要重新设计文件的结构,并且修改保存和加载的方法;
为了解决上述问题,java引入了序列化和反序列化技术,只需简单几步,就可以一劳永逸第完成这些对象信息的读写操作。
二、序列化概述:
所谓序列化,是指将java对象的状态存储到特定的存储介质中的过程(将对象的状态转换为可保持或可传输的格式的过程)。在序列化过程中,会将对象的共有成员、私有成员包括类名等——》字节流——》数据流,存储(字节序列、二进制状态)到介质(通常指的是文件)中,必要的时候,再读取出来。
需要注意的是:
1、转化后的是字节序列,这些字节序列可以保存在磁盘上、借助于网络进行传输;
2、序列化后的对象,保存的是其二进制状态,实现了平台的无关性,即windows系统中实现序列化一个对象,传输到unix机器上,反序列化后可以得到;
3、序列化后的对象,不仅保存了对象的所有成员,而且包含对象内的所有引用,并保存了那些引用对象,构建了对象网。
序列化的好处:序列化机制,使得对象可以脱离程序而独立存在;
序列化的实现:借助I/O流来实现。
三、序列化的应用场景:
场景1、登录某个网站后,设置个人窗体,例如登录博客后,设置自己的个性空间(背景色、特色布局等等)。为了使用户设置完成后,下次登录的依然保持有这个界面风格,可以将用户设置的信息保存在一个对象中,然后将该对象序列化后保存在后台的数据库表中或文件中。下次用户再登录时,加载页面时,从表中或文件中读取这些信息,利用反序列化机制,再生成这个对象应用到用户界面上。
场景2:点对点聊天系统(如QQ)中,可以自己设置喜爱的字体、大小、颜色,然后同网络的另一端一个用户进行聊天。网络端的另一个永不他接收到的信息原则上讲也应该是你发出去的风格。此时,可以将用户输入的风格、内容封装成一个对象,然后序列化——》网络传输,在网络的另一端,取出这个二进制流并反序列化成对象,然后显示在他的电脑上。
四、如何实现对象的序列化与反序列化:
1、对象序列化:对象要想能够被序列化,则必须实现java.io.Serializable接口,例如java中的String类、包装类、Date类都默认实现了这个接口;
2、对象序列化的步骤:
A、创建一个对象输出流:ObjectOutputStream,它可以包含一个其他类型的输出流,比如文件输出流FileOutputStream
B、通过writeObject()方法输出序列化对象;
3、反序列化的步骤
A、创建一个对象输入流:objectInputStream,可以包含一个其他类型的输入流,比如文件输入流FileInputStream;
B、通过readObject()方法读流中的对象,默认返回的是Object对象。
注意:
反序列化机制,并不是通过构造器来初始化对象的(无需使用构造器生成对象);
如果向文件中写入多个对象时,反序列化的时候,则必须按写入的顺序来反序列化恢复 对象;
如果一个可序列化的类,有多个父类(包括直接或间接父类),则这些父类要么也是可序列化的,要么就包含无参数的构造方法,否则抛出异常。
但是涉及到隐私的信息:如用户名、密码、卡号等,应禁止序列化某些信息属性,此时使用transient来修饰
五、代码示例:
1、定义一个类,实现序列化接口
package com.pb.io.serilable;
import java.io.Serializable;
public class Student implements Serializable{
private String name;
private int age;
private String gender;
private transient String password;
public Student(String name,int age,String gender,String password){
this.name=name;
this.age=age;
this.gender=gender;
this.password=password;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public String getGender() {
return gender;
}
public void setGender(String gender) {
this.gender = gender;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
}
2、序列化与反序列化操作代码:
package com.pb.io.serilable;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
/**
* 使用对象流序列化和反序列化对象的步骤:
* 1、创建一个对象,这个对象将被序列化并被写入文件中,前提是当前类创建的时候,实现了Serializable接口
* 2、实例化ObjectOutputStream对象
* 3、调用ObjectOutputStream类的writeObject()方法,将对象写入到文件中
* ===========================
* 4、实例化ObjectInputStream对象
* 5、调用ObjectinputStream类的readObject()方法,获取对象的时候要进行强制类型转换
* 6、关闭相关的流
*
* 注意:transient关键字可以保护对象中的隐藏信息不被写入到对象流中。
*
*/
public class SerializableObj {
public static void main(String[] args) throws FileNotFoundException, IOException, ClassNotFoundException {
//实例化ObjectOutputStream对象
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("c:/mydoc/stu.txt"));
//实例化Student对象
Student student = new Student("安娜",30,"女","123456");
//student对象序列化,并且写入oos流
oos.writeObject(student);
//关闭流
oos.close();
/**
* 反序列化,即读取序列化对象的过程
*/
//1.实例化ObjectInputStream对象
ObjectInputStream ois = new ObjectInputStream(new FileInputStream("c:/mydoc/stu.txt"));
//实例化Student对象
Student student1=null;
//反序列化读取ObjectinputStream流中的信息,进行强制类型转换
student1=(Student)ois.readObject();
//输出student对象
System.out.println("=========输出获取的student的各个信息=========");
System.out.println("姓名为:"+student1.getName());
System.out.println("性别为:"+student1.getGender());
System.out.println("年龄为:"+student1.getAge());
System.out.println("密码为:"+student1.getPassword());
//关闭流
ois.close();
}
}
七、序列化机制的算法
为了确保同一个对象不被多次序列化(例如,可以被序列化的类被另一类引用),则引入了序列化算法:
A、对象序列化的时候,分配一个序列号;
B、当程序试图序列化一个对象时,将会检查这个对象是否已经被序列化(就是看序列化编号是佛存在)?如果已经被序列化,则直接输出当前对象的序列化编号,而不再重新序列化。
C、反序列化的时候,程序会自动查找当前对象的序列化编号,如果发现当前的序列化编号被其他对象引用,则返回同一个对象。