我们知道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转化为连续空间的二进制字节流,才能存储到磁盘上。
常用序列化框架
@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默认的序列化机制缺点:
(2)XML序列化框架
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,每天进步一点点,我们追求在交流中收获成长和快乐