Android 對象序列化之你不知道的 Serializable

閃存
Android 存儲優化系列專題
  • SharedPreferences 系列

Android 之不要濫用 SharedPreferences
Android 之不要濫用 SharedPreferences(2)— 數據丟失

  • ContentProvider 系列(待更)

《Android 存儲選項之 ContentProvider 啟動存在的暗坑》
《Android 存儲選項之 ContentProvider 深入分析》

  • 對象序列化系列

Android 對象序列化之你不知道的 Serializable
Android 對象序列化之 Parcelable 取代 Serializable ?
Android 對象序列化之追求性能完美的 Serial

  • 數據序列化系列(待更)

《Android 數據序列化之 JSON》
《Android 數據序列化之 Protocol Buffer 使用》
《Android 數據序列化之 Protocol Buffer 源碼分析》

  • SQLite 存儲系列

Android 存儲選項之 SQLiteDatabase 創建過程源碼分析
Android 存儲選項之 SQLiteDatabase 源碼分析
數據庫連接池 SQLiteConnectionPool 源碼分析
SQLiteDatabase 啟用事務源碼分析
SQLite 數據庫 WAL 模式工作原理簡介
SQLite 數據庫鎖機制與事務簡介
SQLite 數據庫優化那些事兒


前言

對象序列化系列,主要內容包括:Java 原生提供的 Serializable ,更加適合 Android 平臺輕量且高效的 Parcelable,以及追求性能完美的 Serial。該系列內容主要結合源碼的角度分析它們各自的優缺點以及合適的使用場景。

今天先來聊聊 Java 原生提供的 Serializable 對象序列化機制,本文不是與大家一起探討關于 Serializable 的具體使用,相信關于 Serializable 大家肯定也不會感到陌生,Serializable 是 Java 原生的序列化機制,在 Android 中也有被廣泛使用。我們可以通過 Serializable 將對象持久化存儲,也可以通過 Bundle 傳遞 Serializable 的序列化數據。

Serializable 簡單實現背后包含著復雜的計算邏輯,本文也將結合源碼角度深入分析 Serializable 背后實現原理。

序列化/反序列化
  • 什么是對象序列化

大家是否有思考過,什么是序列化?應用程序中的對象存儲在內存中,如果此時我們想把對象存儲下來或者在網絡上傳輸,這個時候就需要用到對象的序列化和反序列化。

對象序列化就是把一個 Object 對象的所有信息表示成一個字節序列,這包括 Class 信息、繼承關系、訪問權限、變量類型以及數值信息等。

數據存儲不一定就是將數據存放到磁盤中,比如放到內存中、通過網絡傳輸也可以算是存儲的一種形式。或者我們也可以把這個過程叫做對象或者數據的序列化。

Serializable 簡單使用

“好像我們只需要簡單實現 Serializable 接口,它就可以完成序列化/反序列化工作了”, 那它是如何工作的呢?實際上 Serializalbe 這種簡單機制是通過犧牲掉執行性能為代價換來的,也就是在時間開銷和使用成本的權衡上,Serializable 機制選擇是使用成本優先。

public final class Person implements Serializable {

    private String name;
    private int age;
    private String like;

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getLike() {
        return like;
    }

    public void setLike(String like) {
        this.like = like;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }

    @Override
    public String toString() {
        return "Person{" +
                "name='" + name + '\'' +
                ", age=" + age +
                ", like='" + like + '\'' +
                '}';
    }
}

使用 ObjectOutputStream 將 Person 對象序列化到磁盤:

public void testWritePersonObject() {
    Person person;
    ObjectOutputStream out = null;
    try {
        out = new ObjectOutputStream(openFileOutput("person", Context.MODE_PRIVATE));
        person = new Person();
        out.writeObject(person);
    } catch (IOException e) {
        e.printStackTrace();
    } finally {
        IOUtil.close(out);
    }
}

整個使用過程是不是非常簡單?雖然 Serializable 使用真的非常簡單,但是也有一些需要注意的事項。另外它還有一些進階的使用技巧。文中也會詳細介紹到,并且最后也會給大家總結出該部分內容。

Serializable 背后實現原理
  • ObjectOutputStream 和 ObjectInputStream

不知道大家是否跟我一樣,在最初使用 Serializable 時是否會有這樣的疑問:“我們只需要實現這個接口,好像什么都不用管了,那它是如何實現序列化的呢?”

實際上 Serializable 的實現原理分別在 ObjectOutputStream 和 ObjectInputStream 中(本文主要給大家介紹序列化過程),整個序列化過程使用了大量反射和臨時變量,而且在序列化對象的時候,不僅會序列化當前對象本身,還需要遞歸序列化對應引用的其他對象。

