序列化与反序列化是开发过程中不可或缺的一步,简单来说,序列化是将对象转换成字节流的过程,而反序列化的是将字节流恢复成对象的过程。两者的关系如下:
django的django-rest框架序列——基础知识 - 图1
序列化与反序列化是一个标准(具体参考XDR:外部数据表示标准 RFC 1014),它是编程语言的一种共性,只是有些编程语言是内置的(如Java,PHP等),有些语言是通过第三方库来实现的(如C/C++)。

使用场景

  • 对象的持久化(将对象内容保存到数据库或文件中)
  • 远程数据传输(将对象发送给其他计算机系统)

    为什么需要序列化与序列化?

    序列化与序列化主要解决的是数据的一致性问题。简单来说,就是输入数据与输出数据是一样的。
    对于数据的本地持久化,只需要将数据转换为字符串进行保存即可是实现,但对于远程的数据传输,由于操作系统,硬件等差异,会出现内存大小端,内存对齐等问题,导致接收端无法正确解析数据,为了解决这种问题,Sun Microsystems在20世纪80年代提出了XDR规范,于1995年正式成为IETF标准。

    django-rest的的序列化与反序列化

    详情信息请差看——本文链接

    序列化兼容性

    序列化的兼容性指的是对象的结构变化(如增删字段,修改字段,字段修饰符的改变等)对序列化的影响。为了能够识别对象结构的变化,Serializable使用serialVersionUID字段来标识对象的结构。默认情况下,它会根据对象的数据结构自动生成,结构发生变化后,它的值也会跟随变化。虚拟机在反序列化的时候会检查serialVersionUID的值,如果字节码中的serialVersionUID和要被转换的类型的serialVersionUID不一致,就无法进行正常的反序列化。

    序列化兼容性

    序列化的兼容性指的是对象的结构变化(如增删字段,修改字段,字段修饰符的改变等)对序列化的影响。为了能够识别对象结构的变化,Serializable使用serialVersionUID字段来标识对象的结构。默认情况下,它会根据对象的数据结构自动生成,结构发生变化后,它的值也会跟随变化。虚拟机在反序列化的时候会检查serialVersionUID的值,如果字节码中的serialVersionUID和要被转换的类型的serialVersionUID不一致,就无法进行正常的反序列化。
    示例:将Account对象保存到文件中,然后在Account类中添加address字段,再从文件中读取之前保存的内容。

    1. // 将Account对象保存到文件中
    2. FileOutputStream fos = new FileOutputStream(file);
    3. ObjectOutputStream oos = new ObjectOutputStream(fos);
    4. oos.writeObject(account);
    5. oos.flush();
    6. // 修改Account对象的结构
    7. public class Account implements Serializable {
    8. private int age;
    9. private long birthday;
    10. private String name;
    11. private String address;
    12. public Account(int age, String name) {
    13. this.age = age;
    14. this.name = name;
    15. }
    16. }
    17. // 读取Account的内容
    18. FileInputStream fis = new FileInputStream(file);
    19. ObjectInputStream ois = new ObjectInputStream(fis);
    20. Account account2 = (Account)ois.readObject();

    由于在保存Account对象后修改了Account的结构,会导致serialVersionUID的值发生变化,在读文件(反序列化)的时候就会出错。所以为了更好的兼容性,在序列化的时候,最好将serialVersionUID的值设置为固定的。

    1. public class Account implements Serializable {
    2. private static final long serialVersionUID = 1L;
    3. private int age;
    4. private long birthday;
    5. private String name;
    6. }

    序列化的存储规则

    Java中的序列化在将对象持久化(序列化)的时候,为了节省磁盘空间,对于相同的对象会进行优化。当多次保存相同的对象时,其实保存的只是第一个对象的引用。

    1. // 将account对象保存两次,第二次保存时修改其用户名
    2. Account account = new Account("Freeman");
    3. FileOutputStream fos = new FileOutputStream(file);
    4. ObjectOutputStream oos = new ObjectOutputStream(fos);
    5. oos.writeObject(account);
    6. System.out.println("fileSize=" +file.length());
    7. account.setUserName("Tom");
    8. oos.writeObject(account);
    9. System.out.println("fileSize=" +file.length());
    10. // 读取两次保存的account对象
    11. FileInputStream fis = new FileInputStream(file);
    12. ObjectInputStream ois = new ObjectInputStream(fis);
    13. Account account2 = (Account)ois.readObject();
    14. Account account3 = (Account)ois.readObject();
    15. System.out.println("account2.name=" + account2.getUserName() + "\n account3.name=" + account3.getUserName() + "\naccount2==account3 -> " + account2.equals(account3));

    输出结果:

    1. account2.name=Freeman
    2. account3.name=Freeman
    3. account2==account3 -> true

    所以在对同一个对象进行多次序列化的时候,最好通过clone一个新的对象再进行序列化。

    序列化对单例的影响

    反序列化的时候,JVM会根据序列化生成的内容构造新的对象,对于实现了Serializable的单例类来说,这相当于开放了构造方法。为了保证单例类实例的唯一性,我们需要重写resolveObject方法。

    1. /**
    2. * 在反序列化的时候被调用
    3. * @return 返回根据字节码创建的新对象
    4. * @throws ObjectStreamException
    5. */
    6. private Object readResolve()throws ObjectStreamException {
    7. return instance;
    8. }

    控制序列化过程

    虽然直接使用Serializable很方便,但有时我们并不想序列化所有的字段,如标识选中状态的isSelected字段,涉及安全问题的password字段等。此时可通过通过以下方法实现:

  1. 给不想序列化的字段添加static或transient修饰词:

