Serializable & Parcelable 原理和区别

Posted 安卓开发-顺

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Serializable & Parcelable 原理和区别相关的知识,希望对你有一定的参考价值。

目录

目录

前言

一、认识 Serializable 和 Parcelable

Serializable

Parcelable

二、Serializable 和 Parcelable工作原理

Serializable工作原理

实战代码演示:

serialVersionUID        

Externalizable 接口

注意事项 

Parcelable工作原理

实战代码

总结

性能对比:


 

前言

        Serializable和Parcelable都是序列化相关的接口,所以学习之前,先简单描述下什么是序列化和反序列化。

        一个中国人碰到一个美国人,中国人写了个一二三,美国人写了个one two three,结果双方都看不懂,于是中国人写了123,美国人也写了个123,结果双方都看懂了。这个变成123的过程可以理解为序列化,123变成一二三或one two three的过程可以理解为反序列化。

        上面的小故事虽然简陋点,但是大概说明了序列化的本质:就是通过一套通用的协议让不同进程、不同系统、不同平台、不同语言之间进行通信。


一、认识 Serializable 和 Parcelable

Serializable

        java提供的序列化接口,实现这个接口仅仅是表示当前对象可以被序列化,真正要实现当前对象的序列化和反序列化工作,需要通过ObjectOutputStream(序列化) 和 ObjectInputStream (反序列化)来实现(这步操作我们很少自己去实现,大多数场景系统内部都封装处理了)。android中常见应用场景有:网络传输、数据持久化处理、跨进程通信(性能不如Parcelable)等。

Parcelable

        Android提供的序列化接口,实现这个接口并实现writeToParcel、describeContents方法,在定义一个静态内部对象CREATOR(类型是Parcelable.Creator)并实现Creator的createFromParcel、newArray接口,就可以完成序列化和反序列化工作。Android中常见应用场景有:跨进程通信。

二、Serializable 和 Parcelable工作原理

Serializable工作原理

第一步通过ObjectInputStream 的writeObject 进行序列化操作:

/**
 * 通过调用 writeObject 方法来把我们的对象序列化成二进制字节流
 */    
public final void writeObject(Object obj) throws IOException 
        if (enableOverride) 
            writeObjectOverride(obj);
            return;
        
        try 
            //常规情况走这里
            writeObject0(obj, false);
         catch (IOException ex) 
            if (depth == 0) 
                writeFatalException(ex);
            
            throw ex;
        


... 继续往下

private void writeObject0(Object obj, boolean unshared)
        throws IOException 

        try 
           
            ... 省略若干代码
            for (;;) 
               
                //这里很重要,会通过反射来查找外部是否自定义了
                //readObject、writeObject、readResolve、writeReplace方法,
                //如果有定义,走到后面就会执行外部定义的方法,不在执行默认的这些方法
                desc = ObjectStreamClass.lookup(cl, true);
                ... 省略若干代码
            
           
            ... 省略若干代码
            // remaining cases
            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);
        
    


... 继续往下
private void writeOrdinaryObject(Object obj, ObjectStreamClass desc,boolean unshared)
        throws IOException 
        ...
        try 
            ...
            if (desc.isExternalizable() && !desc.isProxy()) 
                //如果我们的对象实现了Externalizable接口,走这里
                writeExternalData((Externalizable) obj);
             else 
                //如果我们的对象实现的是Serializable接口,走这里
                writeSerialData(obj, desc);
            
         finally 
            if (extendedDebugInfo) 
                debugInfoStack.pop();
            
        
    

... 继续往下 看writeSerialData方法
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;
            if (slotDesc.hasWriteObjectMethod()) 
               ...
                这里前面说过,会通过反射来查找外部是否自定义了
                //readObject、writeObject、readResolve、writeReplace方法,
                //如果有定义就走这里了
                slotDesc.invokeWriteObject(obj, this);
               ...
             else 
                //我们自己没定义这四个固定的方法就默认走这里
                defaultWriteFields(obj, slotDesc);
            
        
    

... 继续往下 看defaultWriteFields方法
private void defaultWriteFields(Object obj, ObjectStreamClass desc)
        throws IOException
    
        ... 
        //正真序列化的地方
        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(objVals[i],
                             fields[numPrimFields + i].isUnshared());
             finally 
                if (extendedDebugInfo) 
                    debugInfoStack.pop();
                
            
        
    

 第二步通过ObjectInputStream 的readObject 进行序列化操作: 这一步的源码和第一步源码思路非常像,这里不在分析了。

实战代码演示:

import java.io.Serializable;

/**
 * Serializable 测试类
 */
public class SerializableTest implements Serializable 
    /**
     * serialVersionUID 用来表明类的版本,如果反序列化时此版本和序列化时的版本不一致就会报错InvalidCastException
     * 如果这里我们不写 系统会自动生成一个serialVersionUID,此时我们的类做任何更改这个uid都会变,这一点有时候不符合我们的需求
     * 因为我们有时候希望类修改时会兼容老版本,而不是只要不一样了就不能正常序列化 所以建议显示的自己定义一个serialVersionUID
     * 给它一个默认值就好了,当新版本的类无法兼容老版本的时候就主动修改这个值即可
     */
    private static final long serialVersionUID = 1L;
    private String name;
    private int age;

    public SerializableTest(String name, int age) 
        this.name = name;
        this.age = age;
    

    public String getName() 
        return name;
    

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

    public int getAge() 
        return age;
    

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


public class SerializableMain 
    public static void main(String[] args) 

        SerializableTest test = new SerializableTest("hello", 10);

        try 
            ByteArrayOutputStream out = new ByteArrayOutputStream();
            ObjectOutputStream outputStream = new ObjectOutputStream(out);
            //序列化
            outputStream.writeObject(test);
            byte[] bytes = out.toByteArray();

            //反序列化
            ByteArrayInputStream in = new ByteArrayInputStream(bytes);
            ObjectInputStream inputStream = new ObjectInputStream(in);
            SerializableTest test1 = (SerializableTest) inputStream.readObject();
            System.out.println(test1.getName() + " -- " + test1.getAge());
         catch (IOException | ClassNotFoundException e) 
            e.printStackTrace();
        

    


日志打印:hello -- 10

serialVersionUID
        

        serialVersionUID 用来表明类的版本,如果反序列化时此版本和序列化时的版本不一致就会报错InvalidCastException。
        如果我们不写, 系统会自动生成一个serialVersionUID,此时我们的类做任何更改这个uid都会自动变,这一点有时候不符合我们的需求, 因为我们有时候希望类修改时会兼容老版本,而不是只要不一样了就不能正常序列化 所以建议显示的自己定义一个serialVersionUID,给它一个默认值就好了,当新版本的类无法兼容老版本的时候就主动修改这个值即可

Externalizable 接口

Externalizable接口是Serializable接口的子类。如果你想自己主动控制对象中哪些属性要序列化,就实现这个接口然后覆盖这个接口的writeExternal()和readExternal()方法,来自定义实现就可以了。

    @Override
    public void writeExternal(ObjectOutput out) throws IOException 
        out.writeObject(name);
        out.writeInt(age);
    

    @Override
    public void readExternal(ObjectInput in) throws ClassNotFoundException, IOException 
        name = (String) in.readObject();
        age = in.readInt();
    

注意事项 

  • 单例模式失效问题

    由于反序列化过程是一个“深拷贝过程” 把对象反序列化出来后会执行其无参构造方法创建一个示例,因此单例模式会失效。

       解决办法:在单例类中定义一个readResolve方法 直接返回当前示例对象即可 

private Object readResolve() 
   return instance;
  • 写入过程中更改对象属性值的问题

public static void main(String[] args) 

        SerializableTest test = new SerializableTest("hello", 10);

        try 
            ByteArrayOutputStream out = new ByteArrayOutputStream();
            ObjectOutputStream outputStream = new ObjectOutputStream(out);
            //序列化
            outputStream.writeObject(test);
            test.setAge(15);
            
            //如果writeObject以后又改变了对象的值 就要调用reset方法 然后重新writeObject
            outputStream.reset();
            outputStream.writeObject(test);

            //或者 直接调用一下writeUnshared
//            outputStream.writeUnshared(test);
            byte[] bytes = out.toByteArray();

            //反序列化
            ByteArrayInputStream in = new ByteArrayInputStream(bytes);
            ObjectInputStream inputStream = new ObjectInputStream(in);
            SerializableTest test1 = (SerializableTest) inputStream.readObject();
            System.out.println(test1.getName() + " -- " + test1.getAge());
         catch (IOException | ClassNotFoundException e) 
            e.printStackTrace();
        

    
  • 不是所有属性都会序列化

    (1)用transient关键字标记的成员变量不参与序列化(在被反序列化后,transient 变量的值被

    设为初始值,如 int 型的是 0,对象型的是 null)

    (2)静态成员变量属于类不属于对象,所以不会参与序列化(对象序列化保存的是对象的“状态”,也就是它的成员变量,因此序列化不会关注静态变量)

Parcelable工作原理

        真正完成序列化和反序列化操作的类是Parcel 这个类,Parcel 的核心原理,简单来说就是它提供了一套机制,可以将序列化之后的数据写入到一个共享内存中,其他进程通过Parcel可以从这块共享内存中读出字节流,并反序列化成对象,读写过程都是native方法实现的,下图是这个过程的模型。

实战代码

public class ParcelableTest implements Parcelable 
    private String name;
    private int age;

    protected ParcelableTest(Parcel in) 
        name = in.readString();
        age = in.readInt();
    

    /**
     * 将对象转换成一个 Parcel 对象
     * @param dest 表示要写入的 Parcel 对象
     * @param flags 示这个对象将如何写入
     */
    @Override
    public void writeToParcel(Parcel dest, int flags) 
        dest.writeString(name);
        dest.writeInt(age);
    
    
    /**
     * 实现类必须有一个 Creator 属性,用于反序列化,将 Parcel 对象转换为 Parcelable
     * @param <T>
     */
    public static final Creator<ParcelableTest> CREATOR = new Creator<ParcelableTest>() 

        //反序列化的方法,将Parcel还原成Java对象
        @Override
        public ParcelableTest createFromParcel(Parcel in) 
            return new ParcelableTest(in);
        

        //提供给外部类反序列化这个数组使用。
        @Override
        public ParcelableTest[] newArray(int size) 
            return new ParcelableTest[size];
        
    ;

    /**
     * 描述当前 Parcelable 实例的对象类型 目前就两个值 一个 0 一个 1(ParcelFileDescriptor.CONTENTS_FILE_DESCRIPTOR)
     * 如果对象中有文件描述符,就返回1
     * 我们默认情况 返回 0就可以了
     * @return
     */
    @Override
    public int describeContents() 
        return 0;
    

总结

性能对比:

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

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

如何选择:

        1、在使用内存方面Parcelable比Serializable性能高,所以推荐使用Parcelable。

        2、在数据持久化方面建议使用Serializable,因为Parcelable不能很好的保证数据的持续性,使用相对复杂,也没有明显性能优势。

以上是关于Serializable & Parcelable 原理和区别的主要内容,如果未能解决你的问题,请参考以下文章

Parcel & Svelte:用 Sass 设计标签?

Parcelable, Serializable,Cloneable,copyProperties

Android:使用Parcelable接口在跳转Activity时传递数据以及错误信息 Parcel: unable to marshal value 的解决

Android:使用Parcelable接口在跳转Activity时传递数据以及错误信息 Parcel: unable to marshal value 的解决

我的代码框上带有 react & parcel 的“目标容器不是 DOM 元素”错误

Android -- 每日一问:Parcelable 和 Serializable 有什么用,它们有什么差别?