什么是序列化,为什么需要序列化

什么是序列化和反序列化

  • 序列化:对于Java来说,序列化本质上是把Java对象转换为字节数组(byte[])的过程,序列化之后的byte[]就可以用来存储到文件中,或者在网络中传输

  • 反序列化:把字节数组转换为Java对象的过程,如客户端从文件中或网络上获得序列化后的对象字节流,根据字节流中所保存的对象状态及描述信息,通过反序列化重建对象

    这里有必要分析一下为什么一定要转换为byte[]来存储文件或者在网络中传输,首先看常用的网络传输socket示例:

    private Socket socket;
    private DataOutputStream out;
    
    out = new DataOutputStream(socket.getOutputStream());
    byte[] bytes = { 0x030x02,0x01 };
    out.write(bytes);
    

    首先, Api就要求的用byte[], 为什么Api要求,是因为最底层接口

private native void socketWrite0(FileDescriptor fd, byte[] b, int off,
                                     int len) throws IOException;

可以看到,这是个native方法,说明是操作系统原生方法,限定只能有byte[], 那为啥操作系统要限定呢,有这么个说法:

绝大部分计算机的内存的最小单位(1个地址)是8 bit, 所以大部分编程语言, 例如c/c++, java等所有变量最小长度就是8bit(1个字节).

为什么要序列化和反序列化

  1. 可以应用于RMI,在网络中传输对象的字节流,其实网络传输也必须是流;

  2. java序列化对象不仅保留一个对象的数据,而且递归保留对象引用的每个对象的数据,这也就是为什么说可以用序列化方法进行对象的深拷贝,即复制对象本身及引用的所有对象

  3. 序列化后的对象可以落盘或者存在数据库中,所以要持久化的JavaBean需要实现序列化接口

  4. 对象、文件、数据,有许多不同的格式,很难统一传输和保存,其实这样就保证了可以跨平台

如何实现序列化和反序列化

  1. 实现Serializable接口
public class BaseEntity implements Serializable {

   @Id
   @GeneratedValue(strategy = GenerationType.IDENTITY)
   private Integer id;

   public Integer getId() {
       return id;
   }

   public void setId(Integer id) {
       this.id = id;
   }

}

其实序列化接口就是个标记接口,代表可序列化;

上面这个类实现了Serializable代表这个类可序列化,但是不能保证属性可序列化,如果一个可序列化的类的成员既不是基本类型,也不是String类型,那么就必须保证这个引用类型也是可序列化的,也就是说还要保证属性对象都是可以序列化的,否则序列化时会出错;

  1. 如果类的有些属性不需要序列化,可以使用transient 关键字选择不需要序列化的字段
public class BaseEntity implements Serializable {

   private Integer amount;

   private transient Integer frozen;

   @Id
   @GeneratedValue(strategy = GenerationType.IDENTITY)
   private Integer id;

   public Integer getId() {
       return id;
   }

   public void setId(Integer id) {
       this.id = id;
   }

}
  1. serialVersionUID(版本标识)的作用
public class BaseEntity implements Serializable {
   private static final long serialVersionUID = 1L;

   private Integer amount;

   private transient Integer frozen;

   @Id
   @GeneratedValue(strategy = GenerationType.IDENTITY)
   private Integer id;

   public Integer getId() {
       return id;
   }

   public void setId(Integer id) {
       this.id = id;
   }

}

serialVersionUID是用来控制版本兼容的,举个例子,比如上面的BaseEntity, 假如你某天在这个类新增了一个字段age,此时分两种情况

  • 修改serialVersionUID: 那么之前持久化和按照老的BaseEntity结构传输过来的数据反序列化就会报错

  • 不修改serialVersionUID:即向后兼容,那么反序列化之前已持久化的数据不会报错,同样对老的BaseEntity结构传输过来数据反序列化也是成功的

其实上面两个类都没有自定义serialVersionUID,这是因为若不显式定义 serialVersionUID 的值,Java 会根据类细节自动生成 serialVersionUID 的值,如果对类的源代码作了某些修改,再重新编译,新生成的类文件的serialVersionUID的取值有可能也会发生变化。但是规范来说,每个可序列化的类都应该自定义serialVersionUID,一般IDE也会提示你这点,而且不同的Java编译器默认生成的serialVersionUID可能会不一致,所以最好是自定义,尤其是涉及到RPC交互这种,一定要自定义。

几种序列化方式比较

Java原生序列化

public class Person implements Serializable {
    private static final long serialVersionUID = 1L;

    private int age;

    private String name;

    public Person(int age, String name) {
        this.age = age;
        this.name = name;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }
}

上面是一个可序列化的类,如果采用原生序列化方式如下:

public void serialize() {
   FileOutputStream fileOutputStream = new FileOutputStream("target");
   ObjectOutputStream objectOutputStream = new ObjectOutputStream(fileOutputStream);

   Person tom = new Person(12, "Tom");
   objectOutputStream.writeObject(tom);
   objectOutputStream.flush();
   objectOutputStream.close();
}

