序列化和反序列化

序列化(Serialization)是将对象的状态信息转化为可以存储或者传输的形式的过程,一般将一个对象存储到一个储存媒介,例如档案或记忆体缓冲等,在网络传输过程中,可以是字节或者XML等格式;
而字节或者XML格式的可以还原成完全相等的对象,这个相反的过程又称为反序列化;

Java对象的序列化和反序列化

在Java中,我们可以通过多种方式来创建对象,并且只要对象没有被回收我们都可以复用此对象。但是,我们创建出来的这些对象都存在于JVM中的堆(stack)内存中,只有JVM处于运行状态的时候,这些对象才可能存在。一旦JVM停止,这些对象也就随之消失。

但是在真实的应用场景中,我们需要将这些对象持久化下来,并且在需要的时候将对象重新读取出来,Java的序列化可以帮助我们实现该功能。

对象序列化机制(object serialization)是java语言内建的一种对象持久化方式,通过对象序列化,可以将对象的状态信息保存未字节数组,并且可以在有需要的时候将这个字节数组通过反序列化的方式转换成对象,对象的序列化可以很容易的在JVM中的活动对象和字节数组(流)之间进行转换。

在JAVA中,对象的序列化和反序列化被广泛的应用到RMI(远程方法调用)及网络传输中。


方式

Serializable

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

  • 可序列化类中,未实现 Serializable 的属性状态无法被序列化/反序列化
  • 也就是说,反序列化一个类的过程中,它的非可序列化的属性将会调用无参构造函数重新创建
  • 因此这个属性的无参构造函数必须可以访问,否者运行时会报错
  • 一个实现序列化的类,它的子类也是可序列化的

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


public final class Test implements Serializable {

    private static final long serialVersionUID = 3651464272330759488L;
    ......
}

serialVersionUID
类似接口版本号,运行时这个版本号唯一标识了一个可序列化的类。

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

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

AS设置自动生成serialVersionUID

transient

实现Serilizable接口,将不需要序列化的属性前添加关键字transient,序列化对象的时候,这个属性就不会序列化到指定的目的地中。
1)一旦变量被transient修饰,变量将不再是对象持久化的一部分,该变量内容在序列化后无法获得访问。
2)transient关键字只能修饰变量,而不能修饰方法和类。注意,本地变量是不能被transient关键字修饰的。变量如果是用户自定义类变量,则该类需要实现Serializable接口。
3)被transient关键字修饰的变量不再能被序列化,一个静态变量不管是否被transient修饰,均不能被序列化。

PS: 若实现的是Externalizable接口,则没有任何东西可以自动序列化,需要在writeExternal方法中进行手工指定所要序列化的变量,这与是否被transient修饰无关。

Parcelable

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

  • Parcel 是一个载体,它可以包含数据或者对象引用,然后通过 IBinder 在进程间传递。
  • 因为序列化不支持short类型,所以AIDL不支持short
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 {

        public T createFromParcel(Parcel source);

        public T[] newArray(int size);
    }

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

总结

区别 Serializable Parcelable
所属API Java Android
原理 序列化和反序列化过程需要大量I/O操作 序列化和反序列化过程不需要大量I/O操作
开销
效率
使用场景 保存数据到本地或通过网络传输 运行时数据传递(Intent,Bundle 等)

你可能感兴趣的:(序列化和反序列化)