序列化
将数据结构或对象转换成二进制串的过程。
反序列化
将在序列化过程中所生成的二进制串转换成数据结构或者对象的过程
序列化/反序列化的目的
简单的概括
- 序列化:主要用于网络传输,数据持久化,一般序列化也称为编码(Encode)
- 反序列化:主要用于从网络,磁盘上读取字节数组还原成原始对象,一般反序列化也称为解码 (Decode)
具体的讲:
- 永久的保存对象数据(将对象数据保存在文件当中,或者是磁盘中);
- 通过序列化操作将对象数据在网络上进行传输(由于网络传输是以字节流的方式对数据进行传输的,因此序列化的目的是将对象数据转换成字节流的形式) ;
- 将对象数据在进程之间进行传递(Activity之间传递对象数据时,需要在当前的Activity中对对象数据进行序列化操作。在另一个Activity中需要进行反序列化操作讲数据取出);
- Java平台允许我们在内存中创建可复用的Java对象,但一般情况下,只有当JVM处于运行时,这些对象才可能存在,即这些对象的生命周期不会比JVM的生命周期更长(即每个对象都在JVM中) 。但在现实应用中,就可能要停止JVM运行,但有要保存某些指定的对象,并在将来重新读取被保存的对象。这是Java对象序列化就能够实现该功能。(可选择入数据库、或文件的形式保存);
- 序列化对象的时候只是针对变量进行序列化,不针对方法进行序列化;
- 在Intent之间,基本的数据类型直接进行相关传递即可,但是一旦数据类型比较复杂的时候,就需要进 行序列化操作了。
常见的序列化和反序列化协议
XML&SOAP
XML 是一种常用的序列化和反序列化协议,具有跨机器,跨语言等优点,SOAP(Simple Object Access protocol) 是一种被广泛应用的,基于 XML 为序列化和反序列化协议的结构化消息传递协议。
JSON(Javascript Object Notation)
JSON 起源于弱类型语言 Javascript, 它的产生来自于一种称之为"Associative array"的概念,其本质是 就是采用"Attribute-value"的方式来描述对象。实际上在 Javascript 和 PHP 等弱类型语言中,类的描 述方式就是 Associative array。JSON 的如下优点,使得它快速成为最广泛使用的序列化协议之一。
这种 Associative array 格式非常符合工程师对对象的理解;
它保持了 XML 的人眼可读(Human-readable)的优点;
相对于 XML 而言,序列化后的数据更加简洁。来自于的以下链接的研究表明:XML 所产生序列化之后文件的大小接近 JSON 的两倍;
它具备 Javascript 的先天性支持,所以被广泛应用于 Web browser 的应用常景中,是 Ajax 的事实标准协议;
与 XML 相比,其协议比较简单,解析速度比较快;
松散的 Associative array 使得其具有良好的可扩展性和兼容性。
Protobuf
Protobuf 具备了优秀的序列化协议的所需的众多典型特征。
- 标准的 IDL 和 IDL 编译器,这使得其对工程师非常友好。
- 序列化数据非常简洁,紧凑,与 XML 相比,其序列化之后的数据量约为 1/3 到 1/10。
- 解析速度非常快,比对应的 XML 快约 20-100 倍。
- 提供了非常友好的动态库,使用非常简介,反序列化只需要一行代码。
Serializable接口
是 Java 提供的序列化接口,它是一个空接口:
public interface Serializable {
}
Serializable 用来标识当前类可以被 ObjectOutputStream 序列化,以及被 ObjectInputStream 反序列 化。
Serializable入门
public class Student implements Serializable {
private static final long serialVersionUID = -2100492893943893602L;//serialVersionUID唯一标识了一个可序列化的类
private String name;
private String sax;
private Integer age;
private List<Course> courses;//Course也需要实现Serializable接口
//用transient关键字标记的成员变量不参与序列化
//(在被反序列化后,transient变量的值被设为初始值,如int型的是 0,对象型的是null)
private transient Date createTime;
//静态成员变量属于类不属于对象,所以不会参与序列化
//(对象序列化保存的是对象的“状态”,也 就是它的成员变量,因此序列化不会关注静态变量)
private static SimpleDateFormat simpleDateFormat = new SimpleDateFormat();
public Student() {
System.out.println("Student: empty");
}
public Student(String name, String sax, Integer age) {
System.out.println("Student: " + name + " " + sax + " " + age);
this.name = name;
this.sax = sax;
this.age = age;
courses = new ArrayList<>();
createTime = new Date();、
}
}
//Course也需要实现Serializable接口
public class Course implements Serializable {
private static final long serialVersionUID = 667279791530738499L;
private String name;
private float score;
}
Serializable 有以下几个特点:
- 可序列化类中,未实现 Serializable 的属性状态无法被序列化/反序列化;
- 也就是说,反序列化一个类的过程中,它的非可序列化的属性将会调用无参构造函数重新创建 ;
- 因此这个属性的无参构造函数必须可以访问,否者运行时会报错;
- 一个实现序列化的类,它的子类也是可序列化的。
serialVersionUID与兼容性
-
serialVersionUID的作用
serialVersionUID 用来表明类的不同版本间的兼容性。如果你修改了此类, 要修改此值。否则以前用老版本的类序列化的类恢复时会报错: InvalidClassException
-
设置方式
在JDK中,可以利用JDK的bin目录下的serialver.exe工具产生这个serialVersionUID,对于 Test.class,执行命令:serialver Test
-
兼容性问题
为了在反序列化时,确保类版本的兼容性,最好在每个要序列化的类中加入 private static final long serialVersionUID这个属性,具体数值自己定义。
这样,即使某个类在与之对应的对象已经序列化出去后做了修改,该对象依然可以被正确反序列化。否则,如果不显式定义该属性,这个属性值将由JVM根据类的相关信息计算,而修改后的类的计算结果与修改前的类的计算结果往往不同,从而造成对象的反序列化因为类版本不兼容而失败。
不显式定义这个属性值的另一个坏处是,不利于程序在不同的JVM之间的移植。因为不同的编译器 实现该属性值的计算策略可能不同,从而造成虽然类没有改变,但是因为JVM不同,出现因类版本不兼容而无法正确反序列化的现象出现。
因此 JVM 规范强烈建议我们手动声明一个版本号,这个数字可以是随机的,只要固定不变就可以。同时最好是 private 和 final 的,尽量保证不变。
Externalizable接口
public interface Externalizable extends java.io.Serializable {
void writeExternal(ObjectOutput out) throws IOException;
void readExternal(ObjectInput in) throws IOException, ClassNotFoundException;
}
简单使用
public class Course1 implements Externalizable {
private static final long serialVersionUID = 667279791530738499L;
private String name;
private float score;
...
@Override
public void writeExternal(ObjectOutput objectOutput) throws IOException{
System.out.println("writeExternal");
objectOutput.writeObject(name);
objectOutput.writeFloat(score);
}
@Override
public void readExternal(ObjectInput objectInput) throws IOException,
ClassNotFoundException {
System.out.println("readExternal");
name = (String)objectInput.readObject();
score = objectInput.readFloat();
}
...
public static void main(String... args) throws Exception {
Course1 course = new Course1("英语", 12f);
ByteArrayOutputStream out = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(out);
oos.writeObject(course);
course.setScore(78f);
oos.close();
ObjectInputStream ois = new ObjectInputStream(new ByteArrayInputStream(bs));
Course1 course1 = (Course1) ois.readObject();
System.out.println("course1: " + course1);
}
readObject/writeObject原理分析
以 oos.writeObject(obj) 为例分析
-
ObjectOutputStream的构造函数设置enableOverride = false
public ObjectOutputStream(OutputStream var1) throws IOException { this.verifySubclass(); this.bout = new ObjectOutputStream.BlockDataOutputStream(var1); this.handles = new ObjectOutputStream.HandleTable(10, 3.0F); this.subs = new ObjectOutputStream.ReplaceTable(10, 3.0F); this.enableOverride = false; //有参构造函数,设置enableOverride = false this.writeStreamHeader(); this.bout.setBlockDataMode(true); if (extendedDebugInfo) { this.debugInfoStack = new ObjectOutputStream.DebugTraceInfoStack(); } else { this.debugInfoStack = null; } }
-
所以writeObject方法执行的是writeObject0(obj, false);
public final void writeObject(Object var1) throws IOException { if (this.enableOverride) { this.writeObjectOverride(var1); } else { try { this.writeObject0(var1, false); //enableOverride=false,走这里 } catch (IOException var3) { if (this.depth == 0) { this.writeFatalException(var3); } throw var3; } } }
-
在writeObject0方法中,代码非常多,看重点
private void writeObject0(Object var1, boolean var2) throws IOException { ... if (var1 instanceof Enum) { this.writeEnum((Enum)var1, var7, var2); } else { if (!(var1 instanceof Serializable)) { if (extendedDebugInfo) { throw new NotSerializableException(var6.getName() + "\n" + this.debugInfoStack.toString()); } //如果没有实现Serializable接口,会报NotSerializableException throw new NotSerializableException(var6.getName()); } //实现Serializable接口,走这里 this.writeOrdinaryObject(var1, var7, var2);//实现了Serializable } return; ... }
-
在writeOrdinaryObject(obj, desc, unshared)方法中
private void writeOrdinaryObject(Object var1, ObjectStreamClass var2, boolean var3) throws IOException { if (extendedDebugInfo) { this.debugInfoStack.push((this.depth == 1 ? "root " : "") + "object (class \"" + var1.getClass().getName() + "\", " + var1.toString() + ")"); } try { var2.checkSerialize(); this.bout.writeByte(115); this.writeClassDesc(var2, false); this.handles.assign(var3 ? null : var1); if (var2.isExternalizable() && !var2.isProxy()) { //如果对象实现了Externalizable接口,那么执行 writeExternalData((Externalizable) obj)方法 this.writeExternalData((Externalizable)var1); } else { //如果对象实现的是Serializable接口,那么执行的是writeSerialData(obj, desc) this.writeSerialData(var1, var2); } } finally { if (extendedDebugInfo) { this.debugInfoStack.pop(); } } }
-
如果对象实现的是Serializable接口,走的是writeSerialData(var1, var2)方法,最终写序列化的地方
private void writeSerialData(Object var1, ObjectStreamClass var2) throws IOException { ClassDataSlot[] var3 = var2.getClassDataLayout(); for(int var4 = 0; var4 < var3.length; ++var4) { ObjectStreamClass var5 = var3[var4].desc; //如果writeObjectMethod != null(目标类中定义了私有的writeObject方法),那么将调用目标类中的writeObject方法 if (var5.hasWriteObjectMethod()) { ObjectOutputStream.PutFieldImpl var6 = this.curPut; this.curPut = null; SerialCallbackContext var7 = this.curContext; if (extendedDebugInfo) { this.debugInfoStack.push("custom writeObject data (class \"" + var5.getName() + "\")"); } try { this.curContext = new SerialCallbackContext(var1, var5); this.bout.setBlockDataMode(true); var5.invokeWriteObject(var1, this);//定义了writeObject方法,去执行invokeWriteObject(var1, this) this.bout.setBlockDataMode(false); this.bout.writeByte(120); } finally { this.curContext.setUsed(); this.curContext = var7; if (extendedDebugInfo) { this.debugInfoStack.pop(); } } this.curPut = var6; } else { //如果如果writeObjectMethod == null, 那么将调用默认的 defaultWriteFields方法来读取目标类中的属性 this.defaultWriteFields(var1, var5); } } }
-
调用了ObjectStreamClass的hasWriteObjectMethod()来判断是否定义了私有的writeObject方法。
boolean hasWriteObjectMethod() { this.requireInitialized(); return this.writeObjectMethod != null; }
-
那writeObjectMethod变量是在哪里定义的呢?在ObjectStreamClass构造函数中。
private ObjectStreamClass(final Class<?> var1) { ... if (ObjectStreamClass.this.externalizable) { ObjectStreamClass.this.cons = ObjectStreamClass.getExternalizableConstructor(var1); } else { ObjectStreamClass.this.cons = ObjectStreamClass.getSerializableConstructor(var1); //在序列化(反序列化)的时候, ObjectOutputStream(ObjectInputStream) //会寻找目标类中的私有的writeObject(readObject)方法, //赋值给变量writeObjectMethod(readObjectMethod) ObjectStreamClass.this.writeObjectMethod = ObjectStreamClass.getPrivateMethod(var1, "writeObject", new Class[]{ObjectOutputStream.class}, Void.TYPE); ObjectStreamClass.this.readObjectMethod = ObjectStreamClass.getPrivateMethod(var1, "readObject", new Class[]{ObjectInputStream.class}, Void.TYPE); ObjectStreamClass.this.readObjectNoDataMethod = ObjectStreamClass.getPrivateMethod(var1, "readObjectNoData", (Class[])null, Void.TYPE); ObjectStreamClass.this.hasWriteObjectData = ObjectStreamClass.this.writeObjectMethod != null; } ... }
Serializable需要注意的坑
-
多引用写入
在默认情况下, 对于一个实例的多个引用,为了节省空间,只会写入一次,后面会追加几个字节代表某 个实例的引用。
-
子类实现序列化,父类不实现序列化/ 对象引用
在readObject时抛出java.io.NotSerializableException异常。
-
类的演化
反序列化目标类多一个字段,并不会报错,只是实赋成了默认值。
还有,如果写入的多一个字段,读出的少一个字段,也是不会报错的。 -
枚举类型
事实上序列化Enum对象时,并不会保存元素的值,只会保存元素的name。这样,在不依赖元素值的 前提下,ENUM对象如何更改都会保持兼容性。
Parcelable接口
Parcelable是Android为我们提供的序列化的接口,Parcelable相对于Serializable的使用相对复杂一些,但 Parcelable的效率相对Serializable也高很多,这一直是Google工程师引以为傲的,有时间的可以看一下 Parcelable和Serializable的效率对比,Parcelable vs Serializable 号称快10倍的效率。
Parcelable是Android SDK提供的,它是基于内存的,由于内存读写速度高于硬盘,因此Android中的跨进程对象的传递一般使用Parcelable。
Parcelable与Serializable的性能比较
Serializable性能分析
Serializable是Java中的序列化接口,其使用起来简单但开销较大(因为Serializable在序列化过程中使 用了反射机制,故而会产生大量的临时变量,从而导致频繁的GC),并且在读写数据过程中,它是通 过IO流的形式将数据写入到硬盘或者传输到网络上。
Parcelable性能分析
Parcelable则是以IBinder作为信息载体,在内存上开销比较小,因此在内存之间进行数据传递时,推荐使用Parcelable,而Parcelable对数据进行持久化或者网络传输时操作复杂,一般这个时候推荐使用 Serializable。
注意点:Parcelable安全漏洞
性能比较总结描述
Parcelable的性能要强于Serializable的原因:
在内存的使用中,前者在性能方面要强于后者;
后者在序列化操作的时候会产生大量的临时变量(原因是使用了反射机制),从而导致GC的频繁调用,因此在性能上会稍微逊色;
Parcelable是以Ibinder作为信息载体的。在内存上的开销比较小,因此在内存之间进行数据传递的时候,Android推荐使用Parcelable。既然是内存方面比价有优势,那么自然就要优先选择;
-
在读写数据的时候,Parcelable是在内存中直接进行读写,而Serializable是通过使用IO流的形式将数据读写入在硬盘上;
但是虽然Parcelable的性能要强于Serializable,但是仍然有特殊的情况需要使用Serializable,而不去使用Parcelable。因为Parcelable无法将数据进行持久化,因此在将数据保存在磁盘的时候,仍然需要使用后者,因为前者无法很好的将数据进行持久化(原因是在不同的Android版本当中,Parcelable可能会不同,因此数据的持久化方面仍然是使用Serializable)。
性能测试方法分析
-
通过将一个对象放到一个bundle里面,然后调用Bundle#writeToParcel(Parcel, int)方法来模拟;
传递对象给一个activity的过程,然后再把这个对象取出来。
在一个循环里面运行1000 次。
两种方法分别运行10次来减少内存整理,cpu被其他应用占用等情况的干扰。
参与测试的对象就是上面的相关代码
在多种Android软硬件环境上进行测试
两种如何选择
- 在使用内存方面,Parcelable比Serializable性能高,所以推荐使用Parcelable;
- Serializable在序列化的时候会产生大量的临时变量,从而引起频繁的GC;
- Parcelable不能使用在要将数据存储在磁盘上的情况,因为Parcelable不能很好的保证数据的持续性,在外界有变化的情况下,建议使用Serializable。
Parcelable与Serializable的区别
Parcelable | Serializable |
---|---|
直接在内存操作,效率高,性能好 | 通过IO对硬盘操作,速度较慢 |
一般不能超过1M,修改内核也只能4M | 大小不受限制 |
大量使用反射,产生内存碎片 |
SQLite与SharedPreferences
SQLite主要用于存储复杂的关系型数据,Android支持原生支持SQLite数据库相关操作 (SQLiteOpenHelper),不过由于原生API接口并不友好,所以产生了不少封装了SQLite的ORM框架。
SharedPreferences是Android平台上提供的一个轻量级存储API,一般用于存储常用的配置信息,其本质是一个键值对存储,支持常用的数据类型如boolean、float、int、long以及String的存储和读取。
相关面试题
-
①什么是 serialVersionUID?作用是什么?如果你不定义这个, 会发生什么?
②假设你有一个类,它序列化并存储在持久性中,然后修改了该类以添加新字段。如果对已序列化的对象进行反序列化,会发生什么情况?
serialVersionUID 是一个 private static final long 型 ID,当它被印在对象上时,它通常是对象的哈希码,你可以使用 serialver 这个 JDK 工具来查看序列化对象的 serialVersionUID。 SerialVerionUID 用于对象的版本控制。也可以在类文件中指定 serialVersionUID。不指定 serialVersionUID的后果是,当你添加或修改类中的任何字段时,,则已序列化类将无法恢复,因为为新类和旧序列化对象生成的 serialVersionUID 将有所不同。Java 序列化过程依赖于正确的序列化对象恢复状态的,并在序列化对象序列版本不匹配的情况下引发java.io.InvalidClassException 无效类异常。
-
序列化时,你希望某些成员不要序列化?你如何实现它?
有时候也会变着形式问,比如问什么是瞬态 trasient 变量,瞬态和静态变量会不会得到序列化等,所以,如果你不希望任何字段是对象的状态的一部分,然后声明它静态或瞬态根据你的需要,这样就不会是在 Java 序列化过程中被包含在内。
-
如果类中的一个成员未实现可序列化接口,会发生什么情况?
如果尝试序列化实现可序列化的类的对象,但该对象包含对不可序列化类的引用,则在运行时将引发不可序列化异常 NotSerializableException
-
如果类是可序列化的,但其超类不是,则反序列化后从超级类继承的实例变量的状态如何?
Java 序列化过程仅在对象层次都是可序列化结构中继续,即实现 Java 中的可序列化接口,并且从超级类继承的实例变量的值将通过调用构造函数初始化,在反序列化过程中不可序列化的超级类。
-
①是否可以自定义序列化过程,或者是否可以覆盖 Java 中的默认序列化过程?
②假设新类的超级类实现可序列化接口,如何避免新类被序列化?
对于序列化一个对象需调用 ObjectOutputStream.writeObject(saveThisObject),并用 ObjectInputStream.readObject() 读取对象,但 Java 虚拟机为你提供的还有一件事,是定义这两个方法。如果在类中定义这两种方法,则 JVM 将调用这两种方法,而不是应用默认序列化机制。 你可以在此处通过执行任何类型的预处理或后处理任务来自定义对象序列化和反序列化的行为。
-
在 Java 中的序列化和反序列化过程中使用哪些方法?
考察你是否熟悉 readObject() 的用法、writeObject()、readExternal() 和 writeExternal()。 Java 序列化由java.io.ObjectOutputStream类完成。该类是一个筛选器流,它封装在较低级别的字节流中,以处理序列化机制。要通过序列化机制存储任何对象,我们调用 ObjectOutputStream.writeObject(savethisobject),并反序列化该对象,我们称之为 ObjectInputStream.readObject()方法。调用以 writeObject() 方法在 java 中触发序列化过程。 关于 readObject() 方法,需要注意的一点很重要一点是,它用于从持久性读取字节,并从这些字节创建对象,并返回一个对象,该对象需要类型强制转换为正确的类型。
-
反序列化后的对象,需要调用构造函数重新构造吗
不会,因为是从二进制直接解析出来的。适用的是 Object 进行接收再强转,因此不是原来的那个对象。
-
序列化与持久化的关系和区别是什么?
序列化是为了进程间数据交互而设计的,持久化是为了把数据存储下来而设计的。
-
序列前的对象与序列化后的对象是什么关系?是("=="还是equal?是浅复制还是 深复制?)//枚举
是一个深拷贝, 前后对象的引用地址不同
-
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的通信原理及大小限制?
Android 中的 bundle 实现了 parcelable 的序列化接口,目的是为了在进程间进行通讯。不同的进程共享一片固定大小的内存。parcelable 利用 parcel 对象的 read/write 方法,对需要传递的数据进行内存读写,因此这一块共享内存不能过大,在利用 bundle 进行传输时,会初始化一个 BINDER_VM_SIZE 的大小 = 1 * 1024 * 1024 - 4096 * 2,即便通过 修改 Framework 的代码,bundle 内核的映射只有 4M,最大只能扩展到 4M。
-
为何Intent不能直接在组件间传递对象而要通过序列化机制?
因为 Activity 启动过程是需要与 AMS 交互,AMS 与 UI 进程是不同一个的,因此进程间需要交互数据,就必须序列化。
更详细的结识需要结合AMS。