Serializable相关知识

来源:互联网 发布:整形医院网络咨询招聘 编辑:程序博客网 时间:2024/06/05 20:13

Java的对象序列化是指将那些实现了Serializable接口的对象转换成一个字符序列,并能够在以后将这个字节序列完全恢复为原来的对象。这一过程甚至可通过网络进行,这意味着序列化机制能自动弥补不同操作系统之间的差异。 只要对象实现了Serializable接口(记住,这个接口只是一个标记接口,不包含任何的方法)。

序列化是指将对象实例的状态存储到存储媒体的过程。在此过程中,先将对象的公共字段和私有字段以及类的名称(包括类所在的程序集)转换为字节流,然后再把字节流写入数据流。在随后对对象进行反序列化时,将创建出与原对象完全相同的副本。

如果我们想要序列化一个对象首先要创建某些OutputStream(如FileOutputStream、ByteArrayOutputStream等),然后将这些OutputStream封装在一个ObjectOutputStream中。这时候,只需要调用writeObject()方法就可以将对象序列化,并将其发送给OutputStream(记住:对象的序列化是基于字节的,不能使用Reader和Writer等基于字符的层次结构)。而序列过程(即将一个序列还原成为一个对象),需要将一个InputStream(如FileInputstream、ByteArrayInputStream等)封装在ObjectInputStream内,然后调用readObject()即可。

持久存储

对象序列化后,类的名称、程序集以及类实例的所有数据成员均被写入存储媒体中。对象通常用成员变量来存储对其他实例的引用。类序列化后,序列化引擎将跟踪所有已序列化的引用对象,以确保同一对象不被序列化多次。

按值封送

对象仅在创建对象的应用程序域中有效。除非对象是从 MarshalByRefObject 派生得到或标记为 Serializable,否则,任何将对象作为参数传递或将其作为结果返回的尝试都将失败。如果对象标记为 Serializable,则该对象将被自动序列化,并从一个应用程序域传输至另一个应用程序域,然后进行反序列化,从而在第二个应用程序域中产生出该对象的一个精确副本。此过程通常称为按值封送。

implements Serializable:

没有implements Serializable,你就不能通过RMI(包括EJB)提供远程调用serialization 允许你将实现了Serializable接口的对象转换为字节序列,这些字节序列可以被完全存储以备以后重新生成原来的对象。
serialization不但可以在本机做,而且可以经由网络操作。这个好处是很大的----因为它自动屏蔽了操作系统的差异字节顺序等。比如,在Window平台生成一个对象并序列化之,然后通过网络传到一台Unix机器上,然后可以在这台Unix机器上正确地重构这个对象。
Object serialization主要用来支持2种主要的特性:
1.Java的RMI(remote method invocation).RMI允许在本机上一样操作远程机器上的对象。当发送消息给远程对象时,就需要用到serializaiton机制来发送参数和接收返回
2.Java的JavaBeans. Bean的状态信息通常是在设计时配置的。Bean的状态信息必须被存起来,以便当程序运行时能恢复这些状态信息。这也需要serializaiton机制。
总之如果在网络的环境下做类传输,应该还是implements Serializable。

serialVersionUID

serialVersionUID 用来表明类的不同版本间的兼容性 

简单来说,Java的序列化机制是通过在运行时判断类的serialVersionUID来验证版本一致性的。在进行反序列化时,JVM会把传来的字节流中的serialVersionUID与本地相应实体(类)的serialVersionUID进行比较,如果相同就认为是一致的,可以进行反序列化,否则就会出现序列化版本不一致的异常。

当实现java.io.Serializable接口的实体(类)没有显式地定义一个名为serialVersionUID,类型为long的变量时,Java序列化机制会根据编译的class自动生成一个serialVersionUID作序列化版本比较用,这种情况下,只有同一次编译生成的class才会生成相同的serialVersionUID。

如果我们不希望通过编译来强制划分软件版本,即实现序列化接口的实体能够兼容先前版本,未作更改的类,就需要显式地定义一个名为serialVersionUID,类型为long的变量,不修改这个变量值的序列化实体都可以相互进行串行化和反串行化

一般情况下建议序列化的class都给一个序列化的ID,这样可以保证序列化的成功版本的兼容性。

 

思考:

1)反序列化后的对象,需要调用构造函数重新构造吗?

答案:不需要。对于Serializable对象,对象完全以它存储的二进制位作为基础来构造,而不调用构造器

