序列化的意义
在考虑系统性能的时候,会考虑序列化。远程通信的时候,就要考虑序列化。序列化和反序列化是每天都会碰到的问题。就我而言,序列化这个概念基本上一直在听到,但是很少有了解。Java对象的传输、分布式架构、大数据量的工程。
Java 平台允许我们在内存中创建可复用的Java 对象,但一般情况下,只有当JVM处于运行时,这些对象才可能存在,所以这些对象的生命周期不会比JVM 的生命周期更长。但在现实应用中,就可能要求在JVM停止运行之后能够保存(持久化)指定的对象,并在将来重新读取被保存的对象。Java 对象序列化就能够帮助我们实现该功能。
序列化是把对象的状态信息转化为可存储或传输的形式过程,也就是把对象转化为字节序列的过程称为对象的序列化。
反序列化是序列化的逆向过程,把字节数组反序列化为对象,把字节序列恢复为对象的过程成为对象的反序列化。
那么如何去评价一个序列化工具呢?
评价一个序列化算法优劣的两个重要指标是:序列化以后的数据大小;序列化操作本身的速度及系统资源开销(CPU、内存)。
常见的序列化工具
Java本身自带的序列化
Java本身就有一个序列化工具(实现Serializable接口),但是缺点也是存在的:(1)序列化后数据比较大。(2)其他语言无法识别
Java本身序列化方法:
@Override public <T> byte[] serializer(T obj) { ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream(); ObjectOutputStream objectOutputStream = null; try { objectOutputStream = new ObjectOutputStream(byteArrayOutputStream); objectOutputStream.writeObject(obj); } catch (IOException e) { e.printStackTrace(); } finally { if(objectOutputStream != null) { try { objectOutputStream.close(); } catch (IOException e) { e.printStackTrace(); } } } return byteArrayIutputStream.toByteArray(); } @Override public <T> T deSerializer(byte[] bytes, Class<T> clazz) { ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(bytes); ObjectInput objectInput = null; try { objectInput = new ObjectInputStream(byteArrayInputStream); return (T)objectInput.readObject(); } catch (Exception e) { e.printStackTrace(); } return null; }
对于一个希望通过序列化的类,可能会报InvalidClassException,这是因为序列化的类最好加上serialVersionUID,用来让系统判断序列化的可靠性。
serialVersionUID 的作用
Java 的序列化机制是通过判断类的serialVersionUID 来验证版本一致性的。在进行反序列化时,JVM 会把传来的字节流中的serialVersionUID与本地相应实体类的serialVersionUID 进行比较,如果相同就认为是一致的,可以进行反序列化,否则就会出现序列化版本不一致的异常,即是InvalidCastException如果没有为指定的class 配置serialVersionUID,那么java 编译器会自动给这个class 进行一个摘要算法,类似于指纹算法,只要这个文件有任何改动,得到的UID 就会截然不同的,可以保证在这么多类中,这个编号是唯一的。
serialVersionUID 有两种显示的生成方式:
一是默认的1L,比如:private static final long serialVersionUID = 1L;
二是根据类名、接口名、成员方法及属性等来生成一个64 位的哈希字段。
当实现java.io.Serializable 接口的类没有显式地定义一个serialVersionUID 变量时候,Java 序列化机制会根据编译的Class 自动生成一个serialVersionUID 作序列化版本比较用,这种情况下,如果Class 文件(类名,方法明等)没有发生变化(增加空格,换行,增加注释等等),就算再编译多次,serialVersionUID 也不会变化的。
Java对象序列化过程中,如果序列化得到的对象增加或者减少一个变量,并不会报错,仅仅是某个字段读取不到。对于一个静态变量的序列化,静态变量不会参与序列化。因为序列化保存的是一个对象的状态,而静态变量属于一个类的状态。
transient
transient修饰的字段表示不会在序列化过程被保存,他的值在反序列化之后仍是类定义的值。也可以手动写到流里面,来绕过序列化
序列化与继承
如果说一个子类实现了序列化,父类没有实现序列化,在子类被反序列化之后,是无法获得父类的值,即子类继承父类已经被定义的那个值是空的。
如果一个父类实现序列化,那么子类自动实现序列化,不用继承Serializable接口。
对于同一个对象写入流两次,流里的数据不会加倍,而是增加五个字节(增加新增引用和一些控制信息的空间),因为当流里存在同一个对象的时候,只是会增加一个引用。这算是个优点,节省了存储空间。
序列化实现克隆
Java对于每个接口类都具有克隆能力,但只是浅克隆。浅克隆只是新建对象,对原对象的一些变量只是复制它的引用。我实例2 克隆实例1,当实例2改变某个值后,实例1也会改变。深克隆实现Serializable接口,把对象序列化流中,再从流里读出来,这个对象就不是原来的对象了,所有的变量的引用都会新建一个引用。
xml序列化框架
优点是可读性强,缺点是序列化之后的数据比较大。在技术要求比较高的时候,一般不会用到它。
代码实例:
public class XmlSerializer { XStream xStream = new XStream(new DomDriver()); public <T> String serializer(T obj) { return xStream.toXML(obj); } public <T> T deSerializer(String bytes, Class<T> clazz) { return (T)xStream.fromXML(bytes); } public static void main(String[] args) { XmlSerializer iSerializer = new XmlSerializer(); User user = new User(); String bytes = iSerializer.serializer(user); user.setName("jolivan"); System.out.println(new String(bytes)); User userNow = iSerializer.deSerializer(bytes,User.class); System.out.println(userNow.getName()); System.out.println(userNow.getAge()); } }==============输出================== <serial.User> <name>Lushe</name> <age>23</age> </serial.User>Lushe23
我们可以看到,根据数据我们就知道他是个什么类,里面有啥。可读性非常高。
JSON
JSON(JavaScript Object Notation)是一种轻量级的数据交换格式,相对于XML 来说,JSON 的字节流更小,而且可读性也非常好。现在JSON数据格式在企业运用是最普遍的
JSON 序列化常用的开源工具有很多
1. Jackson (https://github.com/FasterXML/jackson)
2. 阿里开源的FastJson (https://github.com/alibaba/fastjon)
3. Google 的GSON (https://github.com/google/gson)
这几种json序列化工具中,Jackson 与fastjson 要比GSON 的性能要好,但是Jackson、GSON 的稳定性要比Fastjson 好。而fastjson的优势在于提供的api 非常容易使用。
用阿里的FastJson来示例一下:
public class FastjsonSerializeer implements ISerializer { @Override public <T> byte[] serializer(T obj) { return JSON.toJSONBytes(obj); } @Override public <T> T deSerializer(byte[] bytes, Class<T> clazz) { return (T)JSON.parseObject(bytes,clazz); } public static void main(String[] args) { ISerializer iSerializer = new FastjsonSerializeer(); User user = new User(); byte [] bytes = iSerializer.serializer(user); user.setName("jolivan"); System.out.println(new String(bytes)); User userNow = iSerializer.deSerializer(bytes,User.class); System.out.println(userNow.getName()); } } ===================输出=====================
{"age":"23","name":"Lushe"}
Lushe
hessian
dubbo里使用的就是它,但对它做了优化,又称为hessian2。
Protobuf
优势:(1)独立语言(可以基于不同的语言)、独立平台(跨平台交互)(2)性能高,压缩性好;(3)解析性好
缺陷:实现起来很麻烦,学习成本大。
它有独立的编译器
序列化的实际应用举例
比如说我们在一个分布式系统中,有一个订单模块和一个支付模块,订单模块基于一个协议(dubbo)调用订单系统,底层传输的事二进制数据,那么我们要做的事情就是把对象转化为一个二进制数据,这就是序列化的场景。