Android序列化(一) 之 Serializable

1 简介

序列化是指将数据对象转换成为一种可存储或可传输的数据格式,而反序列化则是相反的操作,将序列化后的数据还原成对象。最为常见的序列化应用有Json和XML,它们都是行业公认的标准。而在 Java 里,有专门提供了 Serializable 接口用于对象的序列化和反序列化。

Serializable接口在java.io包中定义,它本身并不存任何字段和方法,只是用于标识类为可序列化。类对象在序列化后会被转换成为字节输出流OutputStream(BufferedOutputStream、ByteArrayOutputStream、DataOutputStream、FilterOutputStream等)的形式可存储到本地文件或者用于传输,反序列化时则是将字节输入流InputStream进行解析后重新创建新的类对象。

2 示例

public class Student implements Serializable {
    private static final long serialVersionUID = 1L;

    private int id;
    private String name;
    private boolean verified;
    private List<Phone> phones;

    public int getId() {
        return id;
    }
    public void setId(int id) {
        this.id = id;
    }
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
    public void setVerified(boolean verified) {
        this.verified = verified;
    }
    public boolean isVerified() {
        return verified;
    }
    public List<Phone> getPhones() {
        return phones;
    }
    public void setPhones(List<Phone> phones) {
        this.phones = phones;
    }

    static class Phone implements Serializable {
        private static final long serialVersionUID = 2L;

        private String number;
        private String type;

        public String getNumber() {
            return number;
        }
        public void setNumber(String number) {
            this.number = number;
        }
        public String getType() {
            return type;
        }
        public void setType(String type) {
            this.type = type;
        }
    }
}
Student student = new Student();
student.setId(1001);
student.setName("子云心");
student.setVerified(true);
List<Student.Phone> phoneList = new ArrayList<>();
Student.Phone phone1 = new Student.Phone();
phone1.setType("MOBILE");
phone1.setNumber("12345678910");
phoneList.add(phone1);
Student.Phone phone2 = new Student.Phone();
phone2.setType("HOME");
phone2.setNumber("0000-1234567");
phoneList.add(phone2);
student.setPhones(phoneList);

try {
    byte[] byteData = serialization(student);
    Student student2 = deserialization(byteData);
} catch (IOException e) {
    e.printStackTrace();
}  catch (ClassNotFoundException e) {
    e.printStackTrace();
}

private byte[] serialization(Student student) throws IOException {
    // 保存到本地文件
    // ObjectOutputStream objectOutputStream = new ObjectOutputStream(new FileOutputStream("test.txt")); 
    
    // 保存到 ByteArrayOutputStream
    ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
    ObjectOutputStream objectOutputStream = new ObjectOutputStream(byteArrayOutputStream);
    objectOutputStream.writeObject(student);
    objectOutputStream.close();
    return byteArrayOutputStream.toByteArray();
}
private Student deserialization(byte[] byteData) throws IOException, ClassNotFoundException {
    ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(byteData);
    ObjectInputStream objectInputStream = new ObjectInputStream(byteArrayInputStream);
    Object object = objectInputStream.readObject();
    objectInputStream.close();
    return (Student) object;
}

3 关于serialVersionUID

在实现Serializable接口的类中,一般都需要声明一个名叫serialVersionUID的long类型变量,因为对于JVM来讲,要进行一个类对象的序列化和反序列化必须需要一个该类的标记。需要注意以下项:

  1. 如果反序列化时使用的serialVersionUID值跟序列化时使用的不一样,那么反序列化过程会抛出InvalidClassException异常。
  2. serialVersionUID变量并不是必需要主动声明,如果没有主动声明,则会在序列化时根据类的信息进行计算出该类默认的serialVersionUID值。
  3. 官方强烈建议使用时进行主动声明serialVersionUID变量,因为默认生成的serialVersionUID值可能会根据编译环境或者代码细节的差异从而导致反序列化中发生意外的InvalidClassException异常。例如应用在未开启R8和开启R8编译后,自动生成的serialVersionUID的值肯定是不一样的。
  4. serialVersionUID字段声明时尽量使用private关键字修饰,因为该字段只适用于该类内部序列化和反序列化过程,不应该作为其它场景使用和被继承。

4 全部字段自定义序列化

Serializable支持自定义序列化,使用Externalizable 接口替换Serializable接口,然后实现writeExternal和readExternal方法后,可以完全自定义所有字段的序列化和反序列化。