2)序列前的对象与序列化后的对象是什么关系?是("=="还是equal?是浅复制还是深复制?)

答案:深复制,反序列化还原后的对象地址与原来的的地址不同

请看下面这段代码

House.java:

package test.serializable;

import java.io.Serializable;

import java.util.Date;

/**

 * 用于测试序列化时的deep copy

 *

 */public class Houseimplements Serializable {

    private static final long serialVersionUID = -6091530420906090649L;

    private Date date = new Date(); //记录当前的时间

    

    public String toString() {

        return "House:" + super.toString() + ".Create Time is:" + date;

    }

}

 

Animal.java:

package test.serializable;

import java.io.Serializable;

public class Animalimplements Serializable {

    private static final long serialVersionUID = -213221189192962074L;

    private String name;

    private House house;

    

    public Animal(String name , House house) {

        this.name = name;

        this.house = house;

        System.out.println("调用了构造器");

    }

    

    public String toString() {

        return  name + "[" +super.toString() + "']" + house;

    }

}

 

Myworld.java:

package test.serializable;

import java.io.ByteArrayInputStream;

import java.io.ByteArrayOutputStream;

import java.io.IOException;

import java.io.ObjectInputStream;

import java.io.ObjectOutputStream;

 

public class Myworld {

 

    public static void main(String[] args)throws IOException, ClassNotFoundException {

        House house = new House();

        System.out.println("序列化前");

        Animal animal = new Animal("test",house);

        System.out.println(animal);

        ByteArrayOutputStream out = new ByteArrayOutputStream();

        ObjectOutputStream oos = new ObjectOutputStream(out);

        oos.writeObject(animal);

        oos.writeObject(animal);//在写一次,看对象是否是一样,       

oos.flush();

        oos.close();

        

        ByteArrayOutputStream out2 = new ByteArrayOutputStream();//换一个输出流

        ObjectOutputStream oos2 = new ObjectOutputStream(out2);

        oos2.writeObject(animal);

        oos2.flush();

        oos2.close();

 

        System.out.println("反序列化后");

        ByteArrayInputStream in = new ByteArrayInputStream(out.toByteArray());

        ObjectInputStream ois = new ObjectInputStream(in);

        Animal animal1 = (Animal)ois.readObject();

        Animal animal2 = (Animal)ois.readObject();

        ois.close();

        

        ByteArrayInputStream in2 = new ByteArrayInputStream(out2.toByteArray());

        ObjectInputStream ois2 = new ObjectInputStream(in2);

        Animal animal3 = (Animal)ois2.readObject();

        ois2.close();

        

        System.out.println("out流:" +animal1);

        System.out.println("out流:" +animal2);

        System.out.println("out2流:" +animal3);

        

        System.out.println("测试序列化前后的对象 ==:"+ (animal==animal1));

        System.out.println("测试序列化后同一流的对象:"+ (animal1 == animal2));

        System.out.println("测试序列化后不同流的对象==:" + (animal1==animal3));

    }

}

 

运行结果如下:

序列化前

调用了构造器

test[test.serializable.Animal@bb7465']House:test.serializable.House@d6c16c.Create Time is:Sat Apr 06 00:11:30 CST 2013

反序列化后

out流:test[test.serializable.Animal@4f80d6']House:test.serializable.House@193722c.Create Time is:Sat Apr 06 00:11:30 CST 2013

out流:test[test.serializable.Animal@4f80d6']House:test.serializable.House@193722c.Create Time is:Sat Apr 06 00:11:30 CST 2013(与上面的相同)

out2流:test[test.serializable.Animal@12cc95d']House:test.serializable.House@157fb52.Create Time is:Sat Apr 06 00:11:30 CST 2013(与上面只是值相同,但是地址不一样。)

测试序列化前后的对象 == :false

测试序列化后同一流的对象:true

测试序列化后不同流的对象==:false

 

从结果可以看到:

序列化前后对象的地址不同了,但是内容是一样的,而且对象中包含的引用也相同。换句话说,通过序列化操作,我们可以实现对任何可Serializable对象的深度复制(deep copy"——这意味着我们复制的是整个对象网,而不仅仅是基本对象及其引用。对于同一流的对象,他们的地址是相同,说明他们是同一个对象,但是与其他流的对象地址却不相同。也就说,只要将对象序列化到单一流中,就可以恢复出与我们写出时一样的对象网,而且只要在同一流中,对象都是同一个。

原创粉丝点击