反序列化如下:

FileInputStream fileInputStream = new FileInputStream("target");
ObjectInputStream objectInputStream = new ObjectInputStream(fileInputStream);

Person person = (Person)objectInputStream.readObject();

Json序列化

Json序列化个人感觉是日常开发见得最多的了,无论你采用的是fastjson, jackson, gson,本质上都是在利用Json序列化。

这里有一点需要说明的是,采用Json序列化的对象Class是不需要Serializable的,但是这并不是说违反了序列化必须实现Serializable的规定,因为Json序列化底层是通过String字符串来完成序列化的,String已经实现了Serializable,并且自定义了serialVersionUID。但是最终肯定还是转成byte[]存储或者传输的,如下:

byte [] serializedUser = JsonConvert.SerializeObject(user).getBytes("UTF-8");

各个json序列化示例:

fastjson

FastJson在复杂类型的Bean转换Json上会出现一些问题,可能会出现引用的类型,导致Json转换出错,需要制定引用。FastJson采用独创的算法,将parse的速度提升到极致,超过所有json库。

// 将Java对象序列化为Json字符串  
String objectToJson = JSON.toJSONString(initUser());
// 将Json字符串反序列化为Java对象  
User user = JSON.parseObject(objectToJson, User.class);

jackson

jackson优点很多:

  1. Jackson 所依赖的jar包较少,简单易用。

  2. 与其他 Java 的 json 的框架 Gson 等相比,Jackson 解析大的 json 文件速度比较快。

  3. Jackson 运行时占用内存比较低,性能比较好

  4. Jackson 有灵活的 API,可以很容易进行扩展和定制。

ObjectMapper objectMapper = new ObjectMapper();  
// 将Java对象序列化为Json字符串  
String objectToJson = objectMapper.writeValueAsString(initUser()); 
// 将Json字符串反序列化为Java对象  
User user = objectMapper.readValue(objectToJson, User.class);  

gson

Gson gson = new GsonBuilder().create();
// 将Java对象序列化为Json字符串
String objectToJson = gson.toJson(initUser());
// 将Json字符串反序列化为Java对象
User user = gson.fromJson(objectToJson, User.class);

Hessian序列化

Hessian 是动态类型、二进制、紧凑的,并且可跨语言移植的一种序列化框架。Hessian 协议要比 JDK、JSON 更加紧凑,性能上要比 JDK、JSON 序列化高效很多,而且生成的字节数也更小

Student student = new Student(); 
student.setNo(101);  
student.setName("HESSIAN"); 
//把student对象转化为byte数组
ByteArrayOutputStream bos = new ByteArrayOutputStream(); 
Hessian2Output output = new Hessian2Output(bos); 
output.writeObject(student); 
output.flushBuffer(); 
byte[] data = bos.toByteArray(); 
bos.close(); 
//把刚才序列化出来的byte数组转化为student对象
ByteArrayInputStream bis = new ByteArrayInputStream(data); 
Hessian2Input input = new Hessian2Input(bis); 
Student deStudent = (Student) input.readObject(); 
input.close(); 
System.out.println(deStudent);

缺点:

  • 官方版本对Java里面一些常见对象的类型不支持,

  • 比如LinkedHashMap、LinkedHashSet 等,但是可以通过扩展CollectionDeserializer 类修复,

  • Locale 类,可以通过扩展 ContextSerializerFactory 类修复;

  • Byte/Short 反序列化的时候变成 Integer

优点:

  • 相对于JDk,JSON,更加高效,生成的字节数更小

  • 有非常好的兼容性和稳定性

ProtoBuf序列化

Protobuf 是 Google 公司内部的混合语言数据标准,是一种轻便、高效的结构化数据存储格式,可以用于结构化数据序列化,支持 Java、Python、C++、Go 等语言。Protobuf使用的时候需要定义 IDL(Interface description language),然后使用不同语言的 IDL编译器,生成序列化工具类

优点:

  1. 高效

  2. 支持多种语言

  3. 支持向前,向后兼容

缺点:

  1. 为了提高性能,protobuf采用了二进制格式进行编码。这直接导致了可读性差

  2. 对于具有反射和动态语言来讲,用起来比较费劲

Thrift序列化

Thrift是Facebook于2007年开发的跨语言的rpc服框架,提供多语言的编译功能,并提供多种服务器工作模式,用户通过Thrift的IDL(接口定义语言)来描述接口函数及数据类型,然后通过Thrift的编译环境生成各种语言类型的接口文件,用户可以根据自己的需要采用不同的语言开发客户端代码和服务器端代码。

优点:

  1. 特性丰富

  2. 性能不错

  3. 有很多开源项目的周边支持 都是 thrift

缺点:

  1. 没有官方文档

  2. Thrift序列化二进制不可读,调试困难

  3. buf fix 和更新不积极,维护成本过高

  4. RPC 在 0.6.1 升级到 0.7.0 是不兼容的


参考:
https://www.cnblogs.com/javazhiyin/p/11841374.html

  • 2
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值