阿里云上做网站套模板怎么做,微信小程序制作公司,瑞安公司做网站,电脑网站手机版怎么做对于Java的序列化#xff0c;我们可以认为是在数据传输的时候的一套协议或者是一个标准#xff0c;因为Java存在自己特定的一个数据结构#xff08;class#xff09;#xff0c;举个例子
data class User(val name: String,val age: Int
)User是一个对象#xff0c;我们…对于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.readObjectUser(/storage/emulated/0/a.out)
Log.e(TAG, result$result)这是常规的序列化和反序列化
resultUser(namelayz4android, age25)那么此时我做什么操作呢先序列化将数据存储本地然后修改一下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: resultUser(namelayz4android, age25, sexnull)transient关键字
还是拿User来说默认情况下所有的字段都会参与序列化那么如果某个字段不想参与序列化那么有什么手段吗就是使用transient关键字。
data class User(Transientval name: String,val age: Int,
) : Serializable {companion object {val serialVersionUID 1}
}反序列化后我们就可以看到因为name没有参与序列化所以拿到的值为null
E/TAG: resultUser(namenull, age25)如果一个类中的类成员变量不支持序列化会发生什么情况
场景如下UserInner没有实现序列化接口
data class UserInner(val a: Int,val b: String
)data class User(Transientval 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(Transientval 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 constructorat 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 objectObject orig obj;Class? cl obj.getClass();ObjectStreamClass desc;Class repCl;// 核心代码 1desc 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 timeif (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) {// 核心代码 2writeOrdinaryObject(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 PrivilegedActionVoid() {public Void run() {// 获取serialVersionUIDsuid 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. selfthrow 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。
Android 学习笔录
Android 性能优化篇https://qr18.cn/FVlo89 Android Framework底层原理篇https://qr18.cn/AQpN4J Android 车载篇https://qr18.cn/F05ZCM Android 逆向安全学习笔记https://qr18.cn/CQ5TcL Android 音视频篇https://qr18.cn/Ei3VPD Jetpack全家桶篇内含Composehttps://qr18.cn/A0gajp OkHttp 源码解析笔记https://qr18.cn/Cw0pBD Kotlin 篇https://qr18.cn/CdjtAF Gradle 篇https://qr18.cn/DzrmMB Flutter 篇https://qr18.cn/DIvKma Android 八大知识体https://qr18.cn/CyxarU Android 核心笔记https://qr21.cn/CaZQLo Android 往年面试题锦https://qr18.cn/CKV8OZ 2023年最新Android 面试题集https://qr18.cn/CgxrRy Android 车载开发岗位面试习题https://qr18.cn/FTlyCJ 音视频面试题锦https://qr18.cn/AcV6Ap