Java中的序列化保存的只是对象的成员变量,既不包括static成员(static成员属于类),也不包括成员方法。同时Java为了让序列化更灵活,提供了transient关键字,用来关闭字段的序列化。

  1. public class Account implements Serializable {
  2. private static final long serialVersionUID = 1L;
  3. private String userName;
  4. private static String idcard;
  5. private transient String password;
  6. }
  1. 直接使用Externalizable接口控制序列化过程:

Externalizable也是Java提供的序列化接口,与Serializable不同的是,默认情况下,它不会序列化任何成员变量,所有的序列化,反序列化工作都需要手动完成。

  1. public class Account implements Externalizable {
  2. private static final long serialVersionUID = 1L;
  3. private String userName;
  4. private String idcard;
  5. private String password;
  6. @Override
  7. public void writeExternal(ObjectOutput out) throws IOException {
  8. out.writeObject(userName);
  9. }
  10. @Override
  11. public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException {
  12. userName = (String) in.readObject();
  13. }
  1. 自己实现序列化/反序列化过程
    public class Account implements Serializable {

    1. private static final long serialVersionUID = 1L;
    2. private String userName;
    3. private transient String idcard;
    4. private String password;
    5. private void writeObject(ObjectOutputStream oos)throws IOException {
    6. // 调用默认的序列化方法,序列化非transient/static字段
    7. oos.defaultWriteObject();
    8. oos.writeObject(idcard);
    9. }
    10. private void readObject(ObjectInputStream ois) throws IOException, ClassNotFoundException {
    11. // 调用默认的反序列化方法,发序列化非transient/static字段
    12. ois.defaultReadObject();
    13. idcard = (String)ois.readObject();
    14. }

    数据交换协议

    序列化与反序列化为数据交换提供了可能,但是因为传递的是字节码,可读性差。在应用层开发过程中不易调试,为了解决这种问题,最直接的想法就是将对象的内容转换为字符串的形式进行传递。具体的传输格式可自行定义,但自定义格式有一个很大的问题——兼容性,如果引入其他系统的模块,就需要对数据格式进行转换,维护其他的系统时,还要先了解一下它的序列化方式。为了统一数据传输的格式,出现了几种数据交换协议,如:JSON, Protobuf,XML。这些数据交换协议可视为是应用层面的序列化/反序列化。

    JSON

    JSON(JavaScript Object Notation)是一种轻量级,完全独立于语言的数据交换格式。目前被广泛应用在前后端的数据交互中。

    语法

    JSON中的元素都是键值对——key:value形式,键值对之间以”:”分隔,每个键需用双引号引起来,值的类型为String时也需要双引号。其中value的类型包括:对象,数组,值,每种类型具有不同的语法表示。

    对象

    对象是一个无序的键值对集合。以”{“开始,以”}”结束, 每个成员以”,”分隔。例如:

    1. "value" : {
    2. "name": "Freeman",
    3. "gender": 1
    4. }
    5. 复制代码

    数组

    数组是一个有序的集合,以”[“开始,以”]”结束,成员之间以”,”分隔。例如:

    1. "value" : [
    2. {
    3. "name": "zhangsan",
    4. "gender": 1
    5. },
    6. {
    7. "name": "lisi",
    8. "gender": 2
    9. }
    10. ]
    11. 复制代码

    值类型表示JSON中的基本类型,包括String,Number(byte, short, int, long, float, double), boolean。

    1. "name": "Freeman"
    2. "gender": 1
    3. "registered": false
    4. "article": null
    5. 复制代码

    ==注意==:对象,数组,值这三种元素可互相嵌套!

    1. {
    2. "code": 1,
    3. "msg": "success",
    4. "data": [
    5. {
    6. "name": "zhangsan",
    7. "gender": 1
    8. },
    9. {
    10. "name": "lisi",
    11. "gender": 2
    12. }
    13. ]
    14. }
    15. 复制代码

    对于JSON,目前流行的第三方库有Gson, fastjson:关于Gson的详细介绍,参考Gson使用教程

    Protobuf

    Protobuf是Google实现的一种与语言无关,与平台无关,可扩展的序列化方式,比XML更小,更快,使用更简单。
    Protobuf具有很高的效率,并且几乎为主流的开发语言都提供了支持,具体参考Protobuf开发文档
    在Android中使用Protobuf,需要protobuf-gradle-plugin插件,具体使用查看其项目说明。

    XML

    XML(Extensible Markup Language)可扩展标记语言,通过标签描述数据。示例如下:

    1. <?xml version="1.0" encoding="UTF-8"?>
    2. <person>
    3. <name>Freeman</name>
    4. <gender>1</gender>
    5. </person>
    6. 复制代码

    使用这种方式传输数据时,只需要将对象转换成这种标签形式,在接收到数据后,将其转换成相应的对象。
    关于JAVA开发中对XML的解析可参考四种生成和解析XML文档的方法详解

    数据交换协议如何选择

    从性能,数据大小,可读性三方面进行比较,结果如下:

协议 性能 数据大小 可读性
JSON
Protobuf
XML

对于数据量不是很大,实时性不是特别高的交互,JSON完全可以满足要求,毕竟它的可读性高,出现问题容易定位(注:它是目前前端,app和后端交换数据使用的主流协议)。而对于实时性要求很高,或数据量大的场景,可使用Protobuf协议。具体数据交换协议的比较可参考github.com/eishay/jvm-…

本文摘自掘金的Liberuman的序列化与反序列化