天天看點

Android中的序列化序列化

序列化

一. 認識序列化

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/3到1/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

    被繼承之後,必須實作

    writeExternal

    readExternal

    方法

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

Android中的序列化序列化

檢視

OutputStream

類的繼承關系,我們可以使用如下類進行資料存儲

Android中的序列化序列化
  • 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;
    }

    @Override
    public void writeExternal(ObjectOutput objectOutput) throws IOException {
        System.out.println("enter UserExternalizable writeExternal");
        //必須保證寫入資料的順序和讀出資料的順序是一緻的,否則會報錯
        objectOutput.writeInt(m_numID);
        objectOutput.writeObject(m_name);
    }

    @Override
    public 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);
    }
}
           
  • 通過

    Serializable

    Externalizable

    接口的簡單使用會産生一個疑問:序列化和反序列化究竟是如果使用

    writeObject

    readObject

    以及

    Externalizable

    接口中

    writeExternal

    readExternal

    是怎樣被調用到的?就讓我們從源碼解析中獲得答案

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 = false
          enableOverride = false;
          writeStreamHeader();
          bout.setBlockDataMode(true);
          if (extendedDebugInfo) {
              debugInfoStack = new DebugTraceInfoStack();
          } else {
              debugInfoStack = null;
          }
      }
                 
    2. writeObject

      方法
      public final void writeObject(Object obj) throws IOException {
          //構造函數中,enableOverride = false
          if (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 objects
              int 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 object
              Object 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 pass
                        
              Class repCl;
              desc = ObjectStreamClass.lookup(cl, true);
              //在這裡會判斷是否存在ReplaceMethod
              if (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 time
              if (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調用到writeOrdinaryObject
                  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);
          }
      }
                 
    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 object
          int 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 read
                          throw 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);
          }
                    
          //同樣判斷是不是Externalizable
          if (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;
          }
      
          @Override
          public void writeExternal(ObjectOutput objectOutput) throws IOException {
              System.out.println("enter UserExternalizable writeExternal");
              //必須保證寫入資料的順序和讀出資料的順序是一緻的,否則會報錯
              objectOutput.writeInt(m_numID);
              objectOutput.writeObject(m_name);
          }
      
          @Override
          public 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 suppressed
                throw 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接口,如果繼承了則直接就傳回null
        while (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 differently
            cons.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

    先于

    writeObject

    readResolve

    後于

    readObject

    ;原因見源碼解析
序列化帶來的單例模式失效問題
  • 經過上面的源碼分析,我們可以獲知,在序列化的過程中,會進行反射調用無參構造,而這就會破壞單例的唯一性!規避方式可以實作

    readResolve()

    ,在其中傳回單例

2.

Parcelable

接口

  • Parcelable

    是Android為我們提供的序列化的接口,

    Parcelable

    相對于

    Serializable

    的使用相對複雜一些,但

    Parcelable

    的效率相對

    Serializable

    也高很多;

    Parcelable

    是Android 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可以從這塊共享記憶體中讀出位元組流,并反序列化成對象,下圖是這個過程的模型。
Android中的序列化序列化
  • 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即可
        @Override
        public int describeContents() {
            return 0;
        }
    
        //通過writeToParcel将資料寫入parcel,實作序列化
        @Override
        public 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>() {
            @Override
            public ParcelableUtil createFromParcel(Parcel in) {
                //内部建構parelableUtil類,并将parcel傳出去
                return new ParcelableUtil(in);
            }
    
            @Override
            public ParcelableUtil[] newArray(int size) {
                return new ParcelableUtil[size];
            }
        };
    
        @NonNull
        @Override
        public String toString() {
            return "name: " + m_name + ", num = " + m_num;
        }
    
    }
    
    /**
     * First Acivity,用來将資料序列化傳輸給Second Activity
     */
    public class MainActivity extends AppCompatActivity {
    
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_main);
    
            Button button = findViewById(R.id.button_1);
            button.setOnClickListener(new View.OnClickListener() {
                @Override
                public 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 {
    
        @Override
        protected 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)