Serializable和Parcelable序列化

前言

Android中常用的序列化方式包含有两种: Serializable和Parcelable。其中Serializable是java中通用的对象序列化方法,在Android实际内存操作时会更加偏向于实现Parcelable接口。

一、序列化和反序列化

序列化

由于存在内存中的对象都是暂时的,无法长期贮存,为了把对象的状态保持下来,这是需要把对象写入到磁盘中或者其他介质中,这个过程就叫做序列化。

反序列化

是序列化的反向操作。

概括性来说序列化是指将对象实例的状态存储到存储媒体(磁盘或者其他介质)的过程。在此过程中,先将对象的公共字段和私有字段以及类的名称(包括类所在的程序集)转换为字节流,然后再把字节流写入数据流。在随后对对象进行反序列化时,将创建出与原对象完全相同的副本。

二、序列化应用场景

主要有以下情况(但不限于以下情况)
1)内存中的对象写入到硬盘;
2)数据传输(如Intent,Binder)

三、序列化流程

一个对象要实现序列化操作,该类就必须实现Serializable接口或者Parcelable接口,其中Serializable接口是在java中的序列化抽象类,而Parcelable接口则是android中特有的序列化接口,在某些情况下,Parcelable接口实现的序列化更为高效。

1. Serializable

在类声明中实现Serializable接口即可,建议可以主动声明序列化标识ID。标识ID是可以自动生成的,主动声明只是为了更高的保证反序列化正常。

序列化标识ID使用标识序列化类的,只有序列化标识一致,才能够正常的被反序列化。序列化和反序列化的ID必须相同才能欧股使序列化操作成功。

  1. 如果不去手动指定serialVersionUID的值,则编译器会根据当前的类结构计算出hash值作为UID
  2. 如果没有手动指定serialVersionUID的值,反序列化是当前的类有所改变,则计算出来的hash值就会不一致,就会使得当前的反序列化报错crash。
  3. 如果类结构发生了非常规性改变,例如修改了类名,修改了成员变量类型,这个时候即使UID验证通过了,反序列化也会报错,无法还原出一个新类型的结构对象。

示例代码

// 构造对象
        User user = new User();
        user.setId(1000);
        user.setName("张三");

        // 把对象序列化到文件
        ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("/serializable/user.txt"));
        oos.writeObject(user);
        oos.close();

        // 反序列化到内存
        ObjectInputStream ois = new ObjectInputStream(new FileInputStream("/serializable/user.txt"));
        User userBack = (User) ois.readObject();
        System.out.println("read serializable user:id=" + userBack.getId() + ", name=" + userBack.getName());
        ois.close();

只需要采用ObjectOutStream和ObjectInputStream即可实现对象的序列化和反序列化操作。

反序列化后的对象是新创建的,和原对象不是同一个。

2. Parcelable

Parcelable是Android专用的序列化,内存上略优于Serializable,开销较小。

public class User implements Parcelable {

    public int id;
    public String name;
    public User friend;

    /**
     * 当前对象的内容描述,一般返回0即可
     */
    @Override
    public int describeContents() {
        return 0;
    }

    /**
     * 将当前对象写入序列化结构中
     */
    @Override
    public void writeToParcel(Parcel dest, int flags) {
        dest.writeInt(this.id);
        dest.writeString(this.name);
        dest.writeParcelable(this.friend, 0);
    }

    public NewClient() {}

    /**
     * 从序列化后的对象中创建原始对象
     */
    protected NewClient(Parcel in) {
        this.id = in.readInt();
        this.name = in.readString();
       //friend是另一个序列化对象,此方法序列需要传递当前线程的上下文类加载器,否则会报无法找到类的错误
       this.friend=in.readParcelable(Thread.currentThread().getContextClassLoader());
    }

    /**
     * public static final一个都不能少,内部对象CREATOR的名称也不能改变,必须全部大写。
     * 重写接口中的两个方法:
     * createFromParcel(Parcel in) 实现从Parcel容器中读取传递数据值,封装成Parcelable对象返回逻辑层,
     * newArray(int size) 创建一个类型为T,长度为size的数组,供外部类反序列化本类数组使用。
     */
    public static final Parcelable.Creator CREATOR = new Parcelable.Creator() {
        /**
         * 从序列化后的对象中创建原始对象
         */
        @Override
        public User createFromParcel(Parcel source) {
            return new User(source);
        }

        /**
         * 创建指定长度的原始对象数组
         */
        @Override
        public User[] newArray(int size) {
            return new User[size];
        }
    };
}

其实通过Intent传递复杂类型(如自定义引用类型数据)的数据时就需要使用Parcelable对象,如下是日常应用中Intent关于Parcelable对象的一些操作方法,引用类型必须实现Parcelable接口才能通过Intent传递,而基本数据类型,String类型则可直接通过Intent传递而且Intent本身也实现了Parcelable接口,所以可以轻松地在组件间进行传输。

四、两者的区别

1.实现差异

Serializable只需要实现Serializable接口即可,Parcelable需要实现Parcelable,,还要添加静态成员变量CREATOR,这个变量需要实现Creator接口,并实现读写的抽象方法。

2.两者的设计初衷

Serializable主要为了序列化对象到本地文件、数据库、网络流等。

Parcelable主要是因为Android中数据传递主要是在内存环境中,因此Parcelable的出现为了满足数据在内存中低开销而且高效的传递问题。

3. 应用场景

Parcelable是直接在内存中读写,我们知道内存的读写速度肯定优于硬盘读写速度,所以Parcelable序列化方式性能上要优于Serializable方式很多。所以Android应用程序在内存间数据传输时推荐使用Parcelable,如activity间传输数据和AIDL数据传递。

Serializable使用IO读写存储在硬盘上。序列化过程使用了反射技术,并且期间产生临时对象,优点代码少,在将对象序列化到存储设置中或将对象序列化后通过网络传输时建议选择Serializable。

你可能感兴趣的:(面试,基础知识,java,android)