两种序列化:Serializable与Parcelable

什么是序列化

  1. Java序列化是指把Java对象转换为字节序列的过程。而Java反序列化是指把字节序列恢复为Java对象的过程;

  2. 序列化:对象序列化的最主要的用处就是在传递和保存对象的时候,保证对象的完整性可传递性。序列化是把对象转换成有序字节流,以便在网络上传输或者保存在本地文件中。序列化后的字节流保存了Java对象的状态以及相关的描述信息。序列化机制的核心作用就是对象状态的保存与重建。

  3. 反序列化:客户端从文件中或网络上获得序列化后的对象字节流后,根据字节流中所保存的对象状态及描述信息,通过反序列化重建对象。

  4. 本质上讲,序列化就是把实体对象状态按照一定的格式写入到有序字节流,反序列化就是从有序字节流重建对象,恢复对象状态。

为什么要序列化?

当两个进程进行远程通信时,可以相互发送各种类型的数据,包括文本、图片、音频、视频等, 而这些数据都会以二进制序列的形式进行传送。

那么当两个Java进程进行通信时,能否实现进程间的对象传送呢?答案是可以的!如何做到呢?这就需要Java序列化与反序列化了!

换句话说,一方面,发送方需要把这个Java对象转换为字节序列,然后在网络上传送;另一方面,接收方需要从字节序列中恢复出Java对象。

当我们明晰了为什么需要Java序列化和反序列化后,我们很自然地会想Java序列化的好处。其好处一是实现了数据的持久化,通过序列化可以把数据永久地保存到硬盘上(通常存放在文件里),二是,利用序列化实现远程通信,即在网络上传送对象的字节序列。

总的来说可以归结为以下几点:

  1. 永久性保存对象,保存对象的字节序列到本地文件或者数据库中;
  2. 通过序列化以字节流的形式使对象在网络中进行传递和接收;
  3. 通过序列化在进程间传递对象;

序列化算法做了什么

  1. 将对象实例相关的类元数据输出。
  2. 递归地输出类的超类描述直到不再有超类。
  3. 类元数据完了以后,开始从最顶层的超类开始输出对象实例的实际数据值。
  4. 从上至下递归输出实例的数据

Serializable

介绍Serializable

Serializable 是 Java 提供的序列化接口,它是一个空接口:

public interface Serializable {
}

Serializable 用来标识当前类可以被 ObjectOutputStream 序列化,以及被 ObjectInputStream 反序列化。

Serializable 的特点:
  • 可序列化类中,未实现 Serializable 的属性状态无法被序列化/反序列化
  • 也就是说,反序列化一个类的过程中,它的非可序列化的属性将会调用无参构造函数重新创建
  • 因此这个属性的无参构造函数必须可以访问,否者运行时会报错
  • 一个实现序列化的类,它的子类也是可序列化的
用Serializable 实现序列化的步骤:
  1. 在需要序列化的class增加implements Serializable
  2. 增加一个唯一个序列化id:
private static final long serialVersionUID = 1L; 
serialVersionUID

从名字就可以看出来,这个 serialVersionUID ,有些类似我们平时的接口版本号,在运行时这个版本号唯一标识了一个可序列化的类。

也就是说,一个类序列化时,运行时会保存它的版本号,然后在反序列化时检查你要反序列化成的对象版本号是否一致,不一致的话就会报错:InvalidClassException

如果我们不自己创建这个版本号,序列化过程中运行时会根据类的许多特点计算出一个默认版本号。然而只要你对这个类修改了一点点,这个版本号就会改变。这种情况如果发生在序列化之后,反序列化时就会导致上面说的错误。

因此 JVM 规范强烈 建议我们手动声明一个版本号,这个数字可以是随机的,只要固定不变就可以。同时最好是 private 和 final 的,尽量保证不变

此外,序列化过程中不会保存 statictransient 修饰的属性。
前者很好理解,因为静态属性是属于类管理的,不属于对象状态;
而后者则是 Java 的关键字,专门用来标识不序列化的属性。

默认实现 Serializable 不会自动创建 serialVersionUID 属性,为了提示我们及时创建 serialVersionUID ,可以在AS设置中搜索 serializable 然后选择下图所示的几个选项,为那些没有声明 serialVersionUID 属性的类以及内部类添加一个警告。

Serializable的序列化与反序列化

Serializable 的序列化与反序列化分别通过 ObjectOutputStreamObjectInputStream 进行,实例代码如下:

/**
 * 序列化对象
 */
synchronized public static boolean saveObject(Object obj, String path) {
    if (obj == null) {
        return false;
    }
    ObjectOutputStream oos = null;
    try {
        oos = new ObjectOutputStream(new FileOutputStream(path));
        oos.writeObject(obj);
        oos.close();
        return true;
    } catch (IOException e) {
        e.printStackTrace();
    } finally {
        if (oos != null) {
            try {
                oos.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
    return false;
}

/**
 * 反序列化对象
 */
@SuppressWarnings("unchecked ")
synchronized public static <T> T readObject(String path) {
    ObjectInputStream ojs = null;
    try {
        ojs = new ObjectInputStream(new FileInputStream(path));
        return (T) ojs.readObject();
    } catch (IOException | ClassNotFoundException e) {
        e.printStackTrace();
    } finally {
        close(ojs);
    }
    return null;
}

Parcelable

介绍

Parcelable是android特有的序列化API,它的出现是为了解决Serializable在序列化的过程中消耗资源严重的问题,但是因为本身使用需要手动处理序列化和反序列化过程,会与具体的代码绑定,使用较为繁琐,一般只获取内存数据的时候使用。

Parcelable依赖于Parcel,Parcel的意思是包装,实现原理是在内存中建立一块共享数据块,序列化和反序列化均是操作这一块的数据,如此来实现。

Parcelable接口

public interface Parcelable {
    //writeToParcel() 方法中的参数,用于标识当前对象作为返回值返回
    //有些实现类可能会在这时释放其中的资源
    public static final int PARCELABLE_WRITE_RETURN_VALUE = 0x0001;

    //writeToParcel() 方法中的第二个参数,它标识父对象会管理内部状态中重复的数据
    public static final int PARCELABLE_ELIDE_DUPLICATES = 0x0002;

    //用于 describeContents() 方法的位掩码,每一位都代表着一种对象类型
    public static final int CONTENTS_FILE_DESCRIPTOR = 0x0001;

    //描述当前 Parcelable 实例的对象类型
    //比如说,如果对象中有文件描述符,这个方法就会返回上面的 CONTENTS_FILE_DESCRIPTOR
    //其他情况会返回一个位掩码
    public int describeContents();

    //将对象转换成一个 Parcel 对象
    //参数中 dest 表示要写入的 Parcel 对象
    //flags 表示这个对象将如何写入
    public void writeToParcel(Parcel dest, int flags);

    //实现类必须有一个 Creator 属性,用于反序列化,将 Parcel 对象转换为 Parcelable 
    public interface Creator<T> {

        public T createFromParcel(Parcel source);

        public T[] newArray(int size);
    }

    //对象创建时提供的一个创建器
    public interface ClassLoaderCreator<T> extends Creator<T> {
        //使用类加载器和之前序列化成的 Parcel 对象反序列化一个对象
        public T createFromParcel(Parcel source, ClassLoader loader);
    }
}

实现了 Parcelable 接口的类在序列化和反序列化时会被转换为 Parcel 类型的数据 。

Parcel 是一个载体,它可以包含数据或者对象引用,然后通过 IBinder 在进程间传递。

实现 Parcelable 接口的类必须有一个 CREATOR 类型的静态变量,下面是一个实例:

public class ParcelableGroupBean implements Parcelable {

    private String mName;
    private List<String> mMemberNameList;
    private User mUser;

    /**
     * 需要我们手动创建的构造函数
     * @param name
     * @param memberNameList
     * @param user
     */
    public ParcelableGroupBean(String name, List<String> memberNameList, User user) {
        mName = name;
        mMemberNameList = memberNameList;
        mUser = user;
    }

    /**
     * 1.内容描述
     * @return
     */
    @Override
    public int describeContents() {
        //几乎都返回 0,除非当前对象中存在文件描述符时为 1
        return 0;
    }

    /**
     * 2.序列化
     * @param dest
     * @param flags 0 或者 1
     */
    @Override
    public void writeToParcel(Parcel dest, int flags) {
        dest.writeString(mName);
        dest.writeStringList(mMemberNameList);
        dest.writeParcelable(mUser, flags);
    }

    /**
     * 3.反序列化
     */
    public static final Creator<ParcelableGroupBean> CREATOR = new Creator<ParcelableGroupBean>() {
        /**
         * 反序列创建对象
         * @param in
         * @return
         */
        @Override
        public ParcelableGroupBean createFromParcel(Parcel in) {
            return new ParcelableGroupBean(in);
        }

        /**
         * 反序列创建对象数组
         * @param size
         * @return
         */
        @Override
        public ParcelableGroupBean[] newArray(int size) {
            return new ParcelableGroupBean[size];
        }
    };

    /**
     * 4.自动创建的的构造器,使用反序列化得到的 Parcel 构造对象
     * @param in
     */
    protected ParcelableGroupBean(Parcel in) {
        mName = in.readString();
        mMemberNameList = in.createStringArrayList();
        //反序列化时,如果熟悉也是 Parcelable 的类,需要使用它的类加载器作为参数,否则报错无法找到类
        mUser = in.readParcelable(User.class.getClassLoader());
    }

}

对比

  1. 作用
    Serializable的作用是为了保存对象的属性到本地文件、数据库、网络流、rmi以方便数据传输,当然这种传输可以是程序内的也可以是两个程序间的。
    而Android的Parcelable的设计初衷是因为Serializable效率过慢,为了在程序内不同组件间以及不同Android程序间(AIDL)高效的传输数据而设计,这些数据仅在内存中存在,Parcelable是通过IBinder通信的消息的载体。
  1. 效率及选择
    Parcelable的性能比Serializable好,在内存开销方面较小,所以在内存间数据传输时推荐使用Parcelable,如activity间传输数据,
    Serializable可将数据持久化方便保存,所以在需要保存或网络传输数据时选择Serializable,因为android不同版本Parcelable可能不同,所以不推荐使用Parcelable进行数据持久化
  1. 编程实现
    对于Serializable,类只需要实现Serializable接口,并提供一个序列化版本id(serialVersionUID)即可。
    Parcelable则需要实现writeToParceldescribeContents函数以及静态CREATOR变量,实际上就是将如何打包和解包的工作自己来定义,而序列化的这些操作完全由底层实现。

推荐阅读更多精彩内容