一、什么是序列化和反序列化?
序列化:把对象转换为字节序列的过程
反序列化:把字节序列恢复为对象的过程
用途:
- 把对象的字节序列永久保存在硬盘上,通常存放在一个文件中。 对象序列化机制允许把内存中的JAVA对象转换成跟平台无关的二进制流,从而允许将这种二进制流持久地保存在磁盘上。在很多应用中,需要将某些对象进行序列化,让它离开内存空间,入住物理硬盘,一遍长期保存,最常见的web服务器的Session对象,当有 10万用户并发访问,就有可能出现10万个Session对象,内存可能吃不消,于是Web容器就会把一些seesion先序列化到硬盘中,等要用了,再把保存在硬盘中的对象还原到内存中
- 在网络上传送对象的字节序列。 发送方需要将这个java对象转换为字节序列,才能在网络上传送,接受方则需要把字节序列再恢复为java对象
二、JDK类库中的序列化API
对象序列化步骤:
1)创建一个对象输出流,它可以包装一个其他类型的目标输出流,如文件输出流
2)通过对象输出流的writeObject()方法写对象
对象反序列化步骤:
1)创建一个对象输入流,它可以包装一个其他类型的目标输入流,如文件输入流
2)通过对象输入流的readObject()方法读取对象
示例:
需要序列化处理的类:
package com.cn;
import java.io.Serializable;
import java.util.Date;
public class Example implements Serializable{
private static final long serialVersionUID = -2317759825949525703L;
private int id;
private String name;
private Date date;
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Date getDate() {
return date;
}
public void setDate(Date date) {
this.date = date;
}
}
序列化、反序列化操作:
package com.cn;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.text.MessageFormat;
import java.util.Date;
public class TestSerialize {
public static void main(String[] args) throws FileNotFoundException, ClassNotFoundException, IOException {
serializeExample();
Example ex = deSerializeExample();
System.out.println(MessageFormat.format("id={0},name={1},date={2}", ex.getId(),ex.getName(),ex.getDate()));
}
private static void serializeExample() throws FileNotFoundException, IOException {
Example ex = new Example();
ex.setId(1111);
ex.setName("测试");
ex.setDate(new Date());
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream(new File("E:/aaa.txt")));
oos.writeObject(ex);
System.out.println("对象序列化--------------");
oos.close();
}
private static Example deSerializeExample() throws FileNotFoundException, IOException, ClassNotFoundException {
ObjectInputStream ois = new ObjectInputStream(new FileInputStream(new File("E:/aaa.txt")));
Example ex = (Example) ois.readObject();
System.out.println("对象反序列化-------------");
return ex;
}
}
运行结果:
对象序列化--------------
对象反序列化-------------
id=1,111,name=测试,date=19-2-28 下午6:27
E盘中会生成aaaa.txt文件
注:如果使用序列化机制向文件中写入了多个对象,在反序列化时,需要按实际写入的顺序读取
三、对象引用的序列化
1.对于对象中成员变量若是引用类型,会有什么不同?
这个引用类型的成员变量必须也是可序列化的,否则拥有该类型成员变量的类的对象不可序列化,static修饰的变量不能序列化,transient修饰的变量不再被序列化,Thread不会被序列化
2. 假如我有两个类,分别是A和B,B类中含有一个指向A类对象的引用,现在我们对两个类进行实例化{ A a = new A(); B b = new B(); },这时在内存中实际上分配了两个空间,一个存储对象a,一个存储对象b,接下来我们想将它们写入到磁盘的一个文件中去,就在写入文件时出现了问题!因为对象b包含对对象a的引用,所以系统会自动的将a的数据复制一份到b中,这样的话当我们从文件中恢复对象时(也就是重新加载到内存中)时,内存分配了三个空间,而对象a同时在内存中存在两份,想一想后果吧,如果我想修改对象a的数据的话,那不是还要搜索它的每一份拷贝来达到对象数据的一致性,这不是我们所希望的!
为了避免这种情况,JAVA的序列化机制采用了一种特殊的算法:
1、所有保存到磁盘中的对象都有一个序列化编号。
2、当程序试图序列化一个对象时,会先检查该对象是否已经被序列化过,只有该对象从未(在本次虚拟机中)被序列化,系统才会将该对象转换成字节序列并输出。
3、如果对象已经被序列化,程序将直接输出一个序列化编号,而不是重新序列化。
四、自定义序列化
自定义序列化是由ObjectInput/OutputStream在序列化反序列化时候通过反射检查该类是否存在以下方法(0个或多个),执行顺序从上到下,序列化调用1/2,反序列化调用3、4;当某个字段被声明为transient后,默认序列化机制就会忽略该字段
1、Object writeReplace() throws ObjectStreamException; 可以通过此方法修改序列化的对象
2、void writeObject(java.io.ObjectOutputStream out) throws IOException; 方法中调用defaultWriteObject() 使用writeObject的默认的序列化方式,除此之外可以加上一些其他的操作,如添加额外的序列化对象到输出:out.writeObject("XX")
3、void readObject(java.io.ObjectInputStream in) throws Exception; 方法中调用defaultReadObject()使用readObject默认的反序列化方式,除此之外可以加上一些其他的操作,如读入额外的序列化对象到输入:in.readObject()
4、Object readResolve() throws ObjectStreamException; 可以通过此方法修改返回的对象
运用场景:
对敏感字段加密,如密码字符等,希望对该密码字段进行序列化时,进行加密,而客户端如果拥有解密的密钥,只有在客户端进行反序列化时,才可以对密码进行读取,这样可以一定程度保证序列化对象的数据安全。
示例一:对密码字段加密
package com.cn.prototype.serialize.self;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectInputStream.GetField;
import java.io.ObjectOutputStream;
import java.io.ObjectOutputStream.PutField;
import java.io.Serializable;
public class Login implements Serializable {
private static final long serialVersionUID = 4651265039305098539L;
private String name;
private String password;
public Login(String name, String password) {
this.name = name;
this.password = password;
}
private void writeObject(ObjectOutputStream out) throws IOException {
PutField putFields = out.putFields();
System.out.println("原密码:" + password);
password = "encryption";
putFields.put("name", name);
putFields.put("password", password); //模拟加密
System.out.println("加密后的密码" + password);
out.writeFields();
}
private void readObject(ObjectInputStream in) throws ClassNotFoundException, IOException {
GetField readFields = in.readFields();
Object object = readFields.get("password", "");
System.out.println("要解密的字符串:" + object.toString());
name = (String) readFields.get("name", "");
password = "123456";
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
}
package com.cn.prototype.serialize.self;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.text.MessageFormat;
public class Test {
public static void main(String[] args) throws Exception {
Login login = new Login("小明","123456");
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream(new File("E:/aaa.txt")));
oos.writeObject(login);
oos.close();
ObjectInputStream ois = new ObjectInputStream(new FileInputStream(new File("E:/aaa.txt")));
Login l = (Login) ois.readObject();
ois.close();
System.out.println(MessageFormat.format("name={0},password={1}", l.getName(),l.getPassword()));
}
}
结果:
原密码:123456
加密后的密码encryption
要解密的字符串:encryption
name=小明,password=123456
示例二:单例模式的类实现序列化接口,若使用默认的序列化策略,则在反序列化返回的对象不符合单例模式(反射创建了新的对象),可以通过修改序列化的readResolve来实现自定义序列化返回结果来实现单例对象唯一(相当于1,2,3方法对4的结果毫无作用)。
package com.cn.prototype.serialize.self;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.ObjectStreamException;
import java.io.Serializable;
public class Singleton implements Serializable {
private static final long serialVersionUID = -1523101416687991284L;
private String name;
private static Singleton singleton = null;
private Singleton(String name) {
this.name = name;
};
public static Singleton getInstance() {
if(singleton == null)
singleton = new Singleton("TEST");
return singleton;
}
private Object writeReplace() {
System.out.println("1 write replace start");
return this;
}
private void writeObject(ObjectOutputStream out) throws IOException {
System.out.println("2 write object start");
out.defaultWriteObject();
}
private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException {
System.out.println("3 read object start");
in.defaultReadObject();
}
private Object readResolve() throws ObjectStreamException {
System.out.println("4 read resolve start");
return Singleton.getInstance();//不管序列化的操作是什么,返回的都是本地的单例对象
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public static void main(String[] args) throws Exception{
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream(new File("E:/aaa.txt")));
oos.writeObject(Singleton.getInstance());
oos.close();
ObjectInputStream ois = new ObjectInputStream(new FileInputStream(new File("E:/aaa.txt")));
Singleton singleton = (Singleton) ois.readObject();
ois = new ObjectInputStream(new FileInputStream(new File("E:/aaa.txt")));
Singleton singleton1 = (Singleton) ois.readObject();
ois.close();
System.out.println("sington person hashcode:" + singleton.hashCode());
System.out.println("sington person1 hashcode:" + singleton1.hashCode());
System.out.println("singleton getInstance hashcode:" + Singleton.getInstance().hashCode());
System.out.println("singleton person equals:" + (singleton == Singleton.getInstance()));
System.out.println("person equals1:" + (singleton1 == singleton));
}
}
结果:
1 write replace start
2 write object start
3 read object start
4 read resolve start
3 read object start
4 read resolve start
sington person hashcode:1442407170
sington person1 hashcode:1442407170
singleton getInstance hashcode:1442407170
singleton person equals:true
person equals1:true
一些知识点总结:
序列化问题
1、静态变量不会被序列化。
2、子类序列化时:
如果父类没有实现Serializable接口,没有提供默认构造函数,那么子类的序列化会出错;
如果父类没有实现Serializable接口,提供了默认的构造函数,那么子类可以序列化,父类的成员变量不会被序列化。
如果父类实现了Serializable接口,则父类和子类都可以序列化。
transient使用小结
1)一旦变量被transient修饰,变量将不再是对象持久化的一部分,该变量内容在序列化后无法获得访问。
2)transient关键字只能修饰变量,而不能修饰方法和类。注意,本地变量是不能被transient关键字修饰的。变量如果是用户自定义类变量,则该类需要实现Serializable接口。
3)被transient关键字修饰的变量不再能被序列化,一个静态变量不管是否被transient修饰,均不能被序列化。