先來看下 ObjectObjectStream 的構造方法:

    public ObjectOutputStream(OutputStream out) throws IOException {
    //檢查繼承權限
    verifySubclass();
    //構造一個BlockDataOutputStream用于向out寫入序列化數據
    bout = new BlockDataOutputStream(out);
    //構造一個大小為10,負載因子為3的HandleTable和ReplaceTable
    handles = new HandleTable(10, (float) 3.00);
    subs = new ReplaceTable(10, (float) 3.00);
    //有參構造方法默認為false,
    enableOverride = false;
    writeStreamHeader();
    //將緩存模式打開,寫入數據時先寫入緩沖區
    bout.setBlockDataMode(true);
    if (extendedDebugInfo) {
        debugInfoStack = new DebugTraceInfoStack();
    } else {
        debugInfoStack = null;
    }
}

我們從上面例子中 ObjectOutputStream writeObject 方法開始說起:

public final void writeObject(Object obj) throws IOException {
    if (enableOverride) {
        //這里判斷是否需要重寫writeObject()
        //如果使用無參構造方法創建,該變量默認為true,此時必須重寫writeObject()
        //此時實際需要重寫的方法為:writeObjectOverride,否則拋出異常
        writeObjectOverride(obj);
        return;
    }
    try {
        //執行writeObject0()
        writeObject0(obj, false);
    } catch (IOException ex) {
        if (depth == 0) {
            try {
                writeFatalException(ex);
            } catch (IOException ex2) {}
        }
        throw ex;
    }
}

在 writeObject 方法中首先判斷當前是否重寫了 writeObject 方法,如果我們使用無參構造方法創建 ObjectOutStream 實例,此時必須重寫該方法(實際重寫的是 writeObjectOverride 方法),否則會拋出異常。

接著跟蹤 writeObject0 方法:

private void writeObject0(Object obj, boolean unshared) throws IOException {
    boolean oldMode = bout.setBlockDataMode(false);
    depth++;
    try {
        int h;
        //處理以前寫過和不可替換的對象
        //如果obj為null,此時一定返回null
        if ((obj = subs.lookup(obj)) == null) {
            writeNull();
            return;
        } else if (!unshared && (h = handles.lookup(obj)) != -1) {
            writeHandle(h);
            return;
        }

        Object orig = obj;
        Class<?> cl = obj.getClass();
        ObjectStreamClass desc;

        Class repCl;
        //創建ObjectStreamClass實例
        //ObjectStreamClass表示序列化對象的class詳細信息
        desc = ObjectStreamClass.lookup(cl, true);

        if (desc.hasWriteReplaceMethod()
                && (obj = desc.invokeWriteReplace(obj)) != null
                && (repCl = obj.getClass()) != cl) {
            //如果實現了writeReplace方法替換掉原寫入對象 與
            //該方法返回對象不為null 與
            //返回對象類型不是當前序列化類型
            cl = repCl;
            //生成替換對象類型的ObjectStreamClass
            desc = ObjectStreamClass.lookup(cl, true);
        }

        if (enableReplace) {
            //如果通過enableReplaceObject()方法設置enablReplace
            //此時回調replaceObject,需要子類重寫,否則返回與上述obj一致。
            //這里是繼承ObjectOutputStream之后重寫了replaceObject方法
            Object rep = replaceObject(obj);
            if (rep != obj && rep != null) {
                cl = rep.getClass();
                desc = ObjectStreamClass.lookup(cl, true);
            }
            //重新賦值要序列化的對象
            obj = rep;
        }

        if (obj != orig) {
            //如果替換了原序列化對象
            subs.assign(orig, obj);
            if (obj == null) {
                writeNull();
                return;
            } else if (!unshared && (h = handles.lookup(obj)) != -1) {
                writeHandle(h);
                return;
            }
        }

        // remaining cases
        // BEGIN Android-changed
        if (obj instanceof Class) {
            writeClass((Class) obj, unshared);
        } else if (obj instanceof ObjectStreamClass) {
            writeClassDesc((ObjectStreamClass) obj, unshared);
        // END Android-changed
        } 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(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);
    }
}

該方法中我們重點關心 desc = ObjectStreamClass.lookup 和
writeOrdinaryObject 兩個方法。

首先看 ObjectStreamClass.lookup 方法:它返回 ObjectStreamClass 實例,ObjectStreamClass 保存了要序列化對象的 Class 相關信息:Class 名稱、serialVersionUID、實現了 Serializable 還是 Externalizable 接口,是否自行實現了 writeObject 和 readObject 方法等內容。每個序列化對象類型對應一個 ObjectStreamClass 實例。

