关于Java的序列化,咱们可以认为是在数据传输的时分的一套协议或者是一个规范,由于Java存在自己特定的一个数据结构(class),举个比如
data class User(
val name: String,
val age: Int
)
User是一个目标,咱们可以创立一个User目标自己运用,可是实践的场景中,咱们往往不会自己单独运用,而是让其他人也用,或者做持久化存储;例如咱们之前讲的IPC通信框架,在Server端创立的User,想作为参数传递给客户端运用,所以这个时分就需求将这个特定的数据结构转化为一组基本的二进制字节数据,这个进程便是一次序列化的进程;而反序列化则是将这组字节数据转换为特定数据结构。
在实践的事务场景中,假如咱们运用序列化传输数据,一般便是两种:Serializable 和 Parcelable。
1 Serializable原理分析
一般咱们在运用Serializable的时分,都是完结Serializable接口,例如:
data class User(
val name: String,
val age: Int
):Serializable
依照序列化的界说,只需是完结了Serializable接口,那么就会将特定的数据结构转换为一组二进制字节数据。
public fun serialize(user: User): ByteArray {
val bos = ByteArrayOutputStream()
val objectInputSystem = ObjectOutputStream(bos)
//写数据
objectInputSystem.writeObject(user)
return bos.toByteArray()
}
关于序列化,底层的完结便是经过ObjectOutputStream包装类将User转换为一组二进制字节数据。那么反序列化,将一组二进制数据转换为User类,就需求运用ObjectInputStream读取二进制字节数据。
public fun unSerialize(byteArray: ByteArray): User {
val bis = ByteArrayInputStream(byteArray)
val objectInputSystem = ObjectInputStream(bis)
//读数据
return objectInputSystem.readObject() as User
}
所以ObjectOutputStream和ObjectInputStream可以认为是Java提供的一个序列化东西,用于将数据拆分和拼装。
1.1 与序列化相关的问题
在了解了序列化的原理之后,针对序列化相关的一些问题,咱们需求了解一下,假如同伴们在预备面试,这块或许会对你有一些协助。
object SerializeUtils {
fun <T> writeObject(t: T,path:String) {
val objectOutputStream = ObjectOutputStream(FileOutputStream(path))
//读数据
objectOutputStream.writeObject(t)
}
fun <T> readObject(path: String): T {
val objectInputSystem = ObjectInputStream(FileInputStream(path))
//读数据
return objectInputSystem.readObject() as T
}
}
serialVersioUID是什么?
首要咱们先看一个场景:
//先序列化
val user = User("layz4android", 25)
SerializeUtils.writeObject(user,"/storage/emulated/0/a.out")
//再反序列化
val result = SerializeUtils.readObject<User>("/storage/emulated/0/a.out")
Log.e("TAG", "result==>$result")
这是常规的序列化和反序列化
result==>User(name=layz4android, age=25)
那么此刻我做什么操作呢?先序列化将数据存储本地,然后修正一下User实体类的数据结构,加了一个sex字段。
data class User(
val name: String,
val age: Int,
val sex:String
) : Serializable
这个时分报了一个错,具体原因便是要反序列化的这组数据与本地现有类的serialVersionUID不共同。由于每个类默许有一个serialVersionUID,假如没有界说那么就会默许生成,由于此刻User类已经发生了变化(新加了一个字段),此刻这个类便是一个新的类,与本地存储的序列化数组不共同,然后导致序列化失利。
Caused by: java.io.InvalidClassException: com.lay.learn.asm.binder.User; local class incompatible: stream classdesc serialVersionUID = 7638979641876441127, local class serialVersionUID = -6727369848665126143
所以为了做版本的统一管理,需求引进serialVersionUID这个字段,即便是修正了类中的字段,只需是serialVersionUID共同,在序列化、反序列化的时分就会找到这个类。
data class User(
val name: String,
val age: Int,
) : Serializable {
companion object {
val serialVersionUID = 1
}
}
即便是新增了一个字段,也不会报错,而是会将这个参数值为空。
E/TAG: result==>User(name=layz4android, age=25, sex=null)
transient关键字
还是拿User来说,默许情况下,所有的字段都会参加序列化,那么假如某个字段不想参加序列化,那么有什么手法吗?便是运用transient关键字。
data class User(
@Transient
val name: String,
val age: Int,
) : Serializable {
companion object {
val serialVersionUID = 1
}
}
反序列化后,咱们就可以看到,由于name没有参加序列化,所以拿到的值为null
E/TAG: result==>User(name=null, age=25)
假如一个类中的类成员变量不支撑序列化,会发生什么情况?
场景如下,UserInner没有完结序列化接口
data class UserInner(
val a: Int,
val b: String
)
data class User(
@Transient
val name: String,
val age: Int,
val inner: UserInner
) : Serializable {
companion object {
val serialVersionUID = 1
}
}
那么当进行序列化操作的时分报错,便是由于UserInner不支撑序列化。
Caused by: java.io.NotSerializableException: com.lay.learn.asm.binder.UserInner
那么这儿咱们需求了解,序列化其实是一次深复制的操作。关于浅复制(这儿不考虑基本数据类型)只是将引用地址做一次复制;深复制则是需求重新创立一个目标,并把数据复制过去。
所以在序列化的时分,由于还需求做一次本地化存储,所以必定是需求拿到UserInner的数据并存储下来,所以就需求把UserInner内部的数据序列化,可是UserInner又不支撑序列化,所以就会报错。
假如某个类可以序列化,可是其父类不可以序列化,那么这个类可以序列化吗?
父类:
open class Person(
val des: String
)
子类:
data class User(
@Transient
val name: String,
val age: Int,
) : Serializable, Person(name) {
companion object {
val serialVersionUID = 1
}
}
此刻进行序列化操作的时分,报错:没有可用的结构函数。
Caused by: java.io.InvalidClassException: com.lay.learn.asm.binder.User; no valid constructor
at com.lay.learn.asm.SerializeUtils.readObject(Unknown Source:22)
从堆栈信息中,咱们看是在反序列化的时分报错了,阐明在序列化存储的时分是没问题的,那么这儿咱们就需求看下readObject的源码了
private Object readOrdinaryObject(boolean unshared)
throws IOException
{
//......省掉部分代码
Class<?> cl = desc.forClass();
if (cl == String.class || cl == Class.class
|| cl == ObjectStreamClass.class) {
throw new InvalidClassException("invalid class descriptor");
}
Object obj;
try {
obj = desc.isInstantiable() ? desc.newInstance() : null;
} catch (Exception ex) {
throw (IOException) new InvalidClassException(
desc.forClass().getName(),
"unable to create instance").initCause(ex);
}
//......省掉部分代码
return obj;
}
这儿咱们先重视下中心代码,咱们发现最终反序列化的时分,是经过反射创立一个Object目标,此刻咱们看直接调用了newInstance()办法。
所以这儿咱们这样想,在反序列化的时分类似于创立一个子类的进程,此刻应该先创立父类,调用父类的结构办法,由于父类没有完结序列化接口,那么父类信息是缺失的,只能调用一个无参结构办法,那么此刻父类没有空参结构办法,因而直接报错。
open class Person(
val des: String
){
constructor() : this("") {
}
}
反之,假如子类没完结序列化接口,而父类完结了,那么这种情况下是可以完结序列化的,由于承继关系,子类就可以获取父类的序列化能力。
1.2 Java处理序列化的进程
假如咱们想要从源码视点去看序列化的进程,其实只需求重视两个类:ObjectInputStream和ObjectOutputStream。
ObjectOutputStream:用于将特定数据结构拆分红二进制数据,例如类的信息,并本地化存储,那么这个进程怎样完结的?
ObjectInputStream:用于将二进制数据合并成想要的数据结构。
1.2.1 数据怎样拆
拆数据的中心办法便是ObjectOutputStream的writeObject办法,在这个中传入要拆解的目标,咱们以User为例。
public final void writeObject(Object obj) throws IOException {
if (enableOverride) {
writeObjectOverride(obj);
return;
}
try {
writeObject0(obj, false);
} catch (IOException ex) {
if (depth == 0) {
// BEGIN Android-changed: Ignore secondary exceptions during writeObject().
// writeFatalException(ex);
try {
writeFatalException(ex);
} catch (IOException ex2) {
// If writing the exception to the output stream causes another exception there
// is no need to propagate the second exception or generate a third exception,
// both of which might obscure details of the root cause.
}
// END Android-changed: Ignore secondary exceptions during writeObject().
}
throw ex;
}
}
咱们看writeObject0这个办法干了什么?
private void writeObject0(Object obj, boolean unshared)
throws IOException
{
boolean oldMode = bout.setBlockDataMode(false);
depth++;
try {
// ......省掉部分代码
// check for replacement object
Object orig = obj;
Class<?> cl = obj.getClass();
ObjectStreamClass desc;
Class repCl;
// 中心代码 1
desc = ObjectStreamClass.lookup(cl, true);
if (desc.hasWriteReplaceMethod() &&
(obj = desc.invokeWriteReplace(obj)) != null &&
(repCl = obj.getClass()) != cl)
{
cl = repCl;
desc = ObjectStreamClass.lookup(cl, true);
}
// END Android-changed: Make only one call to writeReplace.
if (enableReplace) {
Object rep = replaceObject(obj);
if (rep != obj && rep != null) {
cl = rep.getClass();
desc = ObjectStreamClass.lookup(cl, true);
}
obj = rep;
}
// if object replaced, run through original checks a second time
if (obj != orig) {
subs.assign(orig, obj);
if (obj == null) {
writeNull();
return;
} else if (!unshared && (h = handles.lookup(obj)) != -1) {
writeHandle(h);
return;
}
}
// remaining cases
// BEGIN Android-changed: Make Class and ObjectStreamClass replaceable.
if (obj instanceof Class) {
writeClass((Class) obj, unshared);
} else if (obj instanceof ObjectStreamClass) {
writeClassDesc((ObjectStreamClass) obj, unshared);
// END Android-changed: Make Class and ObjectStreamClass replaceable.
} else if (obj instanceof String) {
writeString((String) obj, unshared);
} else if (cl.isArray()) {
writeArray(obj, desc, unshared);
} else if (obj instanceof Enum) {
writeEnum((Enum<?>) obj, desc, unshared);
} else if (obj instanceof Serializable) {
// 中心代码 2
writeOrdinaryObject(obj, desc, unshared);
} else {
if (extendedDebugInfo) {
throw new NotSerializableException(
cl.getName() + "\n" + debugInfoStack.toString());
} else {
throw new NotSerializableException(cl.getName());
}
}
} finally {
depth--;
bout.setBlockDataMode(oldMode);
}
}
中心代码1
在这儿,咱们看到拿到User类的class目标,然后创立了一个ObjectStreamClass类,咱们看下这个类是干啥的。
private ObjectStreamClass(final Class<?> cl) {
this.cl = cl;
// 获取类名
name = cl.getName();
isProxy = Proxy.isProxyClass(cl);
isEnum = Enum.class.isAssignableFrom(cl);
// 是否完结了serializable接口
serializable = Serializable.class.isAssignableFrom(cl);
externalizable = Externalizable.class.isAssignableFrom(cl);
Class<?> superCl = cl.getSuperclass();
superDesc = (superCl != null) ? lookup(superCl, false) : null;
localDesc = this;
if (serializable) {
AccessController.doPrivileged(new PrivilegedAction<Void>() {
public Void run() {
// 获取serialVersionUID
suid = getDeclaredSUID(cl);
try {
// 获取悉数的字段信息
fields = getSerialFields(cl);
computeFieldOffsets();
} catch (InvalidClassException e) {
serializeEx = deserializeEx =
new ExceptionInfo(e.classname, e.getMessage());
fields = NO_FIELDS;
}
if (externalizable) {
cons = getExternalizableConstructor(cl);
} else {
cons = getSerializableConstructor(cl);
// 看是否有 writeObject readObject readObjectNoData办法
writeObjectMethod = getPrivateMethod(cl, "writeObject",
new Class<?>[] { ObjectOutputStream.class },
Void.TYPE);
readObjectMethod = getPrivateMethod(cl, "readObject",
new Class<?>[] { ObjectInputStream.class },
Void.TYPE);
readObjectNoDataMethod = getPrivateMethod(
cl, "readObjectNoData", null, Void.TYPE);
hasWriteObjectData = (writeObjectMethod != null);
}
writeReplaceMethod = getInheritableMethod(
cl, "writeReplace", null, Object.class);
readResolveMethod = getInheritableMethod(
cl, "readResolve", null, Object.class);
return null;
}
});
} else {
suid = Long.valueOf(0);
fields = NO_FIELDS;
}
try {
fieldRefl = getReflector(fields, this);
} catch (InvalidClassException ex) {
// field mismatches impossible when matching local fields vs. self
throw new InternalError(ex);
}
if (deserializeEx == null) {
if (isEnum) {
deserializeEx = new ExceptionInfo(name, "enum type");
} else if (cons == null) {
deserializeEx = new ExceptionInfo(name, "no valid constructor");
}
}
for (int i = 0; i < fields.length; i++) {
if (fields[i].getField() == null) {
defaultSerializeEx = new ExceptionInfo(
name, "unmatched serializable field(s) declared");
}
}
initialized = true;
}
在这个类当中,咱们看到其实便是拿到User类的class目标之后,获取类中的悉数信息,像Class类名、是否完结了serialize接口、serialVersionUID、悉数字段信息、是否存在writeObject readObject readObjectNoData办法,总归便是将类的悉数信息,封装成了ObjectStreamClass类。
中心代码 2
回到前面的代码中,咱们看会做一系列的判断,判断User是什么类型的目标,由于User完结了Serializable接口,所以会走到writeOrdinaryObject办法中,同时将ObjectStreamClass也传了进来。
private void writeOrdinaryObject(Object obj,
ObjectStreamClass desc,
boolean unshared)
throws IOException
{
if (extendedDebugInfo) {
debugInfoStack.push(
(depth == 1 ? "root " : "") + "object (class "" +
obj.getClass().getName() + "", " + obj.toString() + ")");
}
try {
desc.checkSerialize();
bout.writeByte(TC_OBJECT);
writeClassDesc(desc, false);
handles.assign(unshared ? null : obj);
if (desc.isExternalizable() && !desc.isProxy()) {
writeExternalData((Externalizable) obj);
} else {
writeSerialData(obj, desc);
}
} finally {
if (extendedDebugInfo) {
debugInfoStack.pop();
}
}
}
首要调用了writeClassDesc办法,在这个办法中,做的首要工作便是把之前获取的类信息悉数悉数转换为二进制数据,从writeNonProxyDesc办法中就可以看到,太深化的我就不介绍了,有精力的同伴可以深化看一下。
private void writeClassDesc(ObjectStreamClass desc, boolean unshared)
throws IOException
{
int handle;
if (desc == null) {
writeNull();
} else if (!unshared && (handle = handles.lookup(desc)) != -1) {
writeHandle(handle);
} else if (desc.isProxy()) {
writeProxyDesc(desc, unshared);
} else {
writeNonProxyDesc(desc, unshared);
}
}
然后紧接着调用writeSerialData办法,在这个办法中首要会判断这个类中是否存在writeObject、readObject这种办法,这两个办法的时分,我会在Parcelable专题中介绍,假如有的话,那么就会调用invokeWriteObject办法执行这个办法;假如没有,那么就会调用defaultWriteFields办法,将所有的字段数据转换为二进制数据。
private void writeSerialData(Object obj, ObjectStreamClass desc)
throws IOException
{
ObjectStreamClass.ClassDataSlot[] slots = desc.getClassDataLayout();
for (int i = 0; i < slots.length; i++) {
ObjectStreamClass slotDesc = slots[i].desc;
if (slotDesc.hasWriteObjectMethod()) {
PutFieldImpl oldPut = curPut;
curPut = null;
SerialCallbackContext oldContext = curContext;
if (extendedDebugInfo) {
debugInfoStack.push(
"custom writeObject data (class "" +
slotDesc.getName() + "")");
}
try {
curContext = new SerialCallbackContext(obj, slotDesc);
bout.setBlockDataMode(true);
slotDesc.invokeWriteObject(obj, this);
bout.setBlockDataMode(false);
bout.writeByte(TC_ENDBLOCKDATA);
} finally {
curContext.setUsed();
curContext = oldContext;
if (extendedDebugInfo) {
debugInfoStack.pop();
}
}
curPut = oldPut;
} else {
defaultWriteFields(obj, slotDesc);
}
}
}
总结
所以在writeObject办法中,首要便是干了两件事:
(1)获取User类的悉数类信息,包括办法、字段、SUID等等,将其封装在ObjectStreamClass中;
(2)在拿到悉数类信息后,会将悉数的类信息以及字段数据转换成二进制数据。
从源码中咱们可以看到,在计算类信息的时分,会检查两个办法是否存在,readObject和writeObject,而且只需完结了这两个办法,那么就不会走defaultWriteFields办法,所以假如咱们添加了这两个办法,就可以修正系统默许序列化的完结方法,然后在readObject和writeObject办法中,对字段数据进行调整。
1.2.2 数据怎样拼装
其实这个在之前1.1小节中的一个问题中提到过了,便是经过反射的方法调用无参结构函数创立一个新的目标。由于在序列化的时分,是将类的悉数信息封装在了ObjectStreamClass中,所以反序列化的时分也会获取这些类信息,然后经过反射对所有的字段赋值。
2 Parcelable的呈现是为了处理什么问题
Parcelable是Google为了Android单独做的一套序列化协议,由于咱们知道,Android为了响应速度,放弃了JVM,选择了根据寄存器的Dalvik和ART,所以Parcelable的呈现意图也是共同的—速度。
经过前面咱们关于Serializable的理解,首要Serializable的序列化和反序列化是根据IO的,需求做本地化的磁盘存储;还有一个问题便是,在反序列化的进程中,需求经过反射的形式创立一些新的目标,这些目标也是被存放在堆内存中,会产生内存碎片,其本质还是一个深复制,假如发生频繁的反射调用关于性能上是有损耗的。
因而Parcelable的呈现便是为了处理这些问题,首战之地便是速度问题,由于少了磁盘IO的读写,听说速度是Serializable的10倍;由于Parcelable是根据Binder的,直接在内存中操作数据,减少了磁盘IO操作,可是由于Binder内存的约束,因而不能超过1M,可是Serializable是没有约束的。
除此之外,Parcelable呈现的另一个意图便是为了进程间通信提供数据模型,像在运用AIDL的时分,假如想要进行数据传递,那么就会运用到Parcelable。