Java中Serializable的序列化、反序列化和transient关键字

序列化的含义:

将内存中的一个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则完全需要程序员自己控制如何写/读,麻烦但可以有效控制序列化的存储的内容。


---------------------------------------------
花了一天的时间,记录了这个博客,原来那些看起来长长的博客都是前辈花很多时间和精力才完成的,
向前辈们致敬了。
如有错误,还请大侠及时指出,多谢~。

猜你喜欢

转载自blog.csdn.net/qq_36819098/article/details/79794155