序列化的意义:
序列化的作用就是将对象转化为字节流,反序列化就是将读取的字节流恢复为对象,序列化是传输对象的一种手段(比如socket通信)
实现序列化有两种方式,一种是对象实现Serializable接口,另一种是实现externalizable接口.实现Serializable接口会自动将其所有成员序列化,实现externalizable需要程序员指定成员序列化.
首先展示实现Serlializable接口实现对象序列化:
person.java
import java.io.Serializable;
public class person implements Serializable{
private String name;
private Sex sex;
private int age;
public person(String name,Sex sex,int age){
this.name=name;
this.sex=sex;
this.age=age;
}
@Override
public String toString(){//重载,便于观察成员
return "name:"+name+"\nsex:"+sex+"\nage:"+age;
}
}
test_first.java
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;
enum Sex{//性别类型
MALE,FEMALE;
}
public class test_first {
public static void main(String[] args) throws FileNotFoundException, IOException, ClassNotFoundException {
person p=new person("宝贝",Sex.FEMALE,21);
ObjectOutputStream out=new ObjectOutputStream(new FileOutputStream(new File("D://test.txt")));
out.writeObject(p);
out.close();
ObjectInputStream in=new ObjectInputStream(new FileInputStream(new File("D://test.txt")));
Object o=in.readObject();
in.close();
System.out.println(o);
}
}
输出效果:
name:宝贝
sex:FEMALE
age:21
如果将peron类中implements Serializable去掉,就会无法正常序列化。
实现Serializable默认是序列化该对象所有成员。如果某些数据不想序列化,那么可以使用transient关键字.
修改person.java
import java.io.Serializable;
public class person implements Serializable{
transient private String name;
private Sex sex;
private int age;
public person(String name,Sex sex,int age){
this.name=name;
this.sex=sex;
this.age=age;
}
@Override
public String toString(){//重载,便于观察成员
return "name:"+name+"\nsex:"+sex+"\nage:"+age;
}
}
得到结果:
name:null
sex:FEMALE
age:21
可见name没有序列化,这里为null是因为反序列化时name为String自动初始化为null,同理,如果transient修饰的是age,那么反序列化得到的age将是0
如果想要在transient修饰下实现序列化,可以定义readObject(ObjectInputStream in)和writeObject(ObjectOutputSteam out)
public class person implements Serializable{
transient private String name;
private Sex sex;
private int age;
public person(String name,Sex sex,int age){
this.name=name;
this.sex=sex;
this.age=age;
}
@Override
public String toString(){//重载,便于观察成员
return "name:"+name+"\nsex:"+sex+"\nage:"+age;
}
private void writeObject(ObjectOutputStream out) throws IOException{
out.defaultWriteObject();
out.write(name.getBytes());
}
private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException {
in.defaultReadObject();
byte b[]=new byte[4];//两个汉字占4个字节
in.read(b);
name=new String(b,"GB2312");//byte[]转为String 修改编码格式
}
}
在writeObject中首先调用了ObjectOutputStream的defaultWriteObject()方法,此时会忽略添加transient关键字的name字符串,然后调用write方法将name转成字节流写入
在readObject中首先调用了ObjectInputStream的defaultReadObject()方法,此时会忽略transient关键字,然后读入字节数组,转成字符串。
如果transient修饰的是age,那么不需要考虑字符串转换问题,可以直接使用out.writeInt,in.readInt实现序列化
也可以把字符串当做对象
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;
public class person implements Serializable{
transient private String name;
private Sex sex;
private int age;
public person(String name,Sex sex,int age){
this.name=name;
this.sex=sex;
this.age=age;
}
@Override
public String toString(){//重载,便于观察成员
return "name:"+name+"\nsex:"+sex+"\nage:"+age;
}
private void writeObject(ObjectOutputStream out) throws IOException{
out.defaultWriteObject();
out.writeObject(name);
}
private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException {
in.defaultReadObject();
name=(String)in.readObject();
}
}
Externalizable接口实现序列化:
public class person2 implements Externalizable{
private String name;
private Sex sex;
private int age;
public person2(String name,Sex sex,int age){
this.name=name;
this.sex=sex;
this.age=age;
}
public person2(){
}
@Override
public String toString(){//重载,便于观察成员
return "name:"+name+"\nsex:"+sex+"\nage:"+age;
}
@Override
public void writeExternal(ObjectOutput out) throws IOException {
out.writeObject(name);
out.writeObject(sex);
out.writeInt(age);
}
@Override
public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException {
name=(String)in.readObject();
sex=(Sex)in.readObject();
age=in.readInt();
}
}
注意此时需要重载writeExternal方法和writeExternal方法,同时需要保留一个默认的无参构造函数,因为externalizable反序列化是首先根据无参构造函数创建一个对象,然后根据readExternal方法来填充成员数值,如果不提供默认的无参构造函数就会提示没有合适的构造函数的错误。
同时需要注意transient对Externalizable接口不起作用:
import java.io.Externalizable;
import java.io.IOException;
import java.io.ObjectInput;
import java.io.ObjectOutput;
public class person2 implements Externalizable{
private String name;
private Sex sex;
transient private int age;
public person2(String name,Sex sex,int age){
this.name=name;
this.sex=sex;
this.age=age;
}
public person2(){
}
@Override
public String toString(){//重载,便于观察成员
return "name:"+name+"\nsex:"+sex+"\nage:"+age;
}
@Override
public void writeExternal(ObjectOutput out) throws IOException {
out.writeObject(name);
out.writeObject(sex);
out.writeInt(age);
}
@Override
public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException {
name=(String)in.readObject();
sex=(Sex)in.readObject();
age=in.readInt();
}
}
结果:
name:小宝贝
sex:FEMALE
age:21
serialVersionUID 的作用:
SerlalVersionUID主要是进行一致性检验,比如我发送端使用person类序列化一个对象并发出,接收端修改了person类反序列化就会导致serialVersionUID不一致,从而保证了一致性。
可在定义变量中加入
private static final long serialVersionUID = 1L;
总结:
实现序列化和反序列的方法共有两种,实现Serializable或者Externalizable接口。他们区别是Serializable会默认序列化所有成员,而Externalzable需要程序员指定序列化的内容(重载writeExternalizable和readExternalizable方法),同时Externalizable需要保留默认的无参构造函数。
使用transient对于Serializable有效,被transient修饰的成员不会被序列化.但是Serializable可以重载readObject和writeObject方法来具体实现该成员(比如arraylist源码中不希望element数组全部序列化,而仅仅序列化有内容的数组空间,所以实现了Serializable接口,同时用transient修饰该数组,再重载writeObject,仅仅序列化element数组中有内容的部分)
一般来说如果大部分成员都是需要序列化,只有个别成员不需要序列化或者需要程序员指定序列化内容的就使用Serializable接口,然后对该部分使用transient关键字,然后看是否需要重载readObject方法或者writeObject方法。而如果只要序列化少部分成员,那就实现Externalizable接口,然后重载readExternal和writeExternal方法。最后注意不管Serializable还是Externalizable接口,重载相应read和write方法都必须遵循同样的顺序。