static ObjectStreamClass lookup(Class<?> cl, boolean all) {
    if (!(all || Serializable.class.isAssignableFrom(cl))) {
        //all在ObjectOutStream中默認傳遞true
        //判斷對象類型是否實現了Serializable接口
        //如果all=false 與 未實現Serializable接口則直接return。
        return null;
    }
    //清除失去Class引用的對應ObjectStreamClass實例緩存
    //不過這在Android好像并不能生效,因為在Android JVM 中類是不可以被卸載的
    processQueue(Caches.localDescsQueue, Caches.localDescs);
    //將寫入對象類交給弱引用持有,WeakClassKey繼承自WeakReference
    WeakClassKey key = new WeakClassKey(cl, Caches.localDescsQueue);
    //根據類對象獲取是否存在緩存的EntryFutrue或ObjectStreamClass
    //這里可能大家會有疑問,每次new WeakClassKey() 作為key?實際上它內部使用
    //Class作為hash值的計算
    Reference<?> ref = Caches.localDescs.get(key);
    Object entry = null;
    if (ref != null) {
        //如果引用不為null,獲取引用的實際類型
        //在Android中只要是之前緩存過,則該ref永遠不為null(Class 不可以被卸載)
        //獲取該ref引用的對象
        entry = ref.get();
    }
    EntryFuture future = null;
    //如果引用的對象已經被回收
    if (entry == null) {
        //創建EntryFuture,并使用軟引用關聯。
        EntryFuture newEntry = new EntryFuture();
        Reference<?> newRef = new SoftReference<>(newEntry);
        do {
            if (ref != null) {
                //此時實際的引用對象已經被回收了,故ref引用就沒有用了
                //從緩存中刪除該鍵值對
                Caches.localDescs.remove(key, ref);
            }
            //localDescs是ConcurrentHashMap,
            //putIfAbsent()方法,不存在該key就添加進去,返回null
            //如果存在就返回之前的Reference。
            ref = Caches.localDescs.putIfAbsent(key, newRef);
            if (ref != null) {
                entry = ref.get();
            }
            //這里是要保證該key(Class)對應的Reference中持有的對象沒有被回收
        } while (ref != null && entry == null);

        if (entry == null) {
            //如果entry為null,此時future就是新創建的EntryFuture
            future = newEntry;
        }
    }
    if (entry instanceof ObjectStreamClass) {
        //如果拿到的是ObjectStreamClass,直接返回
        return (ObjectStreamClass) entry;
    }
    if (entry instanceof EntryFuture) {
        //如果拿到的是EntryFuture
        future = (EntryFuture) entry;
        if (future.getOwner() == Thread.currentThread()) {
            //如果創建這個EntryFuture線程就是當前線程
            //意思就是當前新創建的這個EntryFuture
            //這里實際判斷該future是否是剛創建的newEntry
            entry = null;
        } else {
            //否則就獲取之前的set的
            entry = future.get();
        }
    }
    if (entry == null) {
        try {
            //如果是新創建的future,此時就創建新的ObjectStreamClass
            entry = new ObjectStreamClass(cl);
        } catch (Throwable th) {
            entry = th;
        }
        if (future.set(entry)) {
            //EntryFuture中沒有關聯過entry
            //關聯,并緩存為當前key對應引用關系
            Caches.localDescs.put(key, new SoftReference<Object>(entry));
        } else {
            // nested lookup call already set future
            entry = future.get();
        }
    }

    //entr必須為是ObjectStreamClass,否則拋異常
    if (entry instanceof ObjectStreamClass) {
        return (ObjectStreamClass) entry;
    } else if (entry instanceof RuntimeException) {
        throw (RuntimeException) entry;
    } else if (entry instanceof Error) {
        throw (Error) entry;
    } else {
        throw new InternalError("unexpected entry: " + entry);
    }
}

lookup() 方法的第一個 if 語句就判斷了要序列化的類是否實現了 Serializable 接口,否則返回 null,此時回到 ObjectOutputStream 的 writeObject0() 方法將會出現異常,這里也證明要序列化對象必須實現 Serializable 接口。

lookup 整個方法看起來有些繁瑣,實際是從緩沖中獲取該序列化類型對應的 ObjectStreamClass 對象,ObjectStreamClass 的引入一方面是為了代碼解耦,另一個重要方面是提高獲取類信息的速度(ObjectStreamClass 保存了當前序列化類的相關信息),系統通過 SoftReference 持有 Class 和 ObjectStreamClass 的映射關系。如果反復對一個類的實例進行序列化操作,能夠有效減少耗時,提高緩存命中率。

源碼中有幾個需要說明的地方:

  • WeakClassKey 繼承自 WeakReference,這里持有要序列對象的 Class,不過正如在注釋中所述:這在 Android 中好像并不能生效,因為在 Android 平臺 Java 虛擬機中,類對象是不可以被回收的(這也是出于性能的考慮)。

  • localDescs 是一個 ConcurrentHashMap 對象,源碼中可以看到每次 new WeakClassKey() 對象作為 key 獲取是否已經緩存過對應的 ObjectStreamClass 實例?實際上 WeakClassKey 重寫了 hashCode() 方法,故這里采用的是 Class 對象來計算的 Hash 值。

  • 源碼中的 do {} while() 作用是保證該 Class 對象能夠正常獲取到對應的 ObjectStreamClass 對象。

  • EntryFuture 是 ObjectStreamClass 的內部類,引入它的作用我個人認為是多線程調用 lookup() 方法而設立的。主要體現在判斷:if(future.getOwner() == Thread.currentThread()) 如果成立說明此時 EntryFuture 就是剛剛創建的。否則是從緩存中獲取到的(但不代表就是其它線程創建的)。

