序列化

一. 认识序列化

1. 序列化的定义

  • 序列化

    • 狭义的概念:即将对象转换为字节序列的过程
    • 广义的概念:即将数据结构或者对象转换为我们可以存储或者传输的数据格式的一个过程
  • 反序列化
    • 狭义的概念:即将字节序列转换成对象的过程
    • 广义的概念:即将生成的数据还原成数据结构或者对象的过程

2. 序列化的用途

  • 由于在系统底层,数据的传输形式是简单的字节序列形式传递的,即在底层,系统并不认识对象,只认识字节序列,而为了达到进程间通讯的目的,就需要将对象或者说数据转换为字节序列的形式,而这个过程本质上就是序列化。
  • 简单的概括:
    • 序列化:主要用于网络传输数据持久化;而一般序列化也被称为编码(Encode)
    • 反序列化:主要用于从网络磁盘上读取字节数组还原成原始对象;一般反序列化也被称为解码(Decode)
  • 展开具体的举例:
    • 永久保存对象数据(将对象数据保存在文件当中,或者是磁盘中
    • 通过序列化操作将对象数据在网络上进行传输(由于网络传输是以字节流的方式对数据进行传输的,因此序列化的目的是将对象数据转换成字节流的形式
    • 将对象数据在进程间进行传递(Activity之间传递对象数据时,需要在当前的Activity中对对象数据进行序列化操作,在另一个Activity中需要进行反序列化操作将数据取出
    • Java平台允许我们在内存中创建可复用的Java对象,但一般情况下,只有当JVM处于运行时,这些对象才可能存在,即,这些对象的生命周期不会比JVM的生命周期更长(即每个对象都在JVM中),但在现实应用中,就可能要停止JVM运行,但有要保存某些指定的对象,并在将来重新读取被保存的对象。这时将Java对象序列化就能够实现该功能(可选择存入数据库、或以文件的形式保存

3.实现序列化的方式

  • 狭义上的序列化:

    • Java中的 Serializable/ Externalizable接口
    • Android设计的 Parcelable 接口
  • 广义上的序列化:

    • JSON
    • SQLite

二. 几种常见的序列化和反序列化协议

1. XML & SOAP

  • XML 是一种常用的序列化和反序列化协议,具有跨机器,跨语言等优点,SOAP(Simple Object Access protocol) 是一种被广泛应用的,基于 XML 为序列化和反序列化协议的结构化消息传递协议

2. JSON(JavaScript Object Notation

  • JSON 起源于弱类型语言 JavaScript, 它的产生来自于一种称之为Associative array的概念,其本质是就是采用Attribute-value的方式来描述对象。实际上在 JavaScript和 PHP 等弱类型语言中,类的描述方式就是 Associative array;JSON 的这些优点,使得它快速成为最广泛使用的序列化协议之一。

3. Protobuf

  • Protobuf是谷歌提供的序列化协议,由于其内部采用不是字节全对齐的方式,所以其序列化后的数据十分的简洁、紧凑,与XML相比,其序列化后的数据量约为前者的1/31/10

三. Android中两种最常用的序列化方案

1. Serializable/Externalizable接口

  • Serializable接口是由Java提供的序列化接口,它是一个空接口:

    public interface Serializable {}
    

    Serializable接口可以看作是一种标识,用来标识当前类可以:

    1. ObjectOutPutStream序列化
    2. ObjectInputStream反序列化
  • Externalizable接口是继承了Serializable接口的,声明了两个方法:

    public interface Externalizable extends Serializable {void writeExternal(ObjectOutput var1) throws IOException;void readExternal(ObjectInput var1) throws IOException, ClassNotFoundException;
    }
    

    Externalizable被继承之后,必须实现writeExternalreadExternal方法

1) 基本使用

Serializable接口
class UserSerializable implements Serializable {//自动生成UID,File--Setting--Editor--Inspections--Java--Serialization issues--勾选Serializable class without "serialVersionUID"即可private static final long serialVersionUID = 4896305340906213530L;public static final String path = "E:\\Android_learn_project\\serializeLearn\\";public int m_numId;public String m_name;public UserSerializable(int num, String name) {m_name = name;m_numId = num;}public UserSerializable() {}public String toString() {return "name = " + m_name + ", numID = " + m_numId;}public static void main(String[] args) {UserSerializable user = new UserSerializable(1, "xxx");UserSerializable user1 = new UserSerializable();System.out.println("原始对象:" + user);try {//采用FileOutputStream,其实是将二进制数据保存到文件中了,实现了持久化ObjectOutputStream oss = new ObjectOutputStream(new FileOutputStream(path + "a.out"));oss.writeObject(user);} catch (IOException e) {e.printStackTrace();}try {//采用FileInputStream,将存储了二进制数据的文件转换为ObjectInputStream数据ObjectInputStream iss = new ObjectInputStream(new FileInputStream(path + "a.out"));user1 = (UserSerializable) iss.readObject();} catch (IOException e) {e.printStackTrace();} catch (ClassNotFoundException e) {e.printStackTrace();}System.out.println("------------------------------------------------");System.out.println("序列化后读取的对象:" + user1);}}
  • 见以上代码,serializable接口实现序列化的基本方法,其实上述代码实现的是持久化的存储,将数据存到了对应文件中;查看ObjectOutputStream类,存在两个构造函数,带参数的构造函数接收OutputStream

查看OutputStream类的继承关系,我们可以使用如下类进行数据存储

  • serialVersionUID

    • serialVersionUID参数的作用:serialVersionUID 用来表明类的不同版本间的兼容性。如果你修改了此类, 要修改此值。否则以前用老版本的类序列化的类恢复时会报错: InvalidClassException
    • serialVersionUID兼容性问题:为了在反序列化时,确保类版本的兼容性,最好在每个要序列化的类中加入 private static final long serialVersionUID 这个属性,具体数值自己定义。这样,即使某个类在与之对应的对象已经序列化出去后做了修改,该对象依然可以被正确反序列化。否则,如果不显式定义该属性,这个属性值将由JVM根据类的相关信息计算,而修改后的类的计算 结果与修改前的类的计算结果往往不同,从而造成对象的反序列化因为类版本不兼容而失败。不显式定义这个属性值的另一个坏处是,不利于程序在不同的JVM之间的移植。因为不同的编译器实现该属性值的计算策略可能不同,从而造成虽然类没有改变,但是因为JVM不同,出现因类版本不兼容而无法正确反序列化的现象出现
Externalizable接口
class UserExternalizable implements Externalizable {private static final long serialVersionUID = 967548983806058267L;public int m_numID;public String m_name;public UserExternalizable() {}public UserExternalizable(int numID, String name) {m_name = name;m_numID = numID;}public String toString() {return "numID = " + m_numID + ", name = " + m_name;}@Overridepublic void writeExternal(ObjectOutput objectOutput) throws IOException {System.out.println("enter UserExternalizable writeExternal");//必须保证写入数据的顺序和读出数据的顺序是一致的,否则会报错objectOutput.writeInt(m_numID);objectOutput.writeObject(m_name);}@Overridepublic void readExternal(ObjectInput objectInput) throws IOException, ClassNotFoundException {System.out.println("enter UserExternalizable readExternal");//必须保证写入数据的顺序和读出数据的顺序是一致的,否则会报错m_numID = objectInput.readInt();m_name = (String) objectInput.readObject();}public static void main(String[] args) throws IOException, ClassNotFoundException {UserExternalizable user = new UserExternalizable(10, "ooooooo");UserExternalizable user1 = null;byte[] userData = null;System.out.println("序列化前:" + user);//采用数组的形式进行序列化数据的存储ByteArrayOutputStream out = new ByteArrayOutputStream();ObjectOutputStream oss = new ObjectOutputStream(out);oss.writeObject(user);//将序列化后的数据存储到byte数组中userData = out.toByteArray();//通过读取byte数组进行二进制数据的获取ObjectInputStream iss = new ObjectInputStream(new ByteArrayInputStream(userData));user1 = (UserExternalizable) iss.readObject();System.out.println("----------------------------------");System.out.println("反序列化后:" + user1);}
}
  • 通过SerializableExternalizable接口的简单使用会产生一个疑问:序列化和反序列化究竟是如果使用writeObjectreadObject以及Externalizable接口中writeExternalreadExternal是怎样被调用到的?就让我们从源码解析中获得答案

2)源码分析

writeObject
  • 让我们从writeObject开始看

    1. ObjectOutputStream构造函数

      public ObjectOutputStream(OutputStream out) throws IOException {verifySubclass();bout = new BlockDataOutputStream(out);handles = new HandleTable(10, (float) 3.00);subs = new ReplaceTable(10, (float) 3.00);//调用有参构造函数创建的ObjectOutputStream类,enableOverride = falseenableOverride = false;writeStreamHeader();bout.setBlockDataMode(true);if (extendedDebugInfo) {debugInfoStack = new DebugTraceInfoStack();} else {debugInfoStack = null;}
      }
      
    2. writeObject方法

      public final void writeObject(Object obj) throws IOException {//构造函数中,enableOverride = falseif (enableOverride) {writeObjectOverride(obj);return;}try {//正常都会调用writeObject0方法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;}
      }
      
    3. writeObject0方法

      /*** Underlying writeObject/writeUnshared implementation.*/
      private void writeObject0(Object obj, boolean unshared)throws IOException
      {boolean oldMode = bout.setBlockDataMode(false);depth++;try {// handle previously written and non-replaceable objectsint h;if ((obj = subs.lookup(obj)) == null) {writeNull();return;} else if (!unshared && (h = handles.lookup(obj)) != -1) {writeHandle(h);return;// BEGIN Android-changed:  Make Class and ObjectStreamClass replaceable./*} else if (obj instanceof Class) {writeClass((Class) obj, unshared);return;} else if (obj instanceof ObjectStreamClass) {writeClassDesc((ObjectStreamClass) obj, unshared);return;*/// END Android-changed:  Make Class and ObjectStreamClass replaceable.}// check for replacement objectObject orig = obj;Class<?> cl = obj.getClass();ObjectStreamClass desc;// BEGIN Android-changed: Make only one call to writeReplace./*for (;;) {// REMIND: skip this check for strings/arrays?Class<?> repCl;desc = ObjectStreamClass.lookup(cl, true);if (!desc.hasWriteReplaceMethod() ||(obj = desc.invokeWriteReplace(obj)) == null ||(repCl = obj.getClass()) == cl){break;}cl = repCl;desc = ObjectStreamClass.lookup(cl, true);break;}*/// Do only one replace passClass repCl;desc = ObjectStreamClass.lookup(cl, true);//在这里会判断是否存在ReplaceMethodif (desc.hasWriteReplaceMethod() &&//如果存在,则会通过反射进行调用,所以writeReplace是先于writeObject被调用的(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.//enableReplace是通过enableReplaceObject()方法去赋值的      if (enableReplace) {Object rep = replaceObject(obj);if (rep != obj && rep != null) {cl = rep.getClass();desc = ObjectStreamClass.lookup(cl, true);}obj = rep;}//不调用replaceObject,不会走到这里面// 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;
      // BEGIN Android-changed:  Make Class and ObjectStreamClass replaceable.
      /*} else if (obj instanceof Class) {writeClass((Class) obj, unshared);return;} else if (obj instanceof ObjectStreamClass) {writeClassDesc((ObjectStreamClass) obj, unshared);return;
      */
      // END Android-changed:  Make Class and ObjectStreamClass replaceable.}}// 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) {//正常来说,继承Serializable调用到writeOrdinaryObjectwriteOrdinaryObject(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);}
      }
      
    4. writeOrdinaryObject方法

      /*** Writes representation of a "ordinary" (i.e., not a String, Class,* ObjectStreamClass, array, or enum constant) serializable object to the* stream.*/
      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();//写入的标志位是TC_OBJECT,读的时候对应的值就是这个bout.writeByte(TC_OBJECT);writeClassDesc(desc, false);handles.assign(unshared ? null : obj);//在这里做了判断,如果当前的类继承的是Externalizable接口if (desc.isExternalizable() && !desc.isProxy()) {//那么就会去调用writeExternalData()方法writeExternalData((Externalizable) obj);} else {//否则则调用writeSerialData()方法writeSerialData(obj, desc);}} finally {if (extendedDebugInfo) {debugInfoStack.pop();}}
      }
      
    5. writeExternalData/ writeSerialData 方法

      /*** Writes externalizable data of given object by invoking its* writeExternal() method.*/
      private void writeExternalData(Externalizable obj) throws IOException {PutFieldImpl oldPut = curPut;curPut = null;if (extendedDebugInfo) {debugInfoStack.push("writeExternal data");}SerialCallbackContext oldContext = curContext;try {curContext = null;if (protocol == PROTOCOL_VERSION_1) {obj.writeExternal(this);} else {bout.setBlockDataMode(true);obj.writeExternal(this);bout.setBlockDataMode(false);bout.writeByte(TC_ENDBLOCKDATA);}} finally {curContext = oldContext;if (extendedDebugInfo) {debugInfoStack.pop();}}curPut = oldPut;
      }/*** Writes instance data for each serializable class of given object, from* superclass to subclass.*/
      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;//判断是否有实现WriteObject()方法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);//如果有重写,则通过反射去调用我们自己写的WriteObject()方法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. 如果在继承类中声明实现了WriteObject()方法,最终调用的就是自己声明的函数,并非是方法的重写,而是使用反射的方式实现的
        2. 如果我们声明实现了writeReplace()方法,那么这个方法会在WriteObject()方法之前调用
readObject
  • 再看一下readObject()方法

    1. ObjectInputStream构造函数

      public c(InputStream in) throws IOException {verifySubclass();bin = new BlockDataInputStream(in);handles = new HandleTable(10);vlist = new ValidationList();// Android-removed: ObjectInputFilter logic, to be reconsidered. http://b/110252929// serialFilter = ObjectInputFilter.Config.getSerialFilter();enableOverride = false;readStreamHeader();bin.setBlockDataMode(true);
      }
      
    2. readObject()方法

      public final Object readObject()throws IOException, ClassNotFoundException
      {if (enableOverride) {return readObjectOverride();}// if nested read, passHandle contains handle of enclosing objectint outerHandle = passHandle;try {Object obj = readObject0(false);handles.markDependency(outerHandle, passHandle);ClassNotFoundException ex = handles.lookupException(passHandle);if (ex != null) {throw ex;}if (depth == 0) {vlist.doCallbacks();}return obj;} finally {passHandle = outerHandle;if (closed && depth == 0) {clear();}}
      }
      
    3. readObject0方法

      /*** Underlying readObject implementation.*/
      private Object readObject0(boolean unshared) throws IOException {boolean oldMode = bin.getBlockDataMode();if (oldMode) {int remain = bin.currentBlockRemaining();if (remain > 0) {throw new OptionalDataException(remain);} else if (defaultDataEnd) {/** Fix for 4360508: stream is currently at the end of a field* value block written via default serialization; since there* is no terminating TC_ENDBLOCKDATA tag, simulate* end-of-custom-data behavior explicitly.*/throw new OptionalDataException(true);}bin.setBlockDataMode(false);}byte tc;while ((tc = bin.peekByte()) == TC_RESET) {bin.readByte();handleReset();}depth++;// Android-removed: ObjectInputFilter logic, to be reconsidered. http://b/110252929// totalObjectRefs++;try {switch (tc) {case TC_NULL:return readNull();case TC_REFERENCE:return readHandle(unshared);case TC_CLASS:return readClass(unshared);case TC_CLASSDESC:case TC_PROXYCLASSDESC:return readClassDesc(unshared);case TC_STRING:case TC_LONGSTRING:return checkResolve(readString(unshared));case TC_ARRAY:return checkResolve(readArray(unshared));case TC_ENUM:return checkResolve(readEnum(unshared));//写入的值对应读取的值case TC_OBJECT:return checkResolve(readOrdinaryObject(unshared));case TC_EXCEPTION:IOException ex = readFatalException();throw new WriteAbortedException("writing aborted", ex);case TC_BLOCKDATA:case TC_BLOCKDATALONG:if (oldMode) {bin.setBlockDataMode(true);bin.peek();             // force header readthrow new OptionalDataException(bin.currentBlockRemaining());} else {throw new StreamCorruptedException("unexpected block data");}case TC_ENDBLOCKDATA:if (oldMode) {throw new OptionalDataException(true);} else {throw new StreamCorruptedException("unexpected end of block data");}default:throw new StreamCorruptedException(String.format("invalid type code: %02X", tc));}} finally {depth--;bin.setBlockDataMode(oldMode);}
      }
      
    4. readOrdinaryObject方法

      private Object readOrdinaryObject(boolean unshared)throws IOException
      {if (bin.readByte() != TC_OBJECT) {throw new InternalError();}ObjectStreamClass desc = readClassDesc(false);desc.checkDeserialize();Class<?> cl = desc.forClass();if (cl == String.class || cl == Class.class|| cl == ObjectStreamClass.class) {throw new InvalidClassException("invalid class descriptor");}Object obj;try {//此处会判断是否完成初始化,如果完成,则会调用newInstance去构建对应的类,而这个newInstance使用的是无参的构造函数obj = desc.isInstantiable() ? desc.newInstance() : null;} catch (Exception ex) {throw (IOException) new InvalidClassException(desc.forClass().getName(),"unable to create instance").initCause(ex);}passHandle = handles.assign(unshared ? unsharedMarker : obj);ClassNotFoundException resolveEx = desc.getResolveException();if (resolveEx != null) {handles.markException(passHandle, resolveEx);}//同样判断是不是Externalizableif (desc.isExternalizable()) {readExternalData((Externalizable) obj, desc);} else {readSerialData(obj, desc);}handles.finish(passHandle);if (obj != null &&handles.lookupException(passHandle) == null &&//判断是否存在ResolveMethod()函数的声明定义,这个是在readObject之后desc.hasReadResolveMethod()){Object rep = desc.invokeReadResolve(obj);if (unshared && rep.getClass().isArray()) {rep = cloneArray(rep);}if (rep != obj) {handles.setObject(passHandle, obj = rep);}}return obj;
      }
      

3)分析、总结

Java的序列化步骤
  • 序列化算法一般会按步骤做如下事情:

    1. 将对象实例相关的类元数据输出
    2. 递归地输出类的超类描述直到不再有超类
    3. 类元数据完了以后,开始从最顶层的超类开始输出对象实例的实际数据值
    4. 从上至下递归输出实例的数据
  • 所以由此,我们就引出了两个需要注意的点:

    1. 由于序列化是将类数据按照一定的规则转换成字节序列进行输出,那么我们在反序列化的时候同样要按照对应的规则进行获取,即:重写writeObject()/readObject()或者writeExternal()/readExternal()必须保证写入数据和读取数据的顺序是一致的!

    2. 多引用问题,该问题通过一个例子说明:

      class UserExternalizable implements Externalizable {private static final long serialVersionUID = 967548983806058267L;public int m_numID;public String m_name;public UserExternalizable() {}public UserExternalizable(int numID, String name) {m_name = name;m_numID = numID;}public void setNumID(int numID) {m_numID = numID;}public String toString() {return "numID = " + m_numID + ", name = " + m_name;}@Overridepublic void writeExternal(ObjectOutput objectOutput) throws IOException {System.out.println("enter UserExternalizable writeExternal");//必须保证写入数据的顺序和读出数据的顺序是一致的,否则会报错objectOutput.writeInt(m_numID);objectOutput.writeObject(m_name);}@Overridepublic void readExternal(ObjectInput objectInput) throws IOException, ClassNotFoundException {System.out.println("enter UserExternalizable readExternal");//必须保证写入数据的顺序和读出数据的顺序是一致的,否则会报错m_numID = objectInput.readInt();m_name = (String) objectInput.readObject();}public static void main(String[] args) throws IOException, ClassNotFoundException {UserExternalizable user = new UserExternalizable(10, "ooooooo");UserExternalizable user1 = null;UserExternalizable user2 = null;byte[] userData = null;System.out.println("序列化前:" + user);//采用数组的形式进行序列化数据的存储ByteArrayOutputStream out = new ByteArrayOutputStream();ObjectOutputStream oss = new ObjectOutputStream(out);oss.writeObject(user);//改变类中的数据,再序列化一次user.setNumID(20);oss.writeObject(user);//将序列化后的数据存储到byte数组中userData = out.toByteArray();//通过读取byte数组进行二进制数据的获取ObjectInputStream iss = new ObjectInputStream(new ByteArrayInputStream(userData));user1 = (UserExternalizable) iss.readObject();user2 = (UserExternalizable) iss.readObject();System.out.println("----------------------------------");//发现结果并没有被改变,这是因为在默认的情况下,对于同一个实例的多个引用,为了节省空间,他只会被写入一次System.out.println("反序列化后:" + user1);System.out.println("反序列化后:" + user2);}
      }
      

      那么对于这种问题该如何解决呢?有两种方式,见代码

      public static void main(String[] args) throws IOException, ClassNotFoundException {UserExternalizable user = new UserExternalizable(10, "ooooooo");UserExternalizable user1 = null;UserExternalizable user2 = null;byte[] userData = null;System.out.println("序列化前:" + user);//采用数组的形式进行序列化数据的存储ByteArrayOutputStream out = new ByteArrayOutputStream();ObjectOutputStream oss = new ObjectOutputStream(out);oss.writeObject(user);//改变类中的数据,再序列化一次user.setNumID(20);/*1. 在写入之前reset一下*/oss.reset();oss.writeObject(user);/*2. 采用非共享的方式,进行写入*/oss.writeUnshared(user);userData = out.toByteArray();//通过读取byte数组进行二进制数据的获取ObjectInputStream iss = new ObjectInputStream(new ByteArrayInputStream(userData));user1 = (UserExternalizable) iss.readObject();user2 = (UserExternalizable) iss.readObject();System.out.println("----------------------------------");System.out.println("反序列化后:" + user1);System.out.println("反序列化后:" + user2);
      }
      
子类实现序列化,父类不实现序列化的场景
  • 如果执行序列化的类,它的父类不支持序列化且父类未声明无参的构造函数,那么就会抛出警告,那么为什么只要声明了无参构造就不会有警告呢,还得从源码中查看:

    我们知道反序列化是调用readObject()实现的,而在一路调用的过程中,在readOrdinaryObject()方法中

    private Object readOrdinaryObject(boolean unshared)throws IOException
    {.........Object obj;try {//此处会判断是否完成初始化,如果完成,则会调用newInstance去构建对应的类,而这个newInstance使用的是无参的构造函数obj = desc.isInstantiable() ? desc.newInstance() : null;} catch (Exception ex) {........}Object newInstance()throws InstantiationException, InvocationTargetException,UnsupportedOperationException
    {requireInitialized();if (cons != null) {try {//调用的是cons的newInstance()//接下来找一下cons是在哪里获取的return cons.newInstance();} catch (IllegalAccessException ex) {// should not occur, as access checks have been suppressedthrow new InternalError(ex);}} else {throw new UnsupportedOperationException();}
    }//初始化时,获取了cons构造函数
    private ObjectStreamClass(final Class<?> cl) {.........                          if (externalizable) {//如果是Externalizable接口则调用这个cons = getExternalizableConstructor(cl);} else {//如果是Serializable接口则调用这个cons = getSerializableConstructor(cl);writeObjectMethod = getPrivateMethod(cl, "writeObject",new Class<?>[] { ObjectOutputStream.class },Void.TYPE);readObjectMethod = getPrivateMethod(cl, "readObject",new Class<?>[] { ObjectInputStream.class },Void.TYPE);.....
    }//如果是继承了Externalizable的类,调用这个方法获取cons构造函数
    private static Constructor<?> getExternalizableConstructor(Class<?> cl) {try {//此处是反射获取无参构造函数Constructor<?> cons = cl.getDeclaredConstructor((Class<?>[]) null);cons.setAccessible(true);return ((cons.getModifiers() & Modifier.PUBLIC) != 0) ?cons : null;} catch (NoSuchMethodException ex) {return null;}
    }//如果是继承了Serializable接口的,则调用这个方法进行构造函数cons的获取
    private static Constructor<?> getSerializableConstructor(Class<?> cl) {Class<?> initCl = cl;//这里会循环获取超类,判断是否继承了Serializable接口,如果继承了则直接就返回nullwhile (Serializable.class.isAssignableFrom(initCl)) {if ((initCl = initCl.getSuperclass()) == null) {return null;}}try {//走到此处代表有超类未实现Serializable接口,那么则反射获取无参构造函数去操作Constructor<?> cons = initCl.getDeclaredConstructor((Class<?>[]) null);int mods = cons.getModifiers();if ((mods & Modifier.PRIVATE) != 0 ||((mods & (Modifier.PUBLIC | Modifier.PROTECTED)) == 0 &&!packageEquals(cl, initCl))){return null;}// BEGIN Android-changed: Serialization constructor obtained differently// cons = reflFactory.newConstructorForSerialization(cl, cons);if (cons.getDeclaringClass() != cl) {cons = cons.serializationCopy(cons.getDeclaringClass(), cl);}// END Android-changed: Serialization constructor obtained differentlycons.setAccessible(true);return cons;} catch (NoSuchMethodException ex) {return null;}
    }
    
父类继承序列化,子类不想让它具备序列化功能
  • 由类的继承关系可知,父类继承序列化接口,子类必然可以进行序列化,那么如何让其无法进行序列化呢?有两种方式:

    1. 将子类中所有的成员变量都用关键字:transient进行声明,这样序列化的时候就不会保存这些变量
    2. 在子类中实现writeObject()readObject()方法,并在其中抛异常或者返回空,具体原因见源码分析。
类的演化问题
  • 如果序列化的类是枚举对象时,那么序列化后并不会保存元素的值,只会保存元素的name;这样即便我们改变了原来的枚举,且不再次存储一遍,再次读取的时候,我们反序列化后读取的数值也会是改变后的数值。

    enum Num1{ONE, TWO, THREE;public void printValues(){System.out.println("ONE: "+ ONE.ordinal() + ", TWO: " + TWO.ordinal() + ", THREE: " + THREE.ordinal());}
    }/*** Java的序列化机制针对枚举类型是特殊处理的。简单来讲,在序列化枚举类型时,只会存储枚举类的引用和枚举常量的名称。随后的反序列化的过程中,* 这些信息被用来在运行时环境中查找存在的枚举类型对象。*/
    public class EnumSerializableTest {public static void main(String[] args) throws Exception {File file = new File("p.out");ObjectOutputStream oss = new ObjectOutputStream(new FileOutputStream(file));oss.writeObject(Num1.ONE);oss.close();Num1.THREE.printValues();System.out.println("hashCode: " + Num1.ONE.hashCode());System.out.println("反序列化后");ObjectInputStream iss = new ObjectInputStream(new FileInputStream(file));Num1 s1 = (Num1) iss.readObject();s1.printValues();System.out.println("hashCode: " + s1.hashCode());System.out.println("== " + (Num1.ONE == s1));}}/* 第一次的结果
    *  ONE: 0, TWO: 1, THREE: 2
    *  hashCode: 705927765
    *  反序列化后
    *  ONE: 0, TWO: 1, THREE: 2
    *  hashCode: 705927765
    *  == true
    *///改动一下代码
    enum Num1{//调换一下顺序TWO, ONE, THREE;public void printValues(){System.out.println("ONE: "+ ONE.ordinal() + ", TWO: " + TWO.ordinal() + ", THREE: " + THREE.ordinal());}
    }public class EnumSerializableTest {public static void main(String[] args) throws Exception {File file = new File("p.out");//去除保存的步骤//ObjectOutputStream oss = new ObjectOutputStream(new FileOutputStream(file));//oss.writeObject(Num1.ONE);//oss.close();Num1.THREE.printValues();System.out.println("hashCode: " + Num1.ONE.hashCode());System.out.println("反序列化后");ObjectInputStream iss = new ObjectInputStream(new FileInputStream(file));Num1 s1 = (Num1) iss.readObject();s1.printValues();System.out.println("hashCode: " + s1.hashCode());System.out.println("== " + (Num1.ONE == s1));}}/* 第二次的结果* ONE: 1, TWO: 0, THREE: 2* hashCode: 705927765* 反序列化后* ONE: 1, TWO: 0, THREE: 2* hashCode: 705927765* == true*/
    
Serializable中方法的执行顺序
  • writeReplace 先于writeObjectreadResolve后于readObject;原因见源码解析
序列化带来的单例模式失效问题
  • 经过上面的源码分析,我们可以获知,在序列化的过程中,会进行反射调用无参构造,而这就会破坏单例的唯一性!规避方式可以实现readResolve(),在其中返回单例

2. Parcelable接口

  • Parcelable是Android为我们提供的序列化的接口,Parcelable相对于Serializable的使用相对复杂一些,但Parcelable的效率相对Serializable也高很多;ParcelableAndroid SDK提供的,它是基于内存的,由于内存读写速度高于硬盘,因此Android中的跨进程对象的传递一般使用Parcelable

1)基本使用

  • Parcelable的使用比起 Serializable复杂一些,但是SDK有例子可以参考:

    /*** Interface for classes whose instances can be written to* and restored from a {@link Parcel}.  Classes implementing the Parcelable* interface must also have a non-null static field called <code>CREATOR</code>* of a type that implements the {@link Parcelable.Creator} interface.* * <p>A typical implementation of Parcelable is:</p>**/<pre>public class MyParcelable implements Parcelable {private int mData;public int describeContents() {return 0;}public void writeToParcel(Parcel out, int flags) {out.writeInt(mData);}public static final Parcelable.Creator&lt;MyParcelable&gt; CREATOR= new Parcelable.Creator&lt;MyParcelable&gt;() {public MyParcelable createFromParcel(Parcel in) {return new MyParcelable(in);}public MyParcelable[] newArray(int size) {return new MyParcelable[size];}};private MyParcelable(Parcel in) {mData = in.readInt();}}</pre>
    
    1. public int describeContents()

      • 返回的是内容的描述信息,只针对一些特殊的需要描述信息的对象,需要返回1,其他情况返回0就可以
    2. public void writeToParcel(Parcel out, int flags)
      • 我们通过writeToParcel方法实现序列化,writeToParcel返回了Parcel,所以我们可以直接调用Parcel中的write方法,基本的write方法都有,对象和集合比较特殊,基本的数据类型除了boolean其他都有,Boolean可以使用int或byte存储
    3. CREATOR变量
      • CREATOR变量是用来反序列化使用的,内部类将parcel对象传出来,使得我们可以通过parcel对象读取数据

    通过源码中的介绍 可以知道,Parcelable接口的实现类是可以通过Parcel写入和恢复数据的,并且必须要有一个非空的静态变量 CREATOR,而且还给了一个例子,这样我们写起来就比较简单了,但是简单的使用并不是我们的最终目的,通过查看Android源码中Parcelable可以看出,Parcelable实现过程主要分为序列化,反序列化,描述三个过程,下面分别介绍下这三个过程。

Parcel的简介
  • 在介绍之前我们需要先了解Parcel是什么?Parcel翻译过来是打包的意思,其实就是包装了我们需要传输的数据,然后在Binder中传输,也就是用于跨进程传输数据;简单来说,Parcel提供了一套机制,可以将序列化之后的数据写入到一个共享内存中,其他进程通过Parcel可以从这块共享内存中读出字节流,并反序列化成对象,下图是这个过程的模型。

  • Parcel可以包含原始数据类型(用各种对应的方法写入,比如writeInt(),writeFloat()等),可以包含Parcelable对象,它还包含了一个活动的IBinder对象的引用,这个引用导致另一端接收到一个指向这个IBinder的代理IBinder。Parcelable通过Parcel实现了read和write的方法,从而实现序列化和反序列化

2)代码示例

  • 我们展示在第一个activity中通过按键去启动第二个activity,同时传递我们自己定义的类结构,然后在第二个activity中输出

    /*** 构建一个parcelable数据传输类*/
    class ParcelableUtil implements Parcelable {public int m_num;public String m_name;public ParcelableUtil(int num, String name) {m_num = num;m_name = name;}//内容描述信息,因为没有需要描述信息的对象,返回0即可@Overridepublic int describeContents() {return 0;}//通过writeToParcel将数据写入parcel,实现序列化@Overridepublic void writeToParcel(Parcel dest, int flags) {//和Serializable一样,必须保证数据的写入和读出顺序一致!!dest.writeInt(m_num);dest.writeString(m_name);}//将数据从parcel中读出,实现反序列化protected ParcelableUtil(Parcel in) {//和Serializable一样,必须保证数据的写入和读出顺序一致!!m_num = in.readInt();m_name = in.readString();}//通过CREATOR变量,将parcel对象传给外部类,提供反序列化数据来源public static final Creator<ParcelableUtil> CREATOR = new Creator<ParcelableUtil>() {@Overridepublic ParcelableUtil createFromParcel(Parcel in) {//内部构建parelableUtil类,并将parcel传出去return new ParcelableUtil(in);}@Overridepublic ParcelableUtil[] newArray(int size) {return new ParcelableUtil[size];}};@NonNull@Overridepublic String toString() {return "name: " + m_name + ", num = " + m_num;}}/*** First Acivity,用来将数据序列化传输给Second Activity*/
    public class MainActivity extends AppCompatActivity {@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_main);Button button = findViewById(R.id.button_1);button.setOnClickListener(new View.OnClickListener() {@Overridepublic void onClick(View v) {ParcelableUtil parcelableUtil = new ParcelableUtil(10, "xxxxxxxxx");Intent intent = new Intent(MainActivity.this, SecondActivity.class);//传输时,通过putExtra将数据传入即可intent.putExtra("KEY", parcelableUtil);System.out.println("first parcelableUtil = " + parcelableUtil);startActivity(intent);}});}
    }/*** Second Activity 将从First Activity传输过来数据反序列化解析出来*/
    public class SecondActivity extends AppCompatActivity {@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_second);//从Intent中将数据读取出来
    //        ParcelableUtil parcelableUtil = getIntent().getExtras().getParcelable("KEY");ParcelableUtil parcelableUtil1 = getIntent().getParcelableExtra("KEY");System.out.println("second parcelableUtil = " + parcelableUtil1);}
    }
    

3. Parcelable与Serializable的性能比较

  1. Serializable性能分析

    • Serializable是Java中的序列化接口,其使用起来简单但开销较大(因为Serializable在序列化过程中使用了反射机制,故而会产生大量的临时变量,从而导致频繁的GC),并且在读写数据过程中,它是通过IO流的形式将数据写入到硬盘或者传输到网络上。
  2. Parcelable性能分析

    • Parcelable则是以IBinder作为信息载体,在内存上开销比较小,因此在内存之间进行数据传递时,推荐使用Parcelable,而Parcelable对数据进行持久化或者网络传输时操作复杂,一般这个时候推荐使用Serializable。
  3. 性能比较总结

    1. 在内存的使用中,Parcelable在性能方面要强于Serializable

    2. Serializable在序列化操作的时候会产生大量的临时变量(原因是使用了反射机制),从而导致GC的频繁调用,因此在性能上会稍微逊色

    3. Parcelable是以IBinder作为信息载体的.在内存上的开销比较小,因此在内存之间进行数据传递的时候,Android推荐使用Parcelable

    4. 在读写数据的时候,Parcelable是在内存中直接进行读写,而Serializable是通过使用IO流的形式将数据读写入在硬盘上。

    5. 虽然Parcelable的性能要强于Serializable,但是仍然有特殊的情况需要使用Serializable,而不去使用Parcelable,因为Parcelable无法将数据进行持久化,因此在将数据保存在磁盘的时候,仍然需要使用后者,因为前者无法很好的将数据进行持久化.(原因是在不同的Android版本当中,Parcelable可能会不同,因此数据的持久化方面仍然是使用Serializable)

Android中的序列化相关推荐

  1. android 序列化存储对象,android中对象序列化存储

    项目中要存储一些数据为了提高不必要的网络请求,提高效率,用到数据持久化的知识点,针对这个问题,解决办法其实有很多,以前在项目中是服务获取到webservice的xml,然后直接将xml保存在本地,之后 ...

  2. android 序列化 xml serializable,关于Android中的序列化Serializable和Parcelable的学习

    简单地说,"序列化"就是将运行时的对象状态转换成二进制,然后保存到流,内存或者通过网络传输给其他端. 两者最大的区别在于 存储媒介的不同,Serializable使用 I/O 读写 ...

  3. android序列化异常,关于序列化:错误:Android中的序列化和反序列化

    每次单击按钮时,我都试图保存数据并将其存储在历史记录列表中. 尝试序列化和反序列化数据时出现错误.我不知道我在做什么错. 我能够添加到历史记录列表中,但是当我返回上一个活动并返回时,数据将不持久.在这 ...

  4. Android中的Serializable和Parcelable序列化

    Serializable和Parcelable接口都可以完成对象的序列化过程,在Android中当我们需要通过Intent和Binder传输数据时,我们要传输的对象就需要使用Serializable和 ...

  5. Android中Parcelable接口用法

    --  通过writeToParcel将你的对象映射成Parcel对象,再通过createFromParcel将Parcel对象映射成你的对象.也可以将Parcel看成是一个流,通过writeToPa ...

  6. Android中的IPC机制

    Android IPC简介 IPC是Inter-Process Communication的缩写,含义就是进程间通信或者跨进程通信,是指两个进程之间进行数据交换的过程.那么什么是进程,什么是线程,进程 ...

  7. Android中Parcelable与Serializable接口用法

    转自: Android中Parcelable接口用法 1. Parcelable接口 Interface for classes whose instances can be written to a ...

  8. Android中Parcelabel对象的使用和理解

    1. Parcelable接口 Interface for classes whose instances can be written to and restored from a Parcel. ...

  9. Android中Parcelable接口用法 和 Serializable实现与Parcelabel实现的区别

    1. Parcelable接口 Interface for classes whose instances can be written to and restored from a Parcel. ...

最新文章

  1. 盘点 Github 上的高仿 app 项目
  2. CentOS 安装NTFS-3G,让系统支持NTFS分区的方法
  3. mysql支持的并发数_重学MySQL系列(五):谈谈对MySQL的存储引擎的理解
  4. 找到一个或多个多重定义的符号
  5. UVA11388GCD LCM
  6. java 短语_从Java中的文本文件中提取短语
  7. 【算法竞赛学习】数字中国创新大赛智慧海洋建设-Task3特征工程
  8. Ubuntu20.04更换为国内源
  9. ROADS POJ - 1724(最短路+邻接表+dfs)
  10. linux添加video驱动,linux下video驱动源码位置
  11. 最新口绑查询HTML源码
  12. 图片抓取_小小爬虫批量抓取微信推文里的图片
  13. 蚂蚁金服数据分析平台演进及数据分析方法应用.pdf(附PPT下载链接)
  14. Gerrit搭建与代码下载
  15. 2345等浏览器主页劫持的解决办法
  16. 我的世界java边境之地_《我的世界》:手机版的边境之地你绝对没见过!那里方块只有空壳?...
  17. 打太极不协调的二三事
  18. mysql数据生成词云图,7个好用的在线词云生成工具
  19. OpenCV:轮廓检测、查找轮廓、绘制轮廓、凸包、图像的矩特征
  20. mysql基础教程下载_MySQL基础教程

热门文章

  1. Python浮点数的比较
  2. D19.1.0 对gluLookAt,gluPerspective和glOrtho的理解
  3. 20191004在LINUX下如何将tar压缩文件解压到指定的目录下
  4. Java反射机制基本概念与相关Class类对反射机制的实现
  5. python读写文件练习
  6. React性能优化(完整版)
  7. Tomcat详解(全网最全,最好的)
  8. C++:值传递和引用传递
  9. Oracle系列十二:视图、记录、同义词、序列
  10. CPU用户态和内核态