概念
序列化并不是java专属的,在java中,序列化是指把java对象转为可以在网络上传输的格式,或者可持久化的格式的过程。
个人理解就是转成字符形式,显然java对象本身是不能在网络上传输的,也不能在磁盘中存储,所以不管是转成二进制编码,还是转成JSON格式,还是转成其他什么格式,只要完事还能转回java对象,就算达到目的了。
所以有序列化还得有反序列化,也就是把序列化后的结果转成java对象。
JDK自带的序列化方式
JDK自带的序列化方式,是通过java类实现java.io.Serializable接口来实现的。Serializable接口内部没有需要实现的方法,所以这个接口只是一个标识,有这个标识的java类,JDK可以通过自己的方式把这个类的对象进行序列化和反序列化。
JDK的序列化会把java对象序列化为二进制编码。
serialVersionUID属性
JDK的序列化需要该java类有一个serialVersionUID属性,是privatestatic final long类型,序列化和反序列化的时候这个值必须是一致的,否则反序列化时会报错。开发者可以显式指定这个值,比如设置为1L,Eclipse也可以给协助计算一个特别长的long型值用以设置,也可以不显式指定。
注意:如果不显式指定serialVersionUID,JDK会根据java类的一些信息自己计算出一个来,计算的方式对java类的状态十分敏感,很可能会导致序列化双方计算的结果不同,从而在反序列化时失败,因此使用序列化时一定要显式指定serialVersionUID的值。
父子类序列化
如果父类没有实现java.io.Serializable接口,那么序列化时不会把父类一起序列化。由于反序列化时要优先构造父类(其实java对象构建的时候都是这样),所以会调用父类无参构造方法,父类中的属性会被设为类型初始值,比如int的初始值0,String的初始值null。
writeObject方法和readObject方法
即使是private类型的属性的属性值也会被序列化,如果有需要保密的属性值,需要自行在类中定义writeObject方法和readObject方法进行,就像这样:
package test;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
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 Test implements Serializable{
/**
*
*/
private static final long serialVersionUID = 1L;
public String password;
public String getPassword(){
return password;
}
public void setPassword(String password) {
this.password = password;
}
private void writeObject(ObjectOutputStream out) {
try {
PutField putFields = out.putFields();
putFields.put("password", password+"abc");//密码后面加abc字符,权当是加密了
out.writeFields();
System.out.println("保存到文件的内容:"+password+"abc");
} catch (IOException e) {
e.printStackTrace();
}
}
private void readObject(ObjectInputStream in) {
try {
GetField readFields = in.readFields();
Object object = readFields.get("password", "");
password = object.toString().substring(0,object.toString().length()-3);
} catch (IOException e) {
e.printStackTrace();
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
}
public static void main(String[] args) {
try {
Test test1= new Test();
test1.setPassword("123456");//假设密码是123456
ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream("E:\\大文件\\siaTest"));
out.writeObject(test1);
out.close();
ObjectInputStream oin = new ObjectInputStream(new FileInputStream("E:\\大文件\\siaTest"));
Test test2 = (Test) oin.readObject();
System.out.println("反序列化后的内容:"+test2.getPassword());
oin.close();
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
}
}
输出的结果是:
保存到文件的内容:123456abc
反序列化后的内容:123456
注意,类中的这两个方法必须是private类型,因为ObjectOutputStream和ObjectInputStream会使用反射,并调用getPrivateMethod方法来获得这两个方法。
如果没有在类中显式指定这两个方法,ObjectOutputStream和ObjectInputStream将使用它们默认的defaultWriteObject()和defaultReadObject()方法。
JDK序列化需要注意
1,static类型的属性值无法被序列化,因为该类型的属性属于java类,不属于某个java对象。
2,Transient类型的属性值无法被自动序列化,这种类型的值在反序列化时被设置为类型初始值,比如int的初始值0,String的初始值null。不过可以在自定义的writeObject方法和readObject方法中自行处理。
JSON的序列化方式
JSON的工具类有很多,阿里巴巴的FastJson,Google的Gson,开源的Jackson,dubbo的JSON等等,其作用都在于把对象转为JSON格式。
和JDK自带的序列化模式的比较:
优点:
语言无关,速度更快(特别是FastJson),序列化后的内容比JDK的字节码更小,内容可读等等,现在很多人选择JSON方式来序列化信息。
缺点:
不能保存类的格式,只是属性名和属性值,使用时需要注意。
一些序列化工具和框架
1,Google的protoBuf
protoBuf项目已开源,可以把对象转换为二进制格式,性能较高,序列化后的内容字节数比较小,除了java外还支持其他语言。
2,Kryo
Kryo是针对java的序列化框架,速度比JDK快很多,序列化后文件大小比JDK小一点
maven依赖:
<dependency>
<groupId>com.esotericsoftware</groupId>
<artifactId>kryo</artifactId>
<version>3.0.3</version>
</dependency>
3, FST
fst是完全兼容JDK序列化协议的系列化框架,序列化速度大概是JDK的4-10倍,大小是JDK大小的1/3左右。
maven依赖:
<dependency>
<groupId>de.ruedigermoeller</groupId>
<artifactId>fst</artifactId>
<version>2.04</version>
</dependency>
Hessian2是一个轻量级的,自定义描述的二进制RPC协议,是跨语言的。
hessian2在java用来序列化的类是:com.caucho.hessian.io.JavaSerializer(Class,ClassLoader),其中用到了反射技术。
从上面的比较来看,JDK自带的序列化貌似是时间最长,效果最差的,这可能也是其他一些序列化方式,特别是java环境下的序列化方式出现的原因吧。