序列化
为什么要用序列化
什么情况下需要序列化 :
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对象