将Java对象序列化为二进制形式->序列化
将二进制形式数据在内存中重建为java对象->反序列化
二进制中包含了当前实例的类的元数据,以及存储的数据等。
Java 提供了一种对象序列化的机制,该机制中,一个对象可以被表示为一个字节序列,该字节序列包括该对象的数据、有关对象的类型的信息和存储在对象中数据的类型。也就是将Java对象序列化为二进制形式。
目的:
将序列化对象写入文件之后,可以从文件或网络中中读取出来,并且对它进行反序列化,也就是说,对象的类型信息、对象的数据,还有对象中的数据类型可以用来在内存中新建对象。
类能够序列化和反序列化,
1.类必须实现Serializable接口。
2.需要序列化的属性必须实现Serializable接口(不需要的属性可以使用transient关键字修饰)
3.第一个未实现Serializable接口的父类必须有无参构造器
Java中的内置序列化机制需要使用ObjectOutputStream完成序列化。
使用ObjectInputStream完成反序列化。
ObjectOutputStream能够让你把对象写入到输出流中,而不需要每次写入一个字节。你可以把OutputStream包装到ObjectOutputStream中,然后就可以把对象写入到该输出流中了。代码如下:
// Serialize today's date to a file.
FileOutputStream f = new FileOutputStream("tmp");
ObjectOutput s = new ObjectOutputStream(f);
s.writeObject("Today");
s.writeObject(new Date());
s.flush();
ObjectInputStream能够让你从输入流中读取Java对象,而不需要每次读取一个字节。你可以把InputStream包装到ObjectInputStream中,然后就可以从中读取对象了。代码如下:
// Deserialize a string and date from a file.
FileInputStream in = new FileInputStream("tmp");
ObjectInputStream s = new ObjectInputStream(in);
String today = (String)s.readObject();
Date date = (Date)s.readObject();
在这个例子中,写入和读取的顺序必须一致,数据类型也必须一致。
注意:可序列化的字段有两种方式确定
1.默认的:序列化不会处理静态和transient的属性。
2.使用serialPersistentFields
1.使用默认的readObject/writeObject方法
方法签名:
private void readObject(ObjectInputStream ois) throws IOException,ClassNotFoundException{
ois.defaultReadObject();
//其他read代码
}
private void writeObject(ObjectOutputStream oos) throws IOException,ClassNotFoundException {
oos.defaultWriteObject();
//其他write代码
}
序列化时ObjectOutputStream调用writeObject方法,反序列化时ObjectInputStream调用readObject方法。
注意:
- 必须调用defaultWriteObject()和defaultReadObject()方法
- 写和读的顺序要一致,写了一个int,读的时候第一个也是读int.
- 序列化、反序列化过程中没有调用任何构造器(类及其父类均实现了Serializable接口时,如父类未实现Serializable,父类里的属性丢失,无法从流中反序列化)
2.使用Externalizable接口
方法签名
@Override
public void writeExternal(ObjectOutput out) throws IOException {
}
@Override
public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException {
}
Externalizable是Serializable接口的子接口。
序列化、反序列化过程中调用了默认构造器
第一种方式可以部分定制序列化过程,序列化、反序列化的过程部分由Java控制,部分由程序员自己控制。
第二种方式则是完全定制序列化过程,程序员控制序列化、反序列化的过程.
序列化:ObjectOutputStream开始向文件中写数据->反射调用writeObject方法并把自己传入->写完后转化为byte数组->写入文件
反序列化:读取文件->反射调用Object(如果某个父类没有实现Serializable接口,则调用该类的默认无参构造器(父类里的属性丢失))的构造器获取实例->设置
类型信息及其他实例化相关的元数据->初始化静态属性值->反射调用readObject方法->完成反序列化。
Serializable | Externalizable |
---|---|
标志接口,无必须实现的方法 | 必须实现writeExternal() 和 readExternal() |
JVM负责序列化实例,数据和数据类型等 | 程序员控制实例化及数据处理过程 |
默认不调用构造器 | 默认必须有无参构造器 |
修改类结构容易导致序列化失败,分析比较困难 | 分析和修改类容易 |
性能不好 | 性能受程序员实现限制,一般会比较好 |
在序列化和反序列化的两端,java的类结构可能会有变动,有的变动会导致反序列化失败或引起业务系统的不兼容,称为非兼容的改动,
有的变动不会导致反序列化失败或业务系统的不兼容,称为兼容的改动。
注意:以下兼容性是在类实现了Serializable接口并定义了固定的serialVersionUID的情况下,如果没有实现该接口则由于serialVersionUID不同的原因,部分兼容的改动也会序列化失败。
private static final long serialVersionUID = -4116638233879810430L;
serialVersionUID是序列化的版本,用来在反序列化时,确保发送者和接收者加载的类是否兼容。其值可自定义。如果没有显示定义serialVersionUID属性,
则Java根据类定义使用SHA-1算法得出的哈希值替代。
serialVersionUID不一致时,报错InvalidCastException.
使用serialVersionUID可以在以上发生的兼容或不兼容的改动时,强制客户端更新。
Java的序列化不考虑transient修饰的属性及static属性。
transient关键字一般用于:
父类对象的序列化,需要让父类也实现Serializable 接口。
如果父类不实现,就需要有默认的无参的构造函数。
在父类没有实现 Serializable 接口时,虚拟机是不会序列化父对象的,而一个 Java 对象的构造必须先有父对象,才有子对象,
反序列化也不例外。所以反序列化时,为了构造父对象,只能调用父类的无参构造函数作为默认的父对象。
因此当我们取父对象的变量值时,它的值是调用父类无参构造函数后的值。
如果你考虑到这种序列化的情况,在父类无参构造函数中对变量进行初始化,否则的话,父类变量值都是默认声明的值,
如 int 型的默认是 0,string 型的默认是 null。
这三个方法都是Serializable接口的除readObject,writeObject方法之外的接口方法。
ANY-ACCESS-MODIFIER Object writeReplace() throws ObjectStreamException;
ANY-ACCESS-MODIFIER Object readResolve() throws ObjectStreamException;
private void readObjectNoData()
throws ObjectStreamException;
readObjectNoData是1.4新增的,目的在于发生:
如果实现了writeReplace方法后,那么在序列化时会先调用writeReplace方法将当前对象替换成另一个对象(该方法会返回替换后的对象)并将其写入流中.
方法执行顺序:writeReplace -> writeObject/writeExternal
导致的结果:所有当前类型的实例都被替换了。假如一个类的100个不同的实例(某些属性的值完全不同)被序列化了,
反序列化时你得到了100个不同的实例,但是所有的属性可能都是一样的(取决于你的writeReplace方法的具体实现)。
如果实现了readResolve方法后,那么在序列化时会最后调用writeReplace方法将当前对象替换成另一个对象。
方法执行顺序:readObject/readExternal -> readResolve
导致的结果:所有当前类型的实例都被替换了。
可以被用于单例模式,确保实例序列化后的唯一性。
功能点 | readObject | writeObject | readExternal | writeExternal | readResolve | writeReplace |
---|---|---|---|---|---|---|
目的 | 读取定制内容 | 写入定制内容 | 读取完全定制的内容,类的实例化自己控制 | 写入完全定制的内容,写入当前对象的那些属性及写入顺序等自定义 | 覆盖读取的内容 | 覆盖写入的内容 |
是否使用默认序列化机制 | 是 | 是 | 否 | 否 | 是,但readObject/readExternal读到的内容被丢弃 | 是,但写入流的原类的实例都被丢弃 |
调用顺序 | readObject -> readResolve | writeReplace -> writeObject | readExternal -> readResolve | writeReplace -> writeExternal | readObject/readExternal -> readResolve | writeReplace -> writeObject/writeExternal |
是否成对 | 是,必须与writeObject一起添加 | 是 | 是必须与writeExternal一起添加 | 否,可独立存在 | 否,可独立存在 |
各个方法的目的不同,理解了目的,其他就好理解了。
java的克隆是属性到属性的克隆。
java实现克隆需要两步。
java默认的克隆方式的浅克隆:即只克隆基础数据类型,引用类型只复制了引用。
深克隆:克隆所有的数据,使得对克隆对象的任何操作不会影响到原被克隆对象。
深克隆的方式可使用以下几种方式:
public class Data {
private Integer x;
private Object y;
public Data(Data point){
this.x = point.x;
this.y = point.y;
}
}
public static T deepCopy(T Object) throws IOException, ClassNotFoundException {
//Serialization of object
ByteArrayOutputStream bos = new ByteArrayOutputStream();
ObjectOutputStream out = new ObjectOutputStream(bos);
System.out.println("begin w");
out.writeObject(Object);
System.out.println("end w");
//De-serialization of object
ByteArrayInputStream bis = new ByteArrayInputStream(bos.toByteArray());
ObjectInputStream in = new ObjectInputStream(bis);
T copied = (T) in.readObject();
System.out.println("end r");
return copied;
}
使用ByteArrayInputStream和ByteArrayOutputStream在内存中完成克隆
SomeObject cloned = org.apache.commons.lang.SerializationUtils.clone(someObject);
以上三种方式的性能未测试。
反序列化默认没有调用构造器就实例化了类。对单例的类无法阻止生成多个实例。破坏了单例。
解决方案就是使用保护性反序列化方法readResolve
protected Object readResolve(){
return getInstance();
}
异常 | 原因 |
---|---|
InvalidClassException | 以下原因导致的无法还原实例时,1.serialVersionUID 不一致 2.同一属性的基础类型改变时 3.最近的未序列化的父类没有无参构造器 4.Externalizable 的类没有无参构造器 |
StreamCorruptedException、InvalidObjectException | 一般是数据损坏,或协议不匹配 |
OptionalDataException | 属性本来是基础类型,被改成了引用类型 |
https://www.ibm.com/developerworks/cn/linux/l-cn-gpb/
各序列化方式的性能
https://github.com/eishay/jvm-serializers/wiki
FastJson为什么那么快
http://wenshao.iteye.com/blog/1142031/
http://docs.oracle.com/javase/7/docs/api/java/io/Serializable.html
java对象序列化规范
http://docs.oracle.com/javase/7/docs/platform/serialization/spec/serialTOC.html
serialVersionUID及其默认生成方式
http://docs.oracle.com/javase/7/docs/platform/serialization/spec/class.html#4100。
https://www.javacodegeeks.com/2012/07/native-cc-like-performance-for-java.html