Serializable与Parcelable
Serializable接口是Java中为对象提供标准的序列化和反序列化操作的接口,而Parcelable接口是Android提供的序列化方式的接口
Serializable
Serialization(序列化)是一种将对象以一连串的字节描述的过程;反序列化deserialization是一种将这些字节重建成一个对象的过程。
序列化只能保存对象的非静态成员交量,不能保存任何的成员方法和静态的成员变量,而且串行化保存的只是变量的值,对于变量的任何修饰符都不能保存
对于某些类型的对象,其状态是瞬时的,这样的对象是无法保存其状态的。例如一个Thread对象或一个FileInputStream对象,对于这些字段,我们必须用transient关键字标明,否则编译器将报措。
另外 ,串行化可能涉及将对象存放到磁盘上或在网络上发送数据,这时候就会产生安全问题。因为数据位于Java运行环境之外,不在Java安全机制的控制之中。对于这些需要保密的字段,不应保存在永久介质中,或者不应简单地不加处理地保存下来,为了保证安全性。应该在这些字段前加上transient关键字。
如果该类有父类,则分两种情况来考虑,如果该父类已经实现了可序列化接口。则其父类的相应字段及属性的处理和该类相同;如果该类的父类没有实现可序列化接口,则该类的父类所有的字段属性将不会序列化,并且反序列化时会调用父类的默认构造函数来初始化父类的属性,而子类却不调用默认构造函数,而是直接从流中恢复属性的值。
使用
java.io.ObjectOutputStream代表对象输出流,它的writeObject(Object obj)方法可对参数指定的obj对象进行序列化,把得到的字节序列写到一个目标输出流中。
java.io.ObjectInputStream代表对象输入流,它的readObject()方法从一个源输入流中读取字节序列,再把它们反序列化为一个对象,并将其返回。
只有实现了Serializable和Externalizable接口的类的对象才能被序列化。Externalizable接口继承自 Serializable接口,实现Externalizable接口的类完全由自身来控制序列化的行为,而仅实现Serializable接口的类可以 采用默认的序列化方式 。
对象序列化包括如下步骤
- 创建一个对象输出流,它可以包装一个其他类型的目标输出流,如文件输出流;
- 通过对象输出流的writeObject()方法写对象。
对象反序列化的步骤
- 创建一个对象输入流,它可以包装一个其他类型的源输入流,如文件输入流;
- 通过对象输入流的readObject()方法读取对象。
serialVersionUId
serialVersionUId的详细工作机制
序列化的时候系统会把当前类的serialVersionUId写入序列化的文件中,当反序列化的时候系统会去检测文件中的serialVersionUId,看它是否和当前类的serialVersionUId一致,如果一致就说明序列化的类的版本和当前类的版本是相同的,这个时候可以成功反序列化;否则说明版本不一致无法正常反序列化。
一般来说,我们应该手动指定serialVersionUId的值,比如1L也可以让IDE根据类的结构自动生成它的hash值,这样序列化和反序列化时两者的serialVersionUID是相同的,因此可以正常进行反序列化。如果不手动指定serialVersionUID的值,反序列化时当前类有所改变,比如增加或删除了某些成员变量,那么系统就会重新计算当前类的hash值并把它赋值给serialVersionUID,这个时候当前类的serialVersionUID就和序列化中的serialVersionUID不一致,于是反序列化失败。
Parcelable
public class User implements Parcelable
{
public int userId;
public String userName;
public boolean isMale;
public Book book;
//返回当前对象的内容描述,如果含有文件描述符,返回1,
// 否则返回0,几乎所有情况都返回0
@Override public int describeContents()
{
return 0;
}
//将当前对象写入序列化结构中
//flags标示有两种值:0或者1.为1时标识当前对象需要作为返回值返回,
//不能立即释放资源,几乎所有情况都为0
@Override public void writeToParcel(Parcel dest, int flags)
{
dest.writeInt(this.userId);
dest.writeString(this.userName);
dest.writeByte(this.isMale ? (byte) 1 : (byte) 0);
dest.writeParcelable(this.book, flags);
}
public User()
{
}
protected User(Parcel in)
{
this.userId = in.readInt( );
this.userName = in.readString( );
this.isMale = in.readByte( ) != 0;
//因为book是另一个可序列化对象,
//所以它的反序列化过程需要传递当前线程的上下文类加载器,
//否则会报找到类的错误
this.book = in.readParcelable(Book.class.getClassLoader( ));
}
//该静态域是必须要有的,而且名字必须是CREATOR,否则会出错
public static final Parcelable.Creator<User> CREATOR = new Parcelable.Creator<User>( )
{
//从序列化后的对象中创建原始对象
@Override public User createFromParcel(Parcel source)
{
//创建返回对象
return new User(source);
}
//创建指定长度的原始对象数组
@Override public User[] newArray(int size)
{
return new User[size];
}
};
}
选择
在使用内存的时候,Parcelable比Serializable性能高,所以推荐使用Parcelable。
Serializable在序列化的时候会产生大量的临时变量,从而引起频繁的GC。
Parcelable不能使用在要将数据存储在磁盘上的情况,因为Parcelable不能很好的保证数据的持续性在外界有变化的情况下。
对象的持久化可以选择Json,XML,网络传输也可以选择Json,XML
参考资料
Android开发艺术探索
Java中的Serializable浅谈,只是浅谈
Java基础学习总结——Java对象的序列化和反序列化
对象序列化与反序列化(Serializable、Externalizable)
Android中Parcelable接口用法