java序列化与反序列化
对象序列化是一种持久化技术,广泛运用于网络传输、RMI等场景中。java对象存在于JVM运行时,然而有时期望即使JVM关闭了也可以保存当前对象以备将来重新使用或者给其他JVM使用,为解决类似问题可以采用持久化技术。java中对象的序列化就是将对象转化为字符序列存放在磁盘或内存中,反序列化则相反。对象要想序列化,要求相应的类实现Serializable接口。注意,序列化保存的只是对象的状态,并不包含方法和静态成员变量。下面是实现序列化的一个小例子:
public class People implements Serializable {
private static final long serialVersionUID = -2189866613532876533L;
public String mName;
public int mAge;
public static String mSchool = "WUST";
public People(String name, int age) {
mName = name;
mAge = age;
}
public static void setSchool(String mSchool) {
People.mSchool = mSchool;
}
@Override
public String toString() {
return "People{" +
"mName='" + mName + '\'' +
", mAge=" + mAge +
'}';
}
public static void main(String[] args) {
People people = new People("ldg", 22);
File file = new File("D:\\Users\\lidegui371\\Desktop\\People");
try {
ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream(file));
System.out.println(People.mSchool);
out.writeObject(people);
People.setSchool("武汉科技大学");
ObjectInputStream in = new ObjectInputStream(new FileInputStream(file));
People p1 = (People) in.readObject();
System.out.println(p1);
System.out.println(People.mSchool);
} catch (IOException e) {
e.printStackTrace();
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
}
}
输出为:WUST People{mName='ldg', mAge=22} 武汉科技大学
类中的serialVersionUID用来标识反序列的类与序列化的类是否为同一个类,假如没有声明serialVersionUID,系统在序列化和反序列时会调用相应的算法生成这个标识,一旦这个类有一点修改都会导致这个标识不一样,所以在将之前序列化对象反序列化时会因为serialVersionUID不一样而出错。虚拟机是否允许反序列化不仅取决于类路径和功能代码是否一致,还取决于这两个类的序列化 ID 是否一致。如果serialVersionUID一样,即使类有改动也可以成功地反序列化(如果类添加了字段,值为java初始值,比如int=0,String=null;如果字段减少,则不影响没改变的其他字段)。
被transient所修饰的字段可以在序列化时不被序列化到文件中,在反序列化时该字段被赋予初始值。如果想让transient修饰的字段也可以进行序列化与反序列化可以在类中定义writeObject和readObject方法,自己控制序列化,如果类中没有这两个方法,则系统会调用ObjectOutputStream中的defaultWriteObject方法和ObjectInputStream中的defaultReadObject方法。
在序列化的时候,private字段的数据是以明文的形式存放的,这在网络传输中及其不安全,所以可以在类中定义readObject和writeObject方法实现序列化数据模糊化,比如:
private void writeObject(ObjectOutputStream out) throws IOException {
// 第一种写法
System.out.println("未加密前的年龄:" + mAge);
ObjectOutputStream.PutField field = out.putFields();
field.put("mAge", mAge >> 1);
field.put("mName", mName);
out.writeFields();
System.out.println("加密完成");
// // 第二种写法
// mAge = mAge >> 2;
// out.defaultWriteObject();
}
private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException {
// 第一种写法
ObjectInputStream.GetField field = in.readFields();
mName = (String) field.get("mName", "");
mAge = field.get("mAge", 0);
System.out.println("读出来的年龄:" + mAge);
mAge = mAge << 1;
System.out.println("解密完成");
// // 第二种写法
// in.defaultReadObject();
// mAge = mAge << 2;
}
由上可知writeObject和readObject方法可以自定义控制序列化。控制序列化还可以使用writeReplace和readResolve,只不过使用了这两个方法后是将序列化对象或反序列化的对象给替换了。比如有一个People类,它在writeReplace方法中返回了一个PeopleProxy的代理类,那么序列化的对象就变成了PeopleProxy,序列化与反序列化时与PeopleProxy类中相关的方法有关,所以可以利用这个方法旧类序列化为新版本。readResolve则相反,将反序列化的数据序列化为指定的对象,这个方法可以用来保护性恢复单例。这四者的调用顺序为:writeReplace—>writeObject—>readObject—>readResolve。需要注意的是,当这个类调用了writeReplace后,序列化的控制就转到了这个方法返回的那个对象的类中了。
在JAVA中,序列化时除了实现Serializable还可以实现Externalizable接口,它继承于Serializable并声明了两个abstract方法:writeExternal和readExternal,所以它要求手动实现序列化与反序列化,用法和Serializable的writeObject和readObject一样。Externalizable在效率上比Serializable要高,但如果不需要自定义序列化过程,一般使用Serializable。
在android开发中,除了使用Serializable还可以使用Parcelable实现对象序列化。Serializable使用起来开销较大,通过IO流写入到磁盘和传输到网络,android设计的Parcelable是为了在程序内不同组件间和android跨进程而设计的一种序列化方式,它是通过IBinder通信的消息的载体,携带的数据仅存在内存中,由于内存的读写速度比磁盘的要快,所以在内存间的数据传输推荐使用Parcelable,但因其在持久化和网络传输上操作复杂,在这方面推荐使用Serializable。