序列化产生的背景
由于在系统底层,数据的传输形式是简单的字节序列形式传递,即在底层,系统不认识对象,只认识字节序列,而为了达到进程通讯的目的,需要先将数据序列化,而序列化就是将对象转化字节序列的过程。相反地,当字节序列被运到相应的进程的时候,进程为了识别这些数据,就要将其反序列化,即把字节序列转化为对象
序列化
将数据结构或对象转成二进制串的过程
反序列化
将序列化产生的二进制串转成数据结构或对象的过程。
序列化/反序列化的目的
序列化:主要是用于网络传输,数据持久化,一般序列化也称为编码(Encode) 反序列化:主要是用于从网络,磁盘上读取字节数组还原成原始对象,一般反序列化也称为解码(Decode)
目的:
- 永久的保存对象数据
- 通过序列化操作将对象数据在网络上进行传输
- 将对象数据在进程之间传递
- java平台允许我们在内存中创建可复用的java对象,但一般情况下,只有当jvm处于运行时,这些对象才可能存在,但是现实应用中,可能有jvm停止运行后仍然要保留指定的对象,并且在将来重新读取被保存的对象,这样就可以用java对象序列化实现
- 序列化对象的时候只是针对变量进行序列化,不针对方法进行序列化
- 在intent之间传递数据,如果传递的数据类型比较复杂,就需要进行序列化操作。
Android中常用的序列化和反序列化
-
Serializable接口(java)
标识当前类可以被ObjectOutputStream序列化,以及被ObjectInputStream反序列化
-
Parcelable接口(android sdk)
通过Parcel写入和恢复数据的,并且必须要有一个非空的静态变量 CREATOR
Serializable
Serializable的特点
- 可序列化类中,未实现 Serializable 的属性状态无法被序列化/反序列化
- 也就是说,反序列化一个类的过程中,它的非可序列化的属性将会调用无参构造函数重新创建
- 因此这个属性的无参构造函数必须可以访问,否者运行时会报错
- 一个实现序列化的类,它的子类也是可序列化的
serialVersionUID
用来表明类的不同版本间的兼容性。如果你修改了此类, 要修改此值。否则以前用老版本的类序列化的类恢复时会报错:InvalidClassException
序列化步骤
- 将对象实例相关的类元数据输出。
- 递归地输出类的超类描述直到不再有超类。
- 类元数据完了以后,开始从最顶层的超类开始输出对象实例的实际数据值。
- 从上至下递归输出实例的数据
二进制打开序列化的文件
- AC ED: STREAM_MAGIC. 声明使用了序列化协议.
- 00 05: STREAM_VERSION. 序列化协议版本.
- 0x73: TC_OBJECT. 声明这是一个新的对象.
- 0x72: TC_CLASSDESC. 声明这里开始一个新Class。
- 00 2e: Class名字的长度.
序列化(writeObject)源码分析
->>>进入java.io.ObjectOutputStream类
1.ObjectOutputStream的构造函数设置enableOverride = false
public ObjectOutputStream(OutputStream out) throws IOException {
...
this.enableOverride = false;
...
}
复制代码
2.序列化的时候进入writeObject0方法中
public final void writeObject(Object obj) throws IOException {
if (this.enableOverride) {
this.writeObjectOverride(obj);
} else {
try {
this.writeObject0(obj, false);
} catch (IOException var3) {
if (this.depth == 0) {
this.writeFatalException(var3);
}
throw var3;
}
}
}
复制代码
3.writeObject0会更具类型进入writeOrdinaryObject这个方法
private void writeObject0(Object obj, boolean unshared) throws IOException {
。。。
if (obj instanceof String) {
this.writeString((String)obj, unshared);
return;
} else {
if (cl.isArray()) {
this.writeArray(obj, desc, unshared);
} else if (obj instanceof Enum) {
this.writeEnum((Enum)obj, desc, unshared);
} else {
if (!(obj instanceof Serializable)) {
if (extendedDebugInfo) {
throw new NotSerializableException(cl.getName() + "\n" + this.debugInfoStack.toString());
}
throw new NotSerializableException(cl.getName());
}
this.writeOrdinaryObject(obj, desc, unshared);
}
break;
}
。。。
}
复制代码
4.writeOrdinaryObject主要执行逻辑
private void writeOrdinaryObject(Object obj, ObjectStreamClass desc, boolean unshared) throws IOException {
。。。
if (desc.isExternalizable() && !desc.isProxy()) {
//如果对象实现了Externalizable接口,那么执行
//writeExternalData((Externalizable) obj)方法
this.writeExternalData((Externalizable)obj);
} else {
this.writeSerialData(obj, desc);
}
。。。
}
复制代码
5.我们进入writeSerialData方法
//最终写序列化的方法
private void writeSerialData(Object obj, ObjectStreamClass desc) throws IOException {
ClassDataSlot[] slots = desc.getClassDataLayout();
for(int i = 0; i < slots.length; ++i) {
ObjectStreamClass slotDesc = slots[i].desc;
//判断目标类中是否有writeObject方法,如果有的话调用目标类中
if (slotDesc.hasWriteObjectMethod()) {
ObjectOutputStream.PutFieldImpl oldPut = this.curPut;
this.curPut = null;
SerialCallbackContext oldContext = this.curContext;
if (extendedDebugInfo) {
this.debugInfoStack.push("custom writeObject data (class "" + slotDesc.getName() + "")");
}
try {
this.curContext = new SerialCallbackContext(obj, slotDesc);
this.bout.setBlockDataMode(true);
slotDesc.invokeWriteObject(obj, this);
this.bout.setBlockDataMode(false);
this.bout.writeByte(120);
} finally {
this.curContext.setUsed();
this.curContext = oldContext;
if (extendedDebugInfo) {
this.debugInfoStack.pop();
}
}
this.curPut = oldPut;
} else {
//调用默认的
this.defaultWriteFields(obj, slotDesc);
}
}
}
复制代码
6 这里会涉及到ObjectOutputStream类,会寻找目标类中的私有的writeObject(readObject)方法,赋值给变量writeObjectMethod(readObjectMethod)
private ObjectStreamClass(final Class<?> cl) {
。。。
if (this.serializable) {
AccessController.doPrivileged(new PrivilegedAction<Object>() {
public Void run() {
if (ObjectStreamClass.this.isEnum) {
ObjectStreamClass.this.suid = 0L;
ObjectStreamClass.this.fields = ObjectStreamClass.NO_FIELDS;
return null;
} else if (cl.isArray()) {
ObjectStreamClass.this.fields = ObjectStreamClass.NO_FIELDS;
return null;
} else {
ObjectStreamClass.this.suid = ObjectStreamClass.getDeclaredSUID(cl);
try {
ObjectStreamClass.this.fields = ObjectStreamClass.getSerialFields(cl);
ObjectStreamClass.this.computeFieldOffsets();
} catch (InvalidClassException var2) {
ObjectStreamClass.this.serializeEx = ObjectStreamClass.this.deserializeEx = new ObjectStreamClass.ExceptionInfo(var2.classname, var2.getMessage());
ObjectStreamClass.this.fields = ObjectStreamClass.NO_FIELDS;
}
if (ObjectStreamClass.this.externalizable) {
ObjectStreamClass.this.cons = ObjectStreamClass.getExternalizableConstructor(cl);
} else {
ObjectStreamClass.this.cons = ObjectStreamClass.getSerializableConstructor(cl);
ObjectStreamClass.this.writeObjectMethod = ObjectStreamClass.getPrivateMethod(cl, "writeObject", new Class[]{ObjectOutputStream.class}, Void.TYPE);
ObjectStreamClass.this.readObjectMethod = ObjectStreamClass.getPrivateMethod(cl, "readObject", new Class[]{ObjectInputStream.class}, Void.TYPE);
ObjectStreamClass.this.readObjectNoDataMethod = ObjectStreamClass.getPrivateMethod(cl, "readObjectNoData", (Class[])null, Void.TYPE);
ObjectStreamClass.this.hasWriteObjectData = ObjectStreamClass.this.writeObjectMethod != null;
}
ObjectStreamClass.this.domains = ObjectStreamClass.this.getProtectionDomains(ObjectStreamClass.this.cons, cl);
ObjectStreamClass.this.writeReplaceMethod = ObjectStreamClass.getInheritableMethod(cl, "writeReplace", (Class[])null, Object.class);
ObjectStreamClass.this.readResolveMethod = ObjectStreamClass.getInheritableMethod(cl, "readResolve", (Class[])null, Object.class);
return null;
}
}
});
} else {
this.suid = 0L;
this.fields = NO_FIELDS;
}
。。。
}
复制代码
Serializable注意事项
- static静态变量和transient 修饰的字段是不会被序列化的
- serialVersionUID问题
- 如果某个序列化类的成员变量是对象类型,则该对象类型的类必须实现序列化
- 子类实现了序列化,父类没有实现序列化,父类中的字段丢失问题
- 单利模式的序列化需要重写readResolve这个方法,如果不重写的话,会导致单例模式在序列化->反序列化后失败
Parcelable
Parcelable实现过程主要分为_序列化,反序列化,描述_三个过程,下面分别介绍下这三个过程
Parcel是什么
在介绍Parcelable之前我们需要先了解Parcel是什么?
Parcel其实就是包装了我们需要传输的数据,然后在Binder中传输,也就是用于跨进程传输数据
简单来说,Parcel提供了一套机制,可以将序列化之后的数据写入到一个共享内存中,其他进程通过Parcel可以从这块共享内存中读出字节流,并反序列化成对象
Parcel可以包含原始数据类型(用各种对应的方法写入,比如writeInt(),writeFloat()等),可以包含Parcelable对象,它还包含了一个活动的IBinder对象的引用,这个引用导致另一端接收到一个指向这个IBinder的代理IBinder。
Parcelable通过Parcel实现了read和write的方法,从而实现序列化和反序列化
常见方法
public class Sync implements Parcelable {
protected Sync(Parcel in) {
}
//反序列化
public static final Creator<Sync> CREATOR = new Creator<Sync>() {
@Override
public Sync createFromParcel(Parcel in) {
return new Sync(in);
}
@Override
public Sync[] newArray(int size) {
return new Sync[size];
}
};
//描述
@Override
public int describeContents() {
return 0;
}
// 序列化
@Override
public void writeToParcel(Parcel dest, int flags) {
}
}
复制代码
describeContents :负责文件描述。只针对一些特殊的需要描述信息的对象,需要返回1,其他情况返回0就可以
writeToParcel :返回了Parcel,所以我们可以直接调用Parcel中的write方法,基本的write方法都有,对象和集合比较特殊下面单独讲,基本的数据类型除了boolean其他都有,Boolean可以使用int或byte存储
CREATOR :通过匿名内部类实现Parcelable中的Creator的接口
常见面试题
Parcelable和Serializable的区别和比较
Parcelable和Serializable都是实现序列化并且都可以用于Intent间传递数据,Serializable是Java的实现方式,可能会频繁的IO操作,所以消耗比较大,但是实现方式简单 Parcelable是Android提供的方式,效率比较高,但是实现起来复杂一些 , 二者的选取规则是:内存序列化上选择Parcelable, 存储到设备或者网络传输上选择Serializable
Android里面为什么要设计出Bundle而不是直接用Map结构
Bundle内部是由ArrayMap实现的,ArrayMap的内部实现是两个数组,一个int数组是存储对象数据对应下标,一个对象数组保存key和value,内部使用二分法对key进行排序,所以在添加、删除、查找数据的时候,都会使用二分法查找,只适合于小数据量操作,如果在数据量比较大的情况下,那么它的性能将退化。而HashMap内部则是数组+链表结构,所以在数据量较少的时候,HashMap的Entry Array比ArrayMap占用更多的内存。因为使用Bundle的场景大多数为小数据量,我没见过在两个Activity之间传递10个以上数据的场景,所以相比之下,在这种情况下使用ArrayMap保存数据,在操作速度和内存占用上都具有优势,因此使用Bundle来传递数据,可以保证更快的速度和更少的内存占用。
另外一个原因,则是在Android中如果使用Intent来携带数据的话,需要数据是基本类型或者是可序列化类型,HashMap使用Serializable进行序列化,而Bundle则是使用Parcelable进行序列化。而在Android平台中,更推荐使用Parcelable实现序列化,虽然写法复杂,但是开销更小,所以为了更加快速的进行数据的序列化和反序列化,系统封装了Bundle类,方便我们进行数据的传输。
Android中Intent/Bundle的通信原理及大小限制
Intent 中的 Bundle 是使用 Binder 机制进行数据传送的。能使用的 Binder 的缓冲区是有大小限制的(有些手机是 2 M),而一个进程默认有 16 个 Binder 线程,所以一个线程能占用的缓冲区就更小了( 有人以前做过测试,大约一个线程可以占用 128 KB)
为何Intent不能直接在组件间传递对象而要通过序列化机制?
Intent在启动其他组件时,会离开当前应用程序进程,进入ActivityManagerService进程(intent.prepareToLeaveProcess()),这也就意味着,Intent所携带的数据要能够在不同进程间传输。首先我们知道,Android是基于Linux系统,不同进程之间的java对象是无法传输,所以我们此处要对对象进行序列化,从而实现对象在 应用程序进程 和 ActivityManagerService进程 之间传输。
而Parcel或者Serializable都可以将对象序列化,其中,Serializable使用方便,但性能不如Parcel容器,后者也是Android系统专门推出的用于进程间通信等的接口