序列化:对于Java来说,序列化本质上是把Java对象转换为字节数组(byte[])的过程,序列化之后的byte[]就可以用来存储到文件中,或者在网络中传输
反序列化:把字节数组转换为Java对象的过程,如客户端从文件中或网络上获得序列化后的对象字节流,根据字节流中所保存的对象状态及描述信息,通过反序列化重建对象
这里有必要分析一下为什么一定要转换为byte[]来存储文件或者在网络中传输,首先看常用的网络传输socket示例:
private Socket socket;
private DataOutputStream out;out = new OutputStream());
byte[] bytes = { 0x03,0x02,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个字节).
可以应用于RMI,在网络中传输对象的字节流,其实网络传输也必须是流;
java序列化对象不仅保留一个对象的数据,而且递归保留对象引用的每个对象的数据,这也就是为什么说可以用序列化方法进行对象的深拷贝,即复制对象本身及引用的所有对象
序列化后的对象可以落盘或者存在数据库中,所以要持久化的JavaBean需要实现序列化接口
对象、文件、数据,有许多不同的格式,很难统一传输和保存,其实这样就保证了可以跨平台
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类型,那么就必须保证这个引用类型也是可序列化的,也就是说还要保证属性对象都是可以序列化的,否则序列化时会出错;
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;}}
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交互这种,一定要自定义。
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 = (adObject();
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 = JSONString(initUser());
// 将Json字符串反序列化为Java对象
User user = JSON.parseObject(objectToJson, User.class);
jackson
jackson优点很多:
Jackson 所依赖的jar包较少,简单易用。
与其他 Java 的 json 的框架 Gson 等相比,Jackson 解析大的 json 文件速度比较快。
Jackson 运行时占用内存比较低,性能比较好
Jackson 有灵活的 API,可以很容易进行扩展和定制。
ObjectMapper objectMapper = new ObjectMapper();
// 将Java对象序列化为Json字符串
String objectToJson = objectMapper.writeValueAsString(initUser());
// 将Json字符串反序列化为Java对象
User user = adValue(objectToJson, User.class);
gson
Gson gson = new GsonBuilder().create();
// 将Java对象序列化为Json字符串
String objectToJson = Json(initUser());
// 将Json字符串反序列化为Java对象
User user = gson.fromJson(objectToJson, User.class);
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 = ByteArray();
bos.close();
//把刚才序列化出来的byte数组转化为student对象
ByteArrayInputStream bis = new ByteArrayInputStream(data);
Hessian2Input input = new Hessian2Input(bis);
Student deStudent = (Student) adObject();
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编译器,生成序列化工具类
优点:
高效
支持多种语言
支持向前,向后兼容
缺点:
为了提高性能,protobuf采用了二进制格式进行编码。这直接导致了可读性差
对于具有反射和动态语言来讲,用起来比较费劲
Thrift是Facebook于2007年开发的跨语言的rpc服框架,提供多语言的编译功能,并提供多种服务器工作模式,用户通过Thrift的IDL(接口定义语言)来描述接口函数及数据类型,然后通过Thrift的编译环境生成各种语言类型的接口文件,用户可以根据自己的需要采用不同的语言开发客户端代码和服务器端代码。
优点:
特性丰富
性能不错
有很多开源项目的周边支持 都是 thrift
缺点:
没有官方文档
Thrift序列化二进制不可读,调试困难
buf fix 和更新不积极,维护成本过高
RPC 在 0.6.1 升级到 0.7.0 是不兼容的
参考:
.html
本文发布于:2024-02-04 23:08:15,感谢您对本站的认可!
本文链接:https://www.4u4v.net/it/170718490060612.html
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系,我们将在24小时内删除。
留言与评论(共有 0 条评论) |