public class Student implements Externalizable {
    private static final long serialVersionUID = 1L;

    private int id;
    private String name;
    private boolean verified;
    private List<Phone> phones;
    ……
    @Override
    public void writeExternal(ObjectOutput out) throws IOException {
        out.writeObject(id + 90000);
        out.writeObject(name + ",你好");
        out.writeObject(verified);
        out.writeObject(phones);
    }

    @Override
    public void readExternal(ObjectInput in) throws ClassNotFoundException, IOException {
        id = (int)in.readObject();
        name = (String) in.readObject();
        verified = (boolean) in.readObject();
        phones= (List<Phone>)in.readObject();
    }
}

使用上需要注意:

  1. Externalizable接口本身也是继承于Serializable接口,只是多出两个方法。
  2. writeExternal方法的调用时机是在serialization方法的objectOutputStream.writeObject(student);里面,同理readExteranl方法的调用时机是在deserialization方法的Object object = objectInputStream.readObject();里面。
  3. readExteranl 方法内自定义字段的读取顺序必须要跟 writeExternal 方法内自定义字段的写入顺序保持一致,否则将会发生反序列化数据异常或类型转换异常报错。

5 部分字段自定义序列化

Serializable支持部分字段自定义序列化。例如某些字段在序列化时需要专门处理,则可以在这些字段前加个 transient 修饰符,那么在序列化和反序列化过程中就会自动过滤这些字段,然后可以在类中添加  writeObject(ObjectOutputStream objectOutputStream) 和 readObject(ObjectInputStream objectInputStream) 方法进行这些字段的自定义序列化和反序列化操作。

public class Student implements Serializable {
    private static final long serialVersionUID = 1L;

    private transient int id;
    private transient String name;
    private boolean verified;
    ……
    private void writeObject(ObjectOutputStream objectOutputStream) throws IOException {
        objectOutputStream.defaultWriteObject();

        objectOutputStream.writeObject(id + 90000);
        objectOutputStream.writeObject(name + ",你好");
    }
    private void readObject(ObjectInputStream objectInputStream) throws IOException, ClassNotFoundException {
        objectInputStream.defaultReadObject();

        id = (int)objectInputStream.readObject();
        name = (String) objectInputStream.readObject();
    }
}

使用上需要注意:

  1. 字段前加个 transient 修饰符,会在序列化时进行这些字段的过滤。
  2. writeObject方法的调用时机是在serialization方法的objectOutputStream.writeObject(student);里面,同理readObject方法的调用时机是在deserialization方法的Object object = objectInputStream.readObject();里面。
  3. writeObject方法和readObject方法内部需要先调用defaultWriteObject和defaultReadObject进行对那些没有添加transient 修饰符的正常字段的自动默认序列化和默认反序列化。如果不调用defaultWriteObject和defaultReadObject,那么也等于全部字段自定义序列化。
  4. readObject 方法内自定义字段的读取顺序必须要跟 writeObject 方法内自定义字段的写入顺序保持一致,否则将会发生反序列化数据异常或类型转换异常报错。

6 序列化原理

根据上面示例得知,序列化就是先创建ObjectOutputStream对象,然后调用其writeObject方法,所以要理解序列化的原理就从这里入手。

6.1 ObjectOutputStream构造

ObjectOutputStream.java

public ObjectOutputStream(OutputStream out) throws IOException {
    verifySubclass();
// 创建一个DataOutputStream对象bout,用于表示底层块数据输出流容器
    bout = new BlockDataOutputStream(out);
    ……
// 写入文件头信息,STREAM_MAGIC是序列化协议流头标识,STREAM_VERSION是协议版本号
    writeStreamHeader();
    ……
}
final static short STREAM_MAGIC = (short)0xaced;
final static short STREAM_VERSION = 5;
protected void writeStreamHeader() throws IOException {
    bout.writeShort(STREAM_MAGIC);
    bout.writeShort(STREAM_VERSION);
}

ObjectOutputStream对象的构造方法中,关键逻辑有:

  1. 创建一个DataOutputStream对象bout,用于表示底层块数据输出流容器。
  2. 写入文件头信息,STREAM_MAGIC是序列化协议流头标识,STREAM_VERSION是协议版本号。

6.2 writeObject方法写入对象

ObjectOutputStream.java

public final void writeObject(Object obj) throws IOException {
    ……
    try {
        writeObject0(obj, false);
    } catch (IOException ex) {
        ……
    }
}
private void writeObject0(Object obj, boolean unshared) throws IOException {
    ……
    depth++;
    try {
        ……
        Object orig = obj;
        Class<?> cl = obj.getClass();
        ObjectStreamClass desc;

        Class repCl;
        // 获取当前类的ObjectStreamClass,desc用于描述类的属性相关信息
        desc = ObjectStreamClass.lookup(cl, true);
        ……
        // 根据不同的类型进行不同的写操作
        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(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);
    }
}

writeObject0方法关键逻辑有:

  1. 通过ObjectStreamClass.lookup方法获取类的属性相关信息ObjectStreamClass,赋值给desc 变量。
  2. 根据类的类型选择不同的写操作,我们的类是实现Serializable接口,自然会跳到writeOrdinaryObject方法去。这里也解释了为什么Serializable接口不存在任何方法,但一定要求implements的原因。
final static byte TC_OBJECT =  (byte)0x73;
private void writeOrdinaryObject(Object obj, ObjectStreamClass desc, boolean unshared) throws IOException {
    ……
    try {
        desc.checkSerialize();
        // 写入TC_OBJECT,表示这是一个新对象
        bout.writeByte(TC_OBJECT);
        // 写入类的元数据
        writeClassDesc(desc, false);
        handles.assign(unshared ? null : obj);
        // 判断类是否实现Externalizable接口进行全部字段自定义序列化
        if (desc.isExternalizable() && !desc.isProxy()) {
            writeExternalData((Externalizable) obj);
        } else {
            writeSerialData(obj, desc);
        }
    } finally {
        if (extendedDebugInfo) {
            debugInfoStack.pop();
        }
    }
}

writeOrdinaryObject方法关键逻辑有:

  1. 在DataOutputStream对象bout中写入0x73,表示这是一个新对象。
  2. 调用writeClassDesc方法,写入类的元数据,这个方法比较重要,等下深入了解。
  3. 判断类是否实现了Externalizable接口进行全部字段自定义序列化,从而决定接下来是调用writeExternalData方法还是writeSerialData方法。

先来看看writeOrdinaryObject方法关键2中writeClassDesc方法,最后会调用到writeNonProxyDesc方法去。

private void writeClassDesc(ObjectStreamClass desc, boolean unshared) throws IOException {
……
    writeNonProxyDesc(desc, unshared);
}
final static byte TC_CLASSDESC =    (byte)0x72;
private void writeNonProxyDesc(ObjectStreamClass desc, boolean unshared) throws IOException {
// 再写入TC_CLASSDESC,表示接下来的数据是一个新的Class描述符
    bout.writeByte(TC_CLASSDESC);
    handles.assign(unshared ? null : desc);

    if (protocol == PROTOCOL_VERSION_1) {
        // do not invoke class descriptor write hook with old protocol
        desc.writeNonProxy(this);
    } else {
// writeClassDescriptor方法也是desc.writeNonProxy方法
        writeClassDescriptor(desc);
    }
    ……
}
protected void writeClassDescriptor(ObjectStreamClass desc) throws IOException {
    desc.writeNonProxy(this);
}

writeNonProxyDesc方法首先再写入了0x72,表示接下来的数据是一个新的Class描述符,然后再调用了ObjectStreamClass的writeNonProxy方法。

ObjectStreamClass.java

void writeNonProxy(ObjectOutputStream out) throws IOException {
    // 写入类的名称
    out.writeUTF(name);
    // 写入类的序列号,通过getSerialVersionUID方法获取或生成serialVersionUID变量的值
    out.writeLong(getSerialVersionUID());

    byte flags = 0;
    // 实现Externalizable接口的标识值
    if (externalizable) {
        flags |= ObjectStreamConstants.SC_EXTERNALIZABLE;
        int protocol = out.getProtocolVersion();
        if (protocol != ObjectStreamConstants.PROTOCOL_VERSION_1) {
            flags |= ObjectStreamConstants.SC_BLOCK_DATA;
        }
    } 
// 实现Serializable接口的标识值
else if (serializable) {
        flags |= ObjectStreamConstants.SC_SERIALIZABLE;
    }
    // 实现Serializable接口且自定义了writeObject()方法进行部分字段自定义序列化
    if (hasWriteObjectData) {
        flags |= ObjectStreamConstants.SC_WRITE_METHOD;
    }
    if (isEnum) {
        flags |= ObjectStreamConstants.SC_ENUM;
    }
    // 写入类的标识值
    out.writeByte(flags);
    // 写入类的字段数
    out.writeShort(fields.length);
    // 写入类的字段信息
    for (int i = 0; i < fields.length; i++) {
        ObjectStreamField f = fields[i];
        out.writeByte(f.getTypeCode());
        out.writeUTF(f.getName());
        if (!f.isPrimitive()) {
            // 如果字段非原始类型(对象或接口),则写入类型字符串
            out.writeTypeString(f.getTypeString());
        }
    }
}
public long getSerialVersionUID() {
    if (suid == null) {
        suid = AccessController.doPrivileged(
            new PrivilegedAction<Long>() {
                public Long run() {
                    return computeDefaultSUID(cl);
                }
            }
        );
    }
    return suid.longValue();
}

如上述注释,writeNonProxy方法写入了类的名称、serialVersionUID字段的值作为序列号、类的标识值、类的字段信息等。其中serialVersionUID字段的值如果不存在则通过类的信息进行计算出类的默认UID值作为序列号值。

回到writeOrdinaryObject方法关键3中,通过判断是否实现Externalizable接口,然后决定接下来调用的writeExternalData方法和writeSerialData方法。

ObjectOutputStream.java

private void writeExternalData(Externalizable obj) throws IOException {
    ……
    try {
        curContext = null;
        if (protocol == PROTOCOL_VERSION_1) {
            obj.writeExternal(this);
        } else {
            bout.setBlockDataMode(true);
            // 调用类中的writeExternal方法进行自定义序列化
            obj.writeExternal(this);
            bout.setBlockDataMode(false);
            // 再写入TC_ENDBLOCKDATA,表示数据读取完毕
            bout.writeByte(TC_ENDBLOCKDATA);
        }
    } finally {
        ……
    }
……
}

writeExternalData方法所实现的便是上面“全部字段自定义序列化”的步骤,它会调用到Externalizable接口要求实现的writeExternal方法的逻辑。

private void writeSerialData(Object obj, ObjectStreamClass desc) throws IOException {
    // 返回序列化对象及其父类的的数据布局ClassDataSlot实例数组,ClassDataSlot按继承顺序排列,数组中最高的超类为首位,本类为末位。
    ObjectStreamClass.ClassDataSlot[] slots = desc.getClassDataLayout();
    for (int i = 0; i < slots.length; i++) {
        ObjectStreamClass slotDesc = slots[i].desc;
        // 判断是否包含writeObject方法
        if (slotDesc.hasWriteObjectMethod()) {
            // ……
            try {
                curContext = new SerialCallbackContext(obj, slotDesc);
                bout.setBlockDataMode(true);
                // 调用类中的writeObject方法实现上面“部分字段自定义序列化”的逻辑
                slotDesc.invokeWriteObject(obj, this);
                bout.setBlockDataMode(false);
                // 再写入TC_ENDBLOCKDATA,表示数据读取完毕
                bout.writeByte(TC_ENDBLOCKDATA);
            } finally {
                ……
            }
        ……
        } else {
            // 实现自动默认的序列化逻辑
            defaultWriteFields(obj, slotDesc);
        }
    }
}

writeSerialData方法关键逻辑有:

  1. 调用getClassDataLayout方法获取序列化对象及其父类的的数据布局ClassDataSlot实例数组,ClassDataSlot按继承顺序排列,数组中最高的超类为首位,本类为末位。
  2. 判断类中如果包含writeObject方法(上面实现的“部分字段自定义序列化”的writeObject方法),则调用。
  3. 如果类中不包含writeObject方法,则调用defaultWriteFields方法进行自动默认的序列化逻辑。其实我们在上面实现的“部分字段自定义序列化”的writeObject方法中也会首先调用了defaultWriteFields()。
private void defaultWriteFields(Object obj, ObjectStreamClass desc) throws IOException {
    Class<?> cl = desc.forClass();
    ……
    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;
    desc.getObjFieldValues(obj, objVals);
    // 写入非基本数据类型的数据
    for (int i = 0; i < objVals.length; i++) {
        ……
        try {
            // 递归调用writeObject0
            writeObject0(objVals[i], fields[numPrimFields + i].isUnshared());
        } finally {
            if (extendedDebugInfo) {
                debugInfoStack.pop();
            }
        }
    }
}

defaultWriteFields方法关键逻辑有:

  1. 写入基本数据类型的数据
  2. 写入非基本数据类型的数据,其中会再次递归调用writeObject0方法进行重复的步骤。

7 反序列化原理

反序列化的过程几乎是跟序列化相对应,需要先创建ObjectInputStream对象,然后调用其readObject方法。

7.1 ObjectInputStream构造

ObjectInputStream.java

public ObjectInputStream(InputStream in) throws IOException {
    verifySubclass();
    // 创建一个DataInputStream对象bout,用于表示底层块数据输入流容器
    bin = new BlockDataInputStream(in);
    ……
    // 读取文件头信息,判断是否STREAM_MAGIC和STREAM_VERSION
    readStreamHeader();
    ……
}
protected void readStreamHeader() throws IOException, StreamCorruptedException {
    short s0 = bin.readShort();
    short s1 = bin.readShort();
    if (s0 != STREAM_MAGIC || s1 != STREAM_VERSION) {
        throw new StreamCorruptedException(String.format("invalid stream header: %04X%04X", s0, s1));
    }
}

7.2 readObject方法读取数据

ObjectInputStream.java

public final Object readObject() throws IOException, ClassNotFoundException {
    ……
    try {
        Object obj = readObject0(false);
        ……
        return obj;
    } finally {
        ……
    }
}
private Object readObject0(boolean unshared) throws IOException {
    ……
    byte tc;
    while ((tc = bin.peekByte()) == TC_RESET) {
        bin.readByte();
        handleReset();
    }
    ……
    try {
        switch (tc) {
            ……
            case TC_OBJECT:
                return checkResolve(readOrdinaryObject(unshared));
            ……
        }
    } finally {
        ……
    }
}

我们知道,在序列化时,判断对象实现了Serializable接口后调用了writeOrdinaryObject方法,方法内为了表示这是一个新对象写入了TC_OBJECT,所以switch中这里将会匹配到TC_OBJECT,然后调用readOrdinaryObject方法。

private Object readOrdinaryObject(boolean unshared) throws IOException {
    ……
    // 当初通用writeClassDesc方法写入类的元数据,现在是读取类的元数据
    // 里面还调用了ObjectStreamClass的initNonProxy方法,校验SerialVersionUID是否相等便是在这里
    ObjectStreamClass desc = readClassDesc(false);
    desc.checkDeserialize();

    Class<?> cl = desc.forClass();
    ……
    // 创建要返回的对象
    Object obj;
    try {
        obj = desc.isInstantiable() ? desc.newInstance() : null;
    } catch (Exception ex) {
        ……
    }
    ……
    // 判断类是否实现Externalizable接口进行全部字段自定义反序列化
    if (desc.isExternalizable()) {
        readExternalData((Externalizable) obj, desc);
    } else {
        readSerialData(obj, desc);
    }
    ……
    return obj;
}

readOrdinaryObject方法对应ObjectOutputStream#writeOrdinaryObject方法,该方法内关键逻辑有:

  1. 通过readClassDesc方法读取类的元数据,这也是对应ObjectOutputStream#writeClassDesc方法。readClassDesc方法里面还调用了ObjectStreamClass的initNonProxy方法,校验SerialVersionUID是否相等便是在这里。关于SerialVersionUID的逻辑可以参考上面的ObjectStreamClass的一些逻辑和处行查阅相关代码,这里不打算贴代码。
  2. 创建要返回的对象。
  3. 判断类是否实现Externalizable接口进行全部字段自定义反序列化,从而决定接下来是调用readExternalData方法还是readSerialData方法。
private void readExternalData(Externalizable obj, ObjectStreamClass desc) throws IOException {
    ……
    try {
        ……
        if (obj != null) {
            try {
                obj.readExternal(this);
            } catch (ClassNotFoundException ex) {
                ……
            }
        }
        ……
    } finally {
        ……
    }
}

readExternalData方法所实现的便是上面“全部字段自定义序列化”的步骤,它会调用到Externalizable接口要求实现的readExternal方法的逻辑。

private void readSerialData(Object obj, ObjectStreamClass desc) throws IOException {
     // 返回序列化对象及其父类的的数据布局ClassDataSlot实例数组,ClassDataSlot按继承顺序排列,数组中最高的超类为首位,本类为末位。
    ObjectStreamClass.ClassDataSlot[] slots = desc.getClassDataLayout();
    for (int i = 0; i < slots.length; i++) {
        ObjectStreamClass slotDesc = slots[i].desc;

        if (slots[i].hasData) {
            if (obj == null || handles.lookupException(passHandle) != null) {
                defaultReadFields(null, slotDesc);
            } 
            // 判断是否包含readObject方法
            else if (slotDesc.hasReadObjectMethod()) {
                ……
                try {
                    curContext = new SerialCallbackContext(obj, slotDesc);

                    bin.setBlockDataMode(true);
                    // 调用类中的readObject方法实现上面“部分字段自定义序列化”的逻辑
                    slotDesc.invokeReadObject(obj, this);
                } catch (ClassNotFoundException ex) {
                    handles.markException(passHandle, ex);
                } finally {
                    ……
                }
                defaultDataEnd = false;
            } else {
                // 实现自动默认的反序列化逻辑
                defaultReadFields(obj, slotDesc);
            }
            ……
        }
……
    }
}

readSerialData方法关键逻辑有:

  1. 调用getClassDataLayout方法获取序列化对象及其父类的的数据布局ClassDataSlot实例数组,ClassDataSlot按继承顺序排列,数组中最高的超类为首位,本类为末位。
  2. 判断类中如果包含readObject方法(上面实现的“部分字段自定义序列化”的readObject方法),则调用。
  3. 如果类中不包含readObject方法,则调用defaultReadFields方法进行自动默认的反序列化逻辑。其实我们在上面实现的“部分字段自定义序列化”的readObject方法中也会首先调用了defaultReadFields()。
private void defaultReadFields(Object obj, ObjectStreamClass desc) throws IOException {
    Class<?> cl = desc.forClass();
    ……
    int primDataSize = desc.getPrimDataSize();
    if (primVals == null || primVals.length < primDataSize) {
        primVals = new byte[primDataSize];
    }
    // 读取基本数据类型的数据
    bin.readFully(primVals, 0, primDataSize, false);
    if (obj != null) {
        desc.setPrimFieldValues(obj, primVals);
    }

    int objHandle = passHandle;
    ObjectStreamField[] fields = desc.getFields(false);
    Object[] objVals = new Object[desc.getNumObjFields()];
    int numPrimFields = fields.length - objVals.length;
    // 读取非基本数据类型的数据
    for (int i = 0; i < objVals.length; i++) {
        ObjectStreamField f = fields[numPrimFields + i];
        // 递归调用readObject0
        objVals[i] = readObject0(f.isUnshared());
        if (f.getField() != null) {
            handles.markDependency(objHandle, passHandle);
        }
    }
    if (obj != null) {
        desc.setObjFieldValues(obj, objVals);
    }
    passHandle = objHandle;
}

defaultReadFields方法关键逻辑有:

  1. 读取基本数据类型的数据
  2. 读取非基本数据类型的数据,其中会再次递归调用readObject0方法进行重复的步骤。

8 总结

  1. 通过实现 Serializable 接口的类,可以简单地将其对象进行字节流的形式序列化和反序列化。序列化就是将 Object 通过 ObjectOutputStream 的 writeObject 方法转化为输出流的过程,而反序列化则是将输入流通过 ObjectInputStream 的 readObject 方法还原成一个新的 Object 的过程。
  2. 使用 Serializable 时建议主动声明一个私有且静态的 serialVersionUID 变量用于序列化标识,它用于区别序列化和反序列化数据结构是否发生变化,如果不主动声明也是会自动生成,但是可能会根据编译环境差异导致前后不一致而导致意外错误发生。若要进行跨应用传递数据使用 Serializable, 则一定要进行 serialVersionUID 变量主动声明,因为编译环境或者代码混淆、开启R8等代码细节上的差异都会有可能导致反序列化时发生意外的异常。
  3. Externalizabler 接口本身继承于 Serializable 接口,如果要进行所有字段自定义序列化和反序列化可以使用Externalizable 接口替换 Serializable 接口,并实现 writeExternal 和 readExternal 方法。
  4. 如果实现 Serializable 接口的类内增加 writeObject 方法和 readObject 方法也可支持字段自定义序列化,不过一般是配合 transient 使用来进行部分字段自定义序列化。
  5. 类字段前加上 transient  修饰符可在序列化和反序列化过程中忽略这些字段,也可以通过 writeObject 方法和 readObject 方法进行这些忽略的字段的自定义序列化。

  • 0
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值