暂无图片
暂无图片
暂无图片
暂无图片
暂无图片

一文搞懂序列化和反序列化

ITSK 2020-07-30
1032
遇到问题多思考,勤动脑,多画问号,速成长,嘿嘿😁😁😁


我们知道RPC框架中序列化是一个重要的模块,所以详细学习总结了一下,分享给小伙伴们。文章从以下几点介绍:什么是序列化和反序列化?其作用是什么?应用场景是什么?序列化的特性及如何实现?


什么是对象的序列化?反序列化?

序列化:把对象转换为字节序列的过程称为对象的序列化;

反序列化:把字节序列恢复为对象的过程称为对象的反序列化。


序列化能干什么?其应用场景是什么?

我们在项目中通常使用“对象”来进行数据的操作,但是当需要对数据进行存储(固化存储、缓存存储)或者传输(跨进程网络传输)时,“对象”就不怎么好用了,所以我们往往需要把数据转化成连续空间的二进制字节流。

对象序列化的目标是将对象保存到磁盘中,或允许在网络中直接传输对象,使对象可以脱离程序的运行而独立存在。对象序列化机制允许把内存中的Java对象转换成平台无关的二进制流,从而允许把这种二进制流持久地保存在磁盘上,通过网络将这种二进制流传输到另一个网络节点。其他程序一旦获得了这种二进制流(无论是从磁盘中获取的,还是通过网络获取的),都可以将这种二进制流恢复成原来的Java对象。

序列化和反序列化主要的应用场景有:

  • 数据的网络传输:Socket发送的数据不能是对象,必须是连续空间的二进制字节流。
  • 进行远程跨进程服务调用时(例如RPC调用),需要使用特定的序列化技术对需要进行网络传输的对象进行编码或者解码,以便完成远程调用。
  • 缓存的KV存储:Redis/Memcache是KV类型的缓存,缓存存储的value必须是连续空间的二进制字节流,而不能够是对象。
    我们看Jedis保存hash类型数据的源码:就是转成字节数组保存的:
  @Override
public void hmset(final String key, final Map<String, String> hash) {
final Map<byte[], byte[]> bhash = new HashMap<byte[], byte[]>(hash.size());
for (final Entry<String, String> entry : hash.entrySet()) {
bhash.put(SafeEncoder.encode(entry.getKey()), SafeEncoder.encode(entry.getValue()));
}
hmset(SafeEncoder.encode(key), bhash);
}
  • 数据库索引的磁盘存储:数据库的索引在内存里是B+树或者Hash的格式,但这个格式是不能够直接存储到磁盘上的,所以需要把B+树或者Hash转化为连续空间的二进制字节流,才能存储到磁盘上。


序列化的特性
在设计分布式微服务框架时,序列化和反序列化作为框架的一部分,有着举足轻重的地位。比如服务提供者和服务消费者在进行网络通信时,对象需要进行序列化和反序列化,如果序列化框架性能差,就会直接影响整个分布式框架的性能。所以在设计序列化和反序列化框架的时候,我们需要从性能、可扩展性/兼容性、跨语言支持等多个角度进行综合考虑。
(1)性能:
影响序列化与反序列化性能的主要因素有:序列化后码流大小;序列化/反序列化的速度;序列化/反序列化系统开销(CPU或者堆内存)
(2)可扩展性/兼用性:
移动互联时代,业务系统需求的更新周期变得更短,新的需求不断涌现,而老的系统还需要继续维护。如果序列化协议具有良好的可扩展性,支持自动增加新的业务字段,而不影响老的服务,那么将大大提高系统的灵活度。一个好的序列化框架应该支持数据结构的向前兼容,比如新增字段、删除字段、调整字段顺序等。
(3)跨语言支持:
跨语言支持是衡量序列化框架是否通用的一个重要指标,如果序列化框架和某种语言绑定,数据在交换的时候,双方就很难保证一定是采用相同的语言开发的。分布式服务框架在不同的业务、不同的团队可能采用不同的开发语言。不同语言开发的服务要能够互通,序列化和反序列化首先要能够支持互通。

常用序列化框架

(1)Java默认序列化
Java序列化是在JDK 1.1中引入的,是Java内核的重要特性之一。如果需要让某个对象支持序列化机制,则必须让它的类是可序列化的(serializable)。为了让某个类是可序列化的,该类必须实现接口Serializable或Externalizable。序列化处理是通过ObjectInputStream和ObjectOutputStream实现的。
如下是一个简单的序列化示例代码:
@Slf4j
public class JavaSerializer implements ISerializer {


/**
* 对象序列化
*
* @param obj
* @param <T>
* @return
*/
public <T> byte[] serialize(T obj) {
ByteArrayOutputStream baos = new ByteArrayOutputStream();
try {
ObjectOutputStream oos = new ObjectOutputStream(baos);
oos.writeObject(obj);
oos.close();
} catch (IOException e) {
log.error(e.getMessage(), e);
}
return baos.toByteArray();
}


/**
* 对象反序列化
*
* @param bytes
* @param clazz
* @param <T>
* @return
*/
public <T> T deserialize(byte[] bytes, Class<T> clazz) {
ByteArrayInputStream bais = new ByteArrayInputStream(bytes);
try {
ObjectInputStream ois = new ObjectInputStream(bais);
return (T) ois.readObject();
} catch (Exception e) {
throw new RuntimeException("反序列化异常:" + e.getMessage());
}
}


/**
* 将对象序列化到文件中
*
* @param obj
* @param fileName
* @param <T>
*/
public <T> void serializeToFile(T obj, String fileName) {
FileOutputStream fos = null;
ObjectOutputStream oos = null;
try {
fos = new FileOutputStream(fileName);
oos = new ObjectOutputStream(fos);
oos.writeObject(obj);
} catch (Exception e) {
log.error(e.getMessage(), e);
} finally {
if (null != oos) {
try {
oos.close();
} catch (IOException e) {
log.error(e.getMessage(), e);
}
}
if (null != fos) {
try {
fos.close();
} catch (IOException e) {
log.error(e.getMessage(), e);
}
}
}


}


/**
* 从文件中反序列化成对象
*
* @param fileName
* @param clazz
* @param <T>
* @return
*/
public <T> T deserializeFromFile(String fileName, Class<T> clazz) {
FileInputStream fis = null;
ObjectInputStream ois = null;
try {
fis = new FileInputStream(fileName);
ois = new ObjectInputStream(fis);
return (T) ois.readObject();
} catch (Exception e) {
throw new RuntimeException("反序列化异常:" + e.getMessage());
}
}
}

感兴趣小伙伴可以自己写个例子测试一下,这里就不粘测试代码了。

针对Java默认序列化实现机制有几点补充:(①-⑦小伙伴可以自行验证,这里就不贴代码了,但是都是亲自验证过的结论)

①当对某个对象进行序列化时,系统会自动把该对象的所有实例变量依次进行序列化,如果某个实例变量引用到另一个对象,则被引用的对象也会被序列化;如果被引用的对象的实例变量也引用了其他对象,则被引用的对象也会被序列化,这种情况被称为递归序列化。

②当使用Java序列化机制序列化可变对象时一定要注意,只有第一次调用wirteObject()方法来输出对象时才会将对象转换成字节序列,并写入到ObjectOutputStream;在后面程序中即使该对象的实例变量发生了改变,再次调用writeObject()方法输出该对象时,改变后的实例变量也不会被输出。

③Java9为ObjectInputStream增加了setObjectInputFilter()、getObjectInputFilter()两个方法,其中第一个方法用于为对象输入流设置过滤器。当程序通过ObjectInputStream反序列化对象时,过滤器的checkInput()方法会被自动激发,用于检查序列化数据是否有效,这样就让反序列化更加安全、健壮。

④使用transient、static关键字修饰的变量不会被实例化。

⑤反序列化机制在恢复Java对象时无须调用构造器来初始化Java对象。从这个意义上来看,序列化机制可以用来“克隆”对象。

⑥可以在要序列化的类中重写writeObject、readObject、readObjectNoData方法实现自定义序列化和反序列。

⑦实现Externalizable接口,实现方法readExternal、writeExternal也可以实现序列化和反序列化。

⑧Java默认的序列化机制缺点:

       a、只支持Java语言,不支持其他语言;
      b、性能差,序列化后的码流大,对于引用过深的对象序列化容易引起OOM异常;


(2)XML序列化框架

XML序列化使用标签表示数据,可读性高。但是序列化后码流较大,性能不高,适用于性能不高且QPS较低的企业级内部系统之间数据交换的场景。
XML具有语言无关性,可用于异构系统间的数据交换协议。XML序列化和反序列化实现方式有多种,比如XStream和Java自带的XML序列化和反序列化方式等。
下面是示例Java自带的XML序列化和反序列化方式,其他的方式小伙伴可以查资料学习。Java自带的XML序列化和反序列化主要使用XMLEncoder和XMLDecoder类实现相应的功能,具体实例如下所示:
public class XmlSerializable {


public static void main(String[] args) {
File userFile = new File("user.xml");
try {
if (!userFile.exists()) {
userFile.createNewFile();
}
User user = new User("lisi", 22);
BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream(userFile));
XMLEncoder xmlEncoder = new XMLEncoder(bos);
xmlEncoder.flush();


//序列化:将User对象写入到xml中
xmlEncoder.writeObject(user);
xmlEncoder.close();
bos.close();


//反序列化:从xml中读取
XMLDecoder xmlDecoder = new XMLDecoder(new BufferedInputStream(new FileInputStream(userFile)));
User user1 = (User) xmlDecoder.readObject();
xmlDecoder.close();
System.out.println("反序列化的User对象:" + user1);
} catch (IOException e) {
e.printStackTrace();
}
}
}


User.java:
@Data
@AllArgsConstructor
public class User implements Serializable {
private String name;
private int age;


public User() {
System.out.println("user 无参构造。。。");
}
}


运行结果:
user.xml:
<?xml version="1.0" encoding="UTF-8"?>
<java version="1.8.0_201" class="java.beans.XMLDecoder">
<object class="com.ren.study.serializable.model.User">
<void property="age">
<int>22</int>
</void>
<void property="name">
<string>lisi</string>
</void>
</object>
</java>

注意:使用XMLDecoder的readObject反序列化对象的时候调用了对象的无参构造方法,打断点阅读源码可知:


JSON序列化框架

JSON(JavaScript Object Notation,JS对象简谱)是一种轻量级的数据交换格式。

JSON可以支持任何数据类型,例如字符串、数字、对象、数组等。相对于XML,JSON码流更小,而且还保留了XML可读性好的优势。

JSON序列化常用的开源工具有Fastjson(阿里巴巴开源)、Jackson和Google开发的GSON。这三者的详细介绍及使用可以参考如下文章:

JSON 的这些知识你都了解吗

雅静,公众号:ITSKJSON 的这些知识你都了解吗?


序列化相关知识总结到这里,有想要一起讨论的小伙伴非常欢迎评论区留言哦

本文参考如下书籍:

《分布式微服务架构:原理与实战》 

《疯狂Java讲义(第五版)》 

《疯狂Java讲义(第二版)》 


ITSK

文章转载自ITSK,如果涉嫌侵权,请发送邮件至:contact@modb.pro进行举报,并提供相关证据,一经查实,墨天轮将立刻删除相关内容。

评论