序列化的含义:
将内存中的一个Java对象编码成一个字节流(序列化),并从字节流中重新构建出新的对象(反序列化)。
序列化的作用:
对象一旦被序列化后,就可以从一台正在运行的虚拟机传输到另一台虚拟机上,或者将序列化的对象保存起来(比如:保存到磁盘),供以后反序列化时使用。
Java是如何实现对象序列化?
默认序列化,实现序列化接口java.io.Serializable,该接口只是一个标识,表示说明该对象可以序列化。
使用默认序列化时,会要求生成一个私有属性serialVersionUID,虚拟机通过在运行时判断类的serialVersionUID来验证版本是否一致。在进行反序列化的过程中,虚拟机会用本地相应实体类的serialVersionUID与传输过来的二进制流中的serialVersionUID进行比较,相同就说明它们的实体类是一致的,可以进行反序列化,不同的话就会抛出异常。生成序列化的两种方式:
1) 默认生成的是1L。(如果源文件不写,运行时jvm会自动添加)
2) 根据类名,方法名和属性名等生成的一个64位的hash值。
此种方式进行序列化和反序列化需要用到 java.io.ObjectOutputStream和java.io.ObjectInputStream这两个类。
class A implements Serializable { private String bbb; //生成的文件中有“bbb”字样 private static String info_static; //生成的文件中没有“info_static”字样 private transient String info_tran; //生成的文件中没有“info_tran”字样
private String info_A; //生成的文件中有“info_A”字样
public A(String info_tran, String info_A) {
A.info_static = "XXX";
this.info_tran = info_tran;
this.info_A = info_A;
}
@Override
public String toString() {
return "A{" +
"info_tran='" + info_tran + '\'' +
", info_A='" + info_A + '\'' +
", info_static'" + info_static +
'}';
}
}
class B implements Serializable {
private A a;
public B(A a) {
this.a = a;
}
@Override
public String toString() {
return "B{" + "a=" + a + '}';
}
}
public class SerializableTest03 {
public static void main(String[] args) throws Exception {
B b = new B(new A("transient的值", "info_A的值"));
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("E:\\test.txt"));
System.out.println(b);
oos.writeObject(b);
oos.close();
ObjectInputStream ois = new ObjectInputStream(new FileInputStream("E:\\test.txt"));
B bb = (B) ois.readObject();
System.out.println(bb);
ois.close();
}
}
运行结果:
生成的序列化文件
小小的总结一下:
1) 序列化的是实例对象的信息,static关键字是与类相关联的,所以static修饰的属性不会被序列化。
2) 被transient修饰的属性不会被序列化,这是给开发人员指定那些不序列化的“特殊”属性的。
3) 实现Serializable接口,序列化是完全序列化,即实例对象和实例对象的对象属性的信息也完全序列化
除上面所写的,在要序列化的类中添加指定的方法可以实现更精确的序列化控制。
给B类添加下面的两个方法
private void writeObject(ObjectOutputStream oos) throws IOException { System.out.println("我自己控制一下序列化的详细方式"); oos.defaultWriteObject(); //在使用正常的序列化之后,加上自己需要的信息,一起序列化。 oos.writeObject("#######加点自己的修饰#####"); } private void readObject(ObjectInputStream ois) throws IOException, ClassNotFoundException { System.out.println("我自己控制一下反序列化的详细方式"); ois.defaultReadObject(); //在反序列化后,读取自己添加的信息。一起反序列化 Object o = ois.readObject(); System.out.println(o); } 执行结果:
B{a=A{info_tran='transient的值', info_A='info_A的值', info_static'XXX}} 我自己控制一下序列化的详细方式 我自己控制一下反序列化的详细方式 #######加点自己的修饰##### B{a=A{info_tran='null', info_A='info_A的值', info_static'XXX}} |
序列化文件的内容:
再小小的总结一下:
为什么要特意加writeObject()和readObject()这两个方呢?Serializable接口中没有这两个方法,
这两个方法又都是私有的,类的外部也访问不到,那到底怎么调用这两个方法呢?
ObjectOutputStream这个类的内部中使用了反射来查找是否声明了这两个方法,
因为ObjectOutputStream使用getPrivateMethod(),所以这些方法不得不被声明为private以至于
供ObjectOutputStream来使用。(这点让本人感觉很无语。但是作用还是很大的。)
要序列话的对象中的所有non-static和non-transient属性都将不会被序列化。
(比如:不想让密码被序列化为二进制的流以免被拦截破解,造成隐私泄露),
可以先调用defaultWriteObject()方法让正常的属性先序列化,再通过程序加密密码属性,
把加密后的密码属性的二进制流加入对象序列化的流里,这样就减少密码被破解的几率了。
补充一些其他的
介绍一下Externalizable接口,该接口是继承于Serializable ,是实现序列化的另一种方式。
区别在于Externalizable多声明了两个方法readExternal()和writeExternal(),子类必须实现二者。
Serializable是内建支持的也就是直接implement即可,但Externalizable的实现类必须提供readExternal()
和writeExternal()实现。对于Serializable来说,Java自己建立对象图和字段进行对象序列化,可能会占用更多空间。
而Externalizable则完全需要程序员自己控制如何写/读,麻烦但可以有效控制序列化的存储的内容。
---------------------------------------------
花了一天的时间,记录了这个博客,原来那些看起来长长的博客都是前辈花很多时间和精力才完成的,
向前辈们致敬了。
如有错误,还请大侠及时指出,多谢~。