序列化

为什么要用序列化

对于一个存在于Java虚拟机中的对象来说,其内部的状态只保持在内存中。JVM停止之后,这些状态就丢失了。在很多情况下,对象的内部状态是需要被持久化下来的。最直接的做法是保存到文件系统或是数据库之中,但这涉及到自定义存储格式以及繁琐的数据转换。序列化提供JVM中对象与字节数组织间的转换,从而实现了对象的持久化。
序列化分为两大部分:序列化和反序列化。序列化是这个过程的第一部分,将数据分解成字节流,以便存储在文件中或在网络上传输。反序列化就是打开字节流并重构对象。

什么情况下需要序列化 :

  a)当你想把的内存中的对象状态保存到一个文件中或者数据库中时候;  

  b)当你想用套接字在网络上传送对象的时候;  

  c)当你想通过RMI传输对象的时候; 

 
序列化的代码实现

待序列化的Java类只需要实现Serializable接口即可。Serializable仅是一个标记接口,并不包含任何需要实现的具体方法。实现该接口只是为了声明该Java类的对象是可以被序列化的。

Serializeable接口的序列化和反序列化的实现是通过ObjectOutputStream和ObjectInputStream来完成的。ObjectOutputStream的writeObject方法可以把一个Java对象写入到流中,ObjectInputStream的readObject方法可以从流中读取一个Java对象。Eg: 

private static void serializePerson() throws FileNotFoundException,IOException {
    try {
        User user = new User("Alex", "Cheng");
        ObjectOutputStream output = new ObjectOutputStream(new FileOutputStream("user.bin"));
        output.writeObject(user);
        output.close();
    } catch (IOException e) {
        e.printStackTrace();
    }
}

private static Person deserializePerson() throws IOException, Exception {
    try {
        ObjectInputStream input = new ObjectInputStream(new FileInputStream("user.bin"));
        User user = (User) input.readObject();
        System.out.println(user);
    } catch (Exception e) {
        e.printStackTrace();
    }
}

 如果想对序列化的过程进行更加细粒度的控制,就需要在类中添加writeObject和对应的 readObject方法。在添加自己的逻辑之前,推荐的做法是先调用Java的默认实现。

private void writeObject(ObjectOutputStream output) throws IOException {
    output.defaultWriteObject();
    output.writeUTF("Hello World");
}

private void readObject(ObjectInputStream input) throws IOException, ClassNotFoundException {
    input.defaultReadObject();
    String value = input.readUTF();
    System.out.println(value);
}

 

什么样的数据能被序列化

当一个父类实现序列化,子类自动实现序列化,不需要显式实现Serializable接口

- Java对象中的非静态和非瞬时域都会被包括进来,而与域的可见性声明没有关系。声明为static和transient类型的成员数据不能被序列化。这可能会导致某些不应该出现的域被包含在序列化之后 的字节数组中,比如密码等隐私信息。由于Java对象序列化之后的格式是固定的,其它人可以很容易的从中分析出其中的各种信息。对于这种情况,一种解决办法是把域声明为transient。另一种是使用serialPersistentFields声明,即只有firstName这个域是要被序列化的。

private static final ObjectStreamField[] serialPersistentFields = {
  new ObjectStreamField("firstName", String.class)
};

在通过ObjectInputStream的readObject方法读取到一个对象之后,这个对象是一个新的实例,但是其构造方法是没有被调用的,其中的域的初始化代码也没有被执行。对于那些没有被序列化的域,在新创建出来的对象中的值都是默认的。这有可能会造成一些隐含的错误。调用者并不知道对象是通过一般的new操作符来创建的,还是通过反序列化所得到的。解决的办法就是在类的readObject方法里面,再执行所需的对象初始化逻辑。

Serializable interface doesn't have any method. 

当一个对象的实例变量引用其他对象,序列化该对象时也把引用对象进行序列化;

序列化的原理

通过在实现了Serializable接口的类中定义一个序列号private static final long serialVersionUID = xxxxL; 该序列号在反序列化过程中用于验证序列化对象的,它是根据类名、接口名、成员方法及属性等来生成一个64位的哈希字段。对于开发人员来说,需要记得的就是在版本更新过程中保持该值不变。

如果用户没有自己声明一个serialVersionUID,接口会默认生成一个,但建议自己定义。因为默认的serialVersionUID对于class的细节非常敏感,如果在序列化之后修改了类的定义,那么反序列化得到的serialVersionUID将会和原来不同

为什么要用RMI技术

RMI(Remote Method Invocation)是Java中的远程过程调用(Remote Procedure Call,RPC)实现,是一种分布式Java应用的实现方式。它的目的在于对开发人员屏蔽横跨不同JVM和网络连接等细节,使得分布在不同JVM上的对象像是存在于一个统一的JVM中一样,可以很方便的互相通讯。

RMI的序列化机制

为了通过Java的序列化机制来进行传输,远程接口中的方法的参数和返回值,要么是Java的基本类型,要么是远程对象,要么是实现了 Serializable接口的Java类。当客户端通过RMI注册表找到一个远程接口的时候,所得到的其实是远程接口的一个动态代理对象。当客户端调用其中的方法的时候,方法的参数对象会在序列化之后,传输到服务器端。服务器端接收到之后,进行反序列化得到参数对象。并使用这些参数对象,在服务器端调用实际的方法。调用的返回值Java对象经过序列化之后,再发送回客户端。客户端再经过反序列化之后得到Java对象,返回给调用者。这中间的序列化过程对于使用者来说是透明的,由动态代理对象自动完成。除了序列化之外,RMI还使用了动态类加载技术。当需要进行反序列化的时候,如果该对象的类定义在当前JVM中没有找到,RMI会尝试从远端下载所需的类文件定义。可以在RMI程序启动的时候,通过JVM参数java.rmi.server.codebase来指定动态下载Java类文件的URL。

JavaBean的序列化

Bean的状态信息通常是在设计时配置的。这些状态信息必须保存起来,供程序启动的时候用。对象序列化就负责这个工作。

Json的序列化

  • JSON.stringify() - 将对象序列化为JSON字符串
  • JSON.parse() - 将JSON数据解析为Javascript对象

 

posted on 2015-07-02 09:43  joannae  阅读(468)  评论(0编辑  收藏  举报

导航