跟蹤到這里有必要進一步分析下 ObjectStreamClass 這個類,看下它的構造方法:

private ObjectStreamClass(final Class<?> cl) {
    this.cl = cl;
    //序列化對象的類名稱
    name = cl.getName();
    //判斷是否是代理類
    isProxy = Proxy.isProxyClass(cl);
    //是否是Enum
    isEnum = Enum.class.isAssignableFrom(cl);
    //是否實現了Serialable接口
    serializable = Serializable.class.isAssignableFrom(cl);
    //是否實現了Externalizable接口
    externalizable = Externalizable.class.isAssignableFrom(cl);
    //獲取其父類
    Class<?> superCl = cl.getSuperclass();
    //與之前分析的lookup()方法一致,這里會遞歸調用每個父類,
    //因為lookup()又會創建對應類的ObjectStramClass
    //superDesc表示其父類的ObjectStreamClass
    superDesc = (superCl != null) ? lookup(superCl, false) : null;
    //表示當前的ObjectStreamClass
    localDesc = this;

    if (serializable) {
        //如果實現了Serializable接口
        AccessController.doPrivileged(new PrivilegedAction<Void>() {
            public Void run() {
                if (isEnum) {
                    suid = Long.valueOf(0);
                    fields = NO_FIELDS;
                    //枚舉類型,此時沒有 Field
                    return null;
                }
                if (cl.isArray()) {
                    //數組類型
                    fields = NO_FIELDS;
                    return null;
                }
                //生成serialVersionUID
                //會去反射我們有沒有聲明serialVersionUID
                suid = getDeclaredSUID(cl);
                try {
                    //反射獲取該類所有需要被序列化的Field
                    //首先反射是否定義了serialPersistentFields,如果存在則根據其內部聲明為準。
                    //否則直接反射獲取該類所有的字段(過濾static 和 transient)
                    fields = getSerialFields(cl);
                    computeFieldOffsets();
                } catch (InvalidClassException e) {
                    serializeEx = deserializeEx =
                            new ExceptionInfo(e.classname, e.getMessage());
                    fields = NO_FIELDS;
                }

                if (externalizable) {
                    //如果實現了Enternalizable接口,獲取其構造方法
                    cons = getExternalizableConstructor(cl);
                } else {
                    //獲取構造方法
                    cons = getSerializableConstructor(cl);
                    //反射獲取是否實現了writeObject方法
                    writeObjectMethod = getPrivateMethod(cl, "writeObject",
                            //參數類是 ObjectOutputStream
                            new Class<?>[]{ObjectOutputStream.class},
                            Void.TYPE);
                    //反射獲取是否實現了readObject方法
                    readObjectMethod = getPrivateMethod(cl, "readObject",
                            new Class<?>[]{ObjectInputStream.class},
                            Void.TYPE);
                    //反射獲取是否實現了readObjectNoData方法
                    readObjectNoDataMethod = getPrivateMethod(
                            cl, "readObjectNoData", null, Void.TYPE);
                    hasWriteObjectData = (writeObjectMethod != null);
                }
                //反射獲取是否實現了writeReplace
                writeReplaceMethod = getInheritableMethod(
                        cl, "writeReplace", null, Object.class);
                //反射獲取是否實現了readResolve
                readResolveMethod = getInheritableMethod(
                        cl, "readResolve", null, Object.class);
                return null;
            }
        });
    } else {
        suid = Long.valueOf(0);
        fields = NO_FIELDS;
    }

    // ... 省略
}

ObjectStreamClass 類的構造方法做的事情還是蠻多的:獲取序列化類的相關信息、一系列類型檢查、創建其父類的 ObjecStreamClass 實例、通過反射獲取當前類的所有字段信息、反射獲取是否自行實現了 writeObject()、readObject() 、writeReplace()、readResolve() 等方法。

下面我們就幾個關鍵方法做進一步說明:

  • suid = getDeclaredSUID(cl) 獲取 serialVersionUID

系統通過反射的方式判斷我們是否有聲明 serialVersionUID:

private static Long getDeclaredSUID(Class<?> cl) {
    try {
        //通過反射獲取是否定義了serialVersionUID
        Field f = cl.getDeclaredField("serialVersionUID");
        int mask = Modifier.STATIC | Modifier.FINAL;
        if ((f.getModifiers() & mask) == mask) {
            //必須static final聲明
            f.setAccessible(true);
            //必須用static聲明,否則獲取不到
            return Long.valueOf(f.getLong(null));
        }
    } catch (Exception ex) {
    }
    return null;
}

從這里我們可以看出自定義 serialVersionUID 必須聲明為 static final。

默認 serialVersionUID 計算規則在 ObjectStreamClass computeDefaultSUID 方法。序列化使用一個 hash,該 hash 是根據給定源文件中幾乎所有東西:方法名稱、字段名稱、字段類型、訪問修改方法等-計算出來的,序列化將該 hash 值與序列化流中的 hash 值比較。

為了使 Java 運行時相信兩種類型實際上是一樣的,第二版和隨后版本的 Person 必須與第一版有相同的序列化版本 hash(存儲為 private static final serialVersionUID 字段)。因此,我們需要 serialVersionUID 字段,它是通過對原始(或 V1)版本的 Person 類運行 JDK serialver 命令計算出的。

一旦有了 Person 的 serialVersionUID,不僅可以從原始對象 Person 的序列化數據創建 PersonV2 對象(當出現新字段時,新字段被設為缺省值,最常見的是“null”),還可以反過來做:即從 PersonV2 的數據通過反序列化得到 Person。

整個默認 serialVersionUID 計算流程非常繁瑣和復雜,通常我建議顯示聲明會更加穩妥,因為隱士聲明假如類發生一點點變化,進行反序列化都會由于 serialVersionUID 改變而導致 InvalidClassException 異常。

  • fields = getSerialFields(cl) 反射獲取當前類的所有字段

首先系統會判斷我們是否自行實現了字段序列化 serialPersistentFields 屬性,否則走默認序列化流程,既忽律 static、transient 字段。

private static ObjectStreamField[] getSerialFields(Class<?> cl)
        throws InvalidClassException {
    ObjectStreamField[] fields;
    //必須實現了Serialiazble,并且不是代理類,不是Externalizable,不是接口類型
    if (Serializable.class.isAssignableFrom(cl)
            && !Externalizable.class.isAssignableFrom(cl)
            && !Proxy.isProxyClass(cl)
            && !cl.isInterface()) {

        //是否聲明serialPersistentFields的自定義字段序列化規則
        if ((fields = getDeclaredSerialFields(cl)) == null) {
            //默認序列化字段規則
            fields = getDefaultSerialFields(cl);
        }
        Arrays.sort(fields);
    } else {
        fields = NO_FIELDS;
    }
    return fields;
}

我們先來看下默認序列化流程的字段獲取規則:

 private static ObjectStreamField[] getDefaultSerialFields(Class<?> cl) {
    //反射獲取當前類的所有字段
    Field[] clFields = cl.getDeclaredFields();
    ArrayList<ObjectStreamField> list = new ArrayList<>();
    //忽略 static 或 transient 字段
    int mask = Modifier.STATIC | Modifier.TRANSIENT;

    for (int i = 0; i < clFields.length; i++) {
        if ((clFields[i].getModifiers() & mask) == 0) {
            //非static,非transient字段
            //將其封裝在ObjectStreamField中
            //ObjectStreamField是對Field封裝
            list.add(new ObjectStreamField(clFields[i], false, true));
        }
    }
    int size = list.size();
    return (size == 0) ? NO_FIELDS :
            list.toArray(new ObjectStreamField[size]);
}

可以看到系統默認過濾掉 static、transient 聲明的字段,將相關字段 Field 包裝在 ObjectStreamField ,ObjectStreamField 是對 Field 的封裝,簡單看下它的構造方法:

 ObjectStreamField(Field field, boolean unshared, boolean showType) {
    //當前字段
    this.field = field;
    //是否可以被序列化
    this.unshared = unshared;
    //字段名稱
    name = field.getName();
    //字段類型
    Class<?> ftype = field.getType();
    //是基本類型還是引用類型
    type = (showType || ftype.isPrimitive()) ? ftype : Object.class;
    //獲取字段類型的簽名
    signature = getClassSignature(ftype).intern();
}

那該如何理解 serialPersistentFields 自行實現字段序列化規則呢?我們還是通過上面的的示例來了解下:

  • serialPersistentFields 自行實現字段序列化規則

      public final class Person implements Serializable {
    
      private String name;
      private int age;
      private String like;
    
      private transient String features;
    
      private static final ObjectStreamField[] serialPersistentFields = {
              new ObjectStreamField("name", String.class),
              new ObjectStreamField("age", Integer.class),
              new ObjectStreamField("features", String.class)
      };
    

    }

對 Person 做了下簡單修改,添加 serialPersistentFields 字段,并指定了要序列化的字段名稱,前面有分析道 Serializable 默認序列化規則會忽略 static、transient 聲明的字段。此時我們看下自行實現字段序列化規則,看下它是如何實現的:

private static ObjectStreamField[] getDeclaredSerialFields(Class<?> cl)
        throws InvalidClassException {
    ObjectStreamField[] serialPersistentFields = null;
    try {
        //通過反射serialPersistentFields判斷是否聲明自定義字段序列化規則
        Field f = cl.getDeclaredField("serialPersistentFields");
        int mask = Modifier.PRIVATE | Modifier.STATIC | Modifier.FINAL;
        if ((f.getModifiers() & mask) == mask) {
            //必須聲明為private static final
            f.setAccessible(true);
            serialPersistentFields = (ObjectStreamField[]) f.get(null);
        }
    } catch (Exception ex) {
    }
    if (serialPersistentFields == null) {
        return null;
    } else if (serialPersistentFields.length == 0) {
        return NO_FIELDS;
    }

    //保存需要序列化的Field字段
    ObjectStreamField[] boundFields =
            new ObjectStreamField[serialPersistentFields.length];
    Set<String> fieldNames = new HashSet<>(serialPersistentFields.length);
    for (int i = 0; i < serialPersistentFields.length; i++) {
        ObjectStreamField spf = serialPersistentFields[i];
        //獲取字段名稱
        String fname = spf.getName();
        if (fieldNames.contains(fname)) {
            //不能存在重復的字段名
            throw new InvalidClassException(
                    "multiple serializable fields named " + fname);
        }
        //不允許存在重復的字段名稱
        fieldNames.add(fname);

        //大量反射
        try {
            //根據字段名稱獲取對應的Field
            Field f = cl.getDeclaredField(fname);
            //自定義傳遞字段類型必須與實際類型一致
            if ((f.getType() == spf.getType())
                    //不是static聲明字段
                    && ((f.getModifiers() & Modifier.STATIC) == 0)) {

                boundFields[i] =
                        new ObjectStreamField(f, spf.isUnshared(), true);
            }
        } catch (NoSuchFieldException ex) {
        }
        if (boundFields[i] == null) {
            //反射獲取第i個字段失敗
            boundFields[i] = new ObjectStreamField(
                    fname, spf.getType(), spf.isUnshared());
        }
    }
    return boundFields;
}

serialPersistentFields 屬性可以幫助我們替換默認字段序列化流程,ObjectStreamField 類型數組定義了需要被序列化的字段,而且該規則將會替代默認字段序列化規則聲明,否則將不會被自動序列化。

此時 transitent 聲明字段也可以被添加進序列化規則,但是 static 聲明字段仍然不可以:

((f.getModifiers() & Modifier.STATIC) == 0)
  • 關于 serialPersistentFields 屬性簡單做下總結:
  1. serialPersistentFields 必須用 private static final 聲明;
  2. 該屬性會替換默認字段(非static、非transient)序列化規則,此時需要序列化字段必須添加到屬性中;
  3. 被 transient 聲明字段也可以被添加進序列化中。

serialPersistentFields 相對來說提供的“支持”有限,比如我們我們要將相關敏感數據序列化之前先經過加密,反序列時自動對其解密,該如何更好的實現呢?writeObject() 和 readObject() 這兩個方法就可以幫我們一次性滿足。

  • writeObject() 和 readObject() 方法作用

還是通過上面的例子,添加 writeObject() 和 readObject() 自定義序列化/反序列流程:

public final class Person implements Serializable {

    private String name;
    private int age;
    private String like;

    private void writeObject(java.io.ObjectOutputStream stream)
            throws java.io.IOException {
        //對age字段序列化之前進行加密
        age = age << 2;
        //繼續使用默認序列化流程
        stream.defaultWriteObject();
        //也可以直接
        //stream.writeInt(age);
    }

    private void readObject(java.io.ObjectInputStream stream)
            throws java.io.IOException, ClassNotFoundException {
        stream.defaultReadObject();
        //也可以使用
        //stream.readInt();
        //反序列化后對其解密
        age = age << 2;
    }
}

通過自行實現序列化 writeObject 和 反序列化 readObject 方法替換默認流程,我們可以對某些字段做些特殊修改,也可以實現序列化的加密功能。

如果此時我們序列化對象的類型發生了變化時該如何處理呢?這時我們可以通過 writeReplace 和 readResolve 方法實現對序列化的版本兼容。接下來我們就聊聊 wirteReplace 和 readResolve 方法的作用。

  • writeReplace 和 readResolve 方法作用

還是通過上面的例子,做下改造進行說明:

    public final class Person implements Serializable {

    private String name;
    private int age;
    private String like;

    private Person spouse;

    public Person(String name, int age, String like) {
        this.name = name;
        this.age = age;
        this.like = like;
    }

    private Object writeReplace()
            throws java.io.ObjectStreamException {
        return new PersonProxy(this);
    }

    public void setSpouse(Person value) {
        spouse = value;
    }

    public Person getSpouse() {
        return spouse;
    }

    //代理類
    private class PersonProxy implements Serializable {

        private String data;

        public PersonProxy(Person person) {
            this.data = person.name + "," + person.age + "," + person.like;

            final Person spouse = person.spouse;
            if (spouse != null) {
                data = data + "," + spouse.name + "," + spouse.age + "," + spouse.like;
            }
        }

        private Object readResolve()
                throws java.io.ObjectStreamException {
            String[] pieces = data.split(",");

            final Person result = new Person(pieces[0], Integer.parseInt(pieces[2]), pieces[1]);
            if (pieces.length > 3) {
                result.setSpouse(new Person(pieces[3], Integer.parseInt
                        (pieces[5]), pieces[4]));
                result.getSpouse().setSpouse(result);
            }
            return result;
        }
    }
}

writeReplace 和 readResolve 方法使 Person 類可以將它的所有數據(或其中的核心數據)打包到一個 PersonProxy 中,將它放入到一個流中,然后在反序列化時再進行解包。

注意,PersonProxy 必須跟蹤 Person 的所有數據。這通常意味著代理需要是 Person 的一個內部類,以便能訪問 private 字段。有時候,代理還需要追蹤其他對象引用并手動序列化它們,例如 Person 的 spouse。

這種技巧是少數幾種不需要讀/寫平衡的技巧之一。例如,一個類被重構成另一種類型后的版本可以提供一個 readResolve 方法,以便靜默地將被序列化的對象轉換成新類型。類似地,它可以采用 writeReplace 方法將舊類序列化成新版本。

  • Serializable 的序列化/反序列化的調用流程如下:

      //序列化
      E/test:SerializableTestData writeRepace
      E/test:SerializableTestData writeObject
      
      //反序列化
      E/test:Serializable readObject
      E/test:Serializable readResolve
    

分析到這里不知道你是否有感受到,Serializable 整個計算過程非常復雜,而且因為存在大量反射和 GC 的影響,序列化的性能會比較差。另外一方面因為序列化文件需要包含的信息非常多,導致它的大小比 Class 文件本身還要大很多,這樣又會導致 I/O 讀寫上的性能問題。

重新回到 ObjectOutputStream writeObject0 方法中,接著向下分析(詳細方法體已在上面貼出),在拿到 ObjectStreamClass 實例后,首先判斷是否有自行實現的 writeReplace、并且該方法返回不為 null 與不是當前對象類型:

        if (desc.hasWriteReplaceMethod()
                && (obj = desc.invokeWriteReplace(obj)) != null
                && (repCl = obj.getClass()) != cl) {
            //如果實現了writeReplace方法替換掉原寫入對象 與
            //該方法返回對象不為null 與
            //返回對象類型不是當前序列化類型
            cl = repCl;
            //生成替換對象類型的ObjectStreamClass
            desc = ObjectStreamClass.lookup(cl, true);
        }

這里大家可以結合上面對 ObjectStreamClass 構造方法和示例進行理解。

接著最后 writeOrdinaryObject 方法(這里只看主線流程):

    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 {
        //檢查ObjectStreamClass對象
        desc.checkSerialize();
        //寫入字節0x73
        bout.writeByte(TC_OBJECT);
        //寫入class信息
        writeClassDesc(desc, false);
        handles.assign(unshared ? null : obj);
        if (desc.isExternalizable() && !desc.isProxy()) {
            //是否實現了Externalizable與不是代理類型
            writeExternalData((Externalizable) obj);
        } else {
            //寫入該對象變量信息及其父類的成員變量
            writeSerialData(obj, desc);
        }
    } finally {
        if (extendedDebugInfo) {
            debugInfoStack.pop();
        }
    }
}

checkSerialize 方法是檢查之前一系列流程是否發生過異常,否則將拋出異常。

這里我們重點跟蹤下 writeSerialData 方法:

   private void writeSerialData(Object obj, ObjectStreamClass desc)
    throws IOException {

    //獲取當前類以及其父類所有實現Serializable的ObectStreamClass
    ObjectStreamClass.ClassDataSlot[] slots = desc.getClassDataLayout();
    
    for (int i = 0; i < slots.length; i++) {
        ObjectStreamClass slotDesc = slots[i].desc;
        //判斷是否重寫了writeObject()方法
        //還記得在ObjectStreamClass構造方法中判斷當前類中是否定義了writeObject方法,
        //判斷writeObject method 是否為null
        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);
        }
    }
}

注意看 for 循環遍歷所有的 ObjectStreamClass , 根據是否自行實現了 writeObject 方法走自行實現字段序列化規則還是走默認序列化規則 defaultWriteFields 方法,關于自定義 writeObject 方法序列化規則,上面通過示例做過分析。下面我們主要看下默認序列化規則 defaultWriteField 方法:

    private void defaultWriteFields(Object obj, ObjectStreamClass desc)
    throws IOException {
    Class<?> cl = desc.forClass();
    if (cl != null && obj != null && !cl.isInstance(obj)) {
        //obj不是cl類型實例
        throw new ClassCastException();
    }
    //檢查中間過程是否有非法信息
    desc.checkDefaultSerialize();

    int primDataSize = desc.getPrimDataSize();
    if (primVals == null || primVals.length < primDataSize) {
        primVals = new byte[primDataSize];
    }
    desc.getPrimFieldValues(obj, primVals);
    bout.write(primVals, 0, primDataSize, false);
    //獲取當前類的所有字段信息
    ObjectStreamField[] fields = desc.getFields(false);
    Object[] objVals = new Object[desc.getNumObjFields()];
    int numPrimFields = fields.length - objVals.length;
    //返回獲取每個Field的值,保存在objVals中
    //這里就是通過反射獲取到Field在當前obj中對應的值
    desc.getObjFieldValues(obj, objVals);
    for (int i = 0; i < objVals.length; i++) {
        if (extendedDebugInfo) {
            debugInfoStack.push(
                "field (class \"" + desc.getName() + "\", name: \"" +
                fields[numPrimFields + i].getName() + "\", type: \"" +
                fields[numPrimFields + i].getType() + "\")");
        }
        try {
            //根據實際類型調用
            //這里會遞歸調用writeObject0方法,
            //writeObject0方法的最后根據實際類型寫入
            //如果是引用類型,又會重復該過程
            writeObject0(objVals[i],
                         fields[numPrimFields + i].isUnshared());
        } finally {
            if (extendedDebugInfo) {
                debugInfoStack.pop();
            }
        }
    }
}

該類的所有字段已經保存在 ObjectStreamClass 中,這里也是直接獲取到所有 Field,然后通過反射獲取到該 Field 在當前 obj 中的值,然后根據實際類型調用 writeObject0 方法,注意如果是引用類型,又會遞歸調用該過程。

    if (obj instanceof Class) {
        writeClass((Class) obj, unshared);
    } else if (obj instanceof ObjectStreamClass) {
        writeClassDesc((ObjectStreamClass) obj, unshared);
    // END Android-changed
    } 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(obj, desc, unshared);
    } else {
        if (extendedDebugInfo) {
            throw new NotSerializableException(
                    cl.getName() + "\n" + debugInfoStack.toString());
        } else {
            throw new NotSerializableException(cl.getName());
        }
    }

以寫入 writeString 方法為例看下:

private void writeString(String str, boolean unshared) throws IOException {
    handles.assign(unshared ? null : str);
    long utflen = bout.getUTFLength(str);
    if (utflen <= 0xFFFF) {
        bout.writeByte(TC_STRING);
        bout.writeUTF(str, utflen);
    } else {
        bout.writeByte(TC_LONGSTRING);
        bout.writeLongUTF(str, utflen);
    }
}

實際先寫入到 BlockDataOutputStream 緩存中(ObjectOutputStream 構造方法中創建,默認緩存大小是 1KB),每當寫入內容超過該閾值:

public void write(int b) throws IOException {
        if (pos >= MAX_BLOCK_SIZE) {
            //超過最大最大可寫入此時要強制寫入
            //outputStream
            drain();
        }
        buf[pos++] = (byte) b;
}

void drain() throws IOException {
        if (pos == 0) {
            return;
        }
        if (blkmode) {
            writeBlockHeader(pos);
        }
        //寫入到outputStream中
        out.write(buf, 0, pos);
        pos = 0;
        warnIfClosed();
    }

此時調用的就是傳入 ObjectOutputStream 構造方法的輸出流了。

分析到這里,關于 Serializable 序列化背后的實現原理就已經分析完了,當然文中還省去了大部分實現細節,感興趣的朋友可以進一步閱讀源碼進行分析。

Java 提供的 Serializable 對象序列化機制,遠比大多數 Java 開發人員想象的更靈活,這使我們有更多的機會解決棘手的情況。其實像這樣的編程妙招在 JVM 中隨處可見。關鍵是我們要知道它們,在后續的項目中可以更好的實踐它們。

總結

  • Serializable 序列化支持替代默認流程,它會先反射判斷是否存在我們自己實現的序列化方法 writeObject 或 反序列化方法 readObject。通過這兩個方法,我們可以對某些字段做一些特殊修改,也可以實現序列化的加密功能。

  • 我們可以通過 writeReplace 和 readResolve 方法實現自定義返回的序列化實例。通過它們實現對序列化的版本兼容,例如通過 readResolve 方法可以把老版本的序列化對象轉換成新版本的對象類型。

  • Serializable 整個序列化過程使用了大量的反射和臨時變量,而且在序列化對象的時候,不僅會序列化當前對象本身,還需要遞歸序列化引用的其它對象。

Serializable 使用注意事項
  • 不被序列化字段。類的 static 變量以及被聲明為 transient 的字段,默認的序列化機制都會忽略該字段,不會進行序列化存儲。當然我們也可以使用進階的 writeObject 和 readObject 方法做自定義的序列化存儲。

  • serialVersionUID。在類實現了 Serializable 接口后,我們需要添加一個 Serial Version ID,它相當于類的版本號。這個 ID 我們可以顯示聲明也可以讓編譯器自己計算。通常建議顯示聲明會更加穩妥,因為隱士聲明假如類發生了一點點變化,進行反序列化都會由于 serialVersionUID 改變而導致 InvalidClassException 異常。

  • 構造方法。Serializable 的反序列化默認是不會執行構造函數的,它是根據數據流中對 Object 的描述信息創建對象的。如果一些邏輯依賴構造函數,就可能會出現問題,例如一個靜態變量只在構造方法中賦值,當然我們也可以通過進階的自定義反序列化修改。

雖然 Serializalbe 性能那么差,但是它也有一些進階的使用技巧。不過在 Android 需要重新設計一套更加輕量且高效的機制,感興趣可以繼續閱讀該系列下篇文章《Android 對象序列化之 Parcelable 取代 Serializable ?

推薦參考資料

以上便是個人在學習 Serializable 時相關心得和體會,文中如有不妥或有更好的分析結果,歡迎指出。

文章如果對你有幫助,就請留個贊吧!

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。