原来一直被误导了!序列化应该这样理解……
最近在研究跨进程通信,其中涉及一个概念叫序列化,总感觉这个东西似懂非懂——
似懂非懂的东西,你想用好它,那就是猴子捞月——瞎折腾~
不行,必须搞懂!
咔咔一顿搜,咚咚各种问……
大部分博客写到 “序列化就是将对象转化成字节序列的过程“,然后就开始讲如何做了,但这样写,我很难想明白一个有方法、有属性的简单对象,怎么就能把它转换成字符串了呢?
终究没有解决心头疑问~
技术征途上果真步步有难,处处是坑啊!
长叹一声,终究还是需要请自己出手
这次从一手资料入手,维基百科+百度百科+代码,通过缜密的逻辑思考合理性,加上不停的与GPT交流
终于, 豁然开朗了——
原来一直被误导了!!!
序列化 不是将对象转换转化成字节序列的过程,而是将对象状态(所有属性值)转换成方便传输和保存的数据格式(一般指字符串、字符序列)的过程
这里面有三个点需要大大的注意:
-
序列化不考虑,也不保存对象的方法,序列化后再反序列化,增加减少方法都不会报错,都无感知;
-
序列化考虑对象的成员变量、对象的元数据(创建时间、权限)等状态信息,序列化就是把这些信息从对象中取出来,整理成一种数据格式(一般指字符串)的过程;
-
序列化不保存对象所属于类的信息,反序列化过程需要提供参照类。
对象是活的,它拥有各种能力、各种关系、不断改变着自己,它占用这个世界上空间,在时间中穿梭~
序列化只是把时间定格,保留它在那个时刻点的自身状态,但它有什么能力不是序列化关注的
想通过反序列化从字符序列还原出来一个对象, 就需要了解这个了解这个对象的所属类的一切
举个例子,首先是一个类定义
/**
* @description: box定义
* @date 2024-7-16
*/
public class Box {
private String name;
private String id;
private int length;
private int width;
private int height;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
public int getLength() {
return length;
}
public void setLength(int length) {
this.length = length;
}
public int getWidth() {
return width;
}
public void setWidth(int width) {
this.width = width;
}
public int getHeight() {
return height;
}
public void setHeight(int height) {
this.height = height;
}
public boolean contain(Box box) {
return box.getLength() <= this.length && box.getWidth() <= this.width && box.getHeight() <= this.height;
}
/**
* 获取周长
* @return 周长
*/
public int getPerimeter() {
return 2 * (this.length + this.width);
}
}
使用
Box myBox = new Box();
myBox.setName("我的盒子");
myBox.setId("1");
myBox.setLength(100);
myBox.setWidth(100);
myBox.setHeight(100);
String BoxString = new Gson().toJson(b);
LogUtil.i(TAG, "box: " + BoxString );
// Log打印 box: {"height":100,"id":"1","length":100,"name":"我的盒子","width":100}
// 序列化 结果是 {"height":100,"id":"1","length":100,"name":"我的盒子","width":100}
这里可以看到,序列化之后 myBox这个对象,就变成一个字符串了,这个字符串中 只包含myBox的各种属性信息,如果只有这个字符串,我们甚至不知道它是什么~
又比如还有一个类
public class SimpleBox {
public String name;
public String id;
public int length;
public int width;
public int height;
}
我们可以利用上一步骤中得到的BoxString来得到一个SimpleBox
SimpleBox simpleBox= new Gson().fromJson(BoxString, SimpleBox.class);
到这里,我们就明白了,序列化只保存了对象的状态,其中最关键的就是属性或者叫成员变量,接下来我们谈谈如何序列化
再次回顾下 , 序列化关键是把成员变量值从对象中取出来,组装成一个数据格式(一般指字符序列)的过程
从这里可以看到序列化是分为两个大步骤的:
-
获取对象(参与序列化)的成员变量
-
把所有成员变量值,按照约定的方式组装到一起
获取对象(参与序列化)成员变量方法有两种,序列化也可以按照此进行分类:
1.通过反射获取对象成员变量
例如就上面的Box类的myBox对象,我们怎么能够获取它所有成员变量呢,通过get方法,不一定可以,因为有一些可能没有get方法,那很容易想到反射,反射就是可以获取一个对象所有的成员变量名称和对应的值,那如果一个成员变量不想参与序列化呢,设计一个标志关键字就好了,比如Java中的transient,C#中的NonSerialized,问题解决!
常见的Java Serializable实现用到的ObjectOutputStream、ObjectInputStream、JSON、Jackson、FastJson都是利用反射来获取对象属性,然后再组装成特定格式,ObjectInputStream、JSON、Jackson、FastJson 规定了如何组装,以及最后的格式~
反射序列化方法可以适用于几乎所有的对象,并且由于格式标准,可以很好的在不同平台之间传递,比如很多网络请求 就用JSON。
2 自定义序列化和反序列化接口
故名思意,就是一个对象,自己实现了序列化和反序列化方法,
序列化时候 由这个对象所属类的方法来决定把什么写入到传输格式中,
反序列化时候 由这个由这个对象所属类的方法来决定如何用传入的格式化数据来构造对象,
自定义序列化的代表有 Protocol Buffers、FlatBuffers、Android Parcel等等。
最后,我们介绍下 Android Parcel,它是Android平台上特有的序列化和反序列化方案:
Parcle是一种数据格式,它设计初衷就是为了方便数据传输,你可以把当做类似String的东西
当你想利用这一套进行序列化的时候,你需要让你的类实现Parcelable接口,主要是实现前文提到的序列化和反序列化两个方法:
public final class NotificationHistory implements Parcelable{
@Override
public void writeToParcel(Parcel dest, int flags) {
// 这里自定义了把当前NotificationHistory对象哪些属性写入到数据结构 Parcel中
}
public static final @NonNull Creator<NotificationHistory> CREATOR
= new Creator<NotificationHistory>() {
@Override
public NotificationHistory createFromParcel(Parcel source) {
// 这里定义了 如何利用一个Parcel格式数据创造一个 NotificationHistory对象
return new NotificationHistory(source);
}
@Override
public NotificationHistory[] newArray(int size) {
return new NotificationHistory[size];
}
};
}
}
Parcel本身一种高效的二进制数据格式,而且序列化和反序列化过程中没有使用反射,非常高效;但Parcel是Android系统特有的类,所以这它只能在Android系统内部进行使用,主要用于跨进程binder传输数据。
其他各种序列化方法也都是这两种序列化的组合或者延伸~
好了,到此结束,这会不妨扩展下思维,如果想把一个现实中的植物、动物甚至是人序列化后传输,再反序列化,我们需要了解什么呢,欢迎各位大佬评论区交流~