序列化作用:
序列化将对象编码成字节流,主要用于对象的持久化,远程通信,跨进程访问等地方。
java中的序列化机制能够 将一个实例对象(只序列化对象的属性值,而不会去序列化什么所谓的方法。)的状态信息写入到一个字节流中使其可以通过socket进行传输、或者持久化到存储数据库或文件系统中; 然后在需要的时候通过字节流中的信息来重构一个相同的对象。
对象序列化:就是将Obj转换成byte序列;
{
并不是所有对象都需要实例化;
transient:该元素不会进行jvm默认的序列化;
不会并不代表不能实例化;
1:wirteObject(ObjectOutputStream stream) throws IO{};
2:stream.defauleWriteObject();把jvm默认序列化的元素进行序列化操作;
3:可以调用stream.writeXxx():进行序列化;
反序列化:
1:readObject(ObjectInputStream s)throws{}
2:s.defaultReadObject();把jvm默认反序列化的元素进行反序列化操作;
3:s.readXxx();
}
注意事项
1.父类实现了序列化,则子类自动实现了序列化,即子类不需要显式实现 Serializable 接口,子类构造时会递归调用父类构造。
2.当父类没有实现序列化,而子类需要实现时,子类需要显式实现 Serializable 接口,并且父类中需要有无参的构造函数。
3.序列化只对对象的属性进行保存,而不会保存其方法。
4.当类中的实例变量引用了其他对象,那么在对该类进行序列化时,引用的对象也会被序列化。
1.Serializable接口:
Serializable接口是Java提供的一个序列化接口,它是一个空接口,为对象提供标准的序列化和反序列化操作。使用Serializable来实现的对象的序列化相当简单,只需要在类的生命中指定一个类似相面的标识即可自动实现默认的序列化过程;
这种方式是Java提供的一种序列化方式,过程非常简单,甚至有些开发人员都不需要声明serialVersionUID也可以完成这个过程,但serialVersionUID到底需不需要指定呢?
需要!
Java API既然提供了这个serialVersionUID,那么它必定是有用的。这个serialVersionUID是用来辅助序列化和反序列化过程的,原则上序列化后的数据中的serialVersionUID只有和当前类的serialVersionUID相同才能够正常地被反序列化。
serialVersionUID的详细工作过程是这样的:序列化的时候系统会把当前类的serialVersionUID写入序列化的二进制文件中,当反序列化的时候系统会检测文件中的serialVersionUID是否和当前类的serialVersionUID一致,如果一致就说明序列化的类的版本和当前类的版本是相同的,这个时候可以成功反序列化;否则说明当前类和反序列化的类相比发生了某些变化,比如成员变量的数量、类型发生了变化,这个时候是无法正常反序列化的。
一般来说,我们应该手动指定serialVersionUID的值,比如1L,也可以让IDE根据当前类的结构自动去生成它的hash值,这样序列化和反序列化时两者的serialVersionUID是相同的,因此可以正常进行反序列化操作。 如果不手动指定serialVersionUID的值 反序列化时当前类有些改变,比如增加或者删除了某些成员变量,那么系统就会重新计算当前类的hash值并把它赋值给serialVersionUID,这个时候当前类的serialVersionUID就和反序列化数据中的serialVersionUID不一致,就会造成反序列化失败的结果。所以, 手动指定serialVersionUID可以在很大程度上避免反序列化过程的失败。 比如当版本升级后,我们可能删除了某个成员变量也可能增加了一些新的成员变量,这个时候我们的反序列化过程依然能够成功,程序仍然能够最大限度地回复数据;相反,如果不指定serialVersionUID的话,程序会发生Crash。
当然,我们还需要考虑一种情况,如果类结构发生了非城规改变,比如修改了类名,修改了成员变量的类型,这个时候尽管serialVersionUID验证通过了,但是反序列化过程仍然会失败,因为类的结构有了毁灭性的改变,根本无法从老版本的数据中还原出一个新的类结构的对象。
对于使用序列化还有两点需要注意:
1.静态成员变量属于类不属于对象,所以不参与序列化过程
2.用transient关键字标记的成员变量不参与序列化过程
2.Parcelable接口
Parcelable接口是Android SDK提供的一种专门用于Android应用中对象的序列化和反序列化的方式,相比于Seriablizable具有更好的性能。实现Parcelable接口的对象就可以实现序列化并可以通过Intent和Binder传递。
下面是一个完成的实现了Parcelable接口的类
public
class
User implements Parcelable{
public
int
userId;
public
String userName;
public
String password;
public
Book book;
public
User(
int
userId,String userName,String password,Book book){
this
.userId=userId;
this
.userName=userName;
this
.password=password;
this
.book=book; }
public
int
describeContents(){
//几乎所有情况都返回0,仅在当前对象中存在文件描述符时返回1
return
0
; }
public
void
writeToParcel(Parcel
out
,
int
flags){
out
.writeInt(userId);
out
.writeString(userName);
out
.writeString(password);
out
.writeParcelable(book,
0
); }
public
static
final Parcelable.Creator<User> CREATOR=
new
Parcelable.Creator<User>(){
public
User createFromParcel(Parcel
in
){
return
new
User(
in
); }
public
User[] newArray(
int
size){
return
new
User[size]; } }
private
User(Parcel
in
){ userId=
in
.readInt(); userName=
in
.readString(); password=
in
.readString(); book=
in
.readParcelable(Thread.currentThread().getContextClassLoader()); }}
41
看起来比Serializable方式复杂太多。我们使用表格把Parcelable方式的相关方法进行说明
方法 | 功能 | 标记位 |
createFromParcel(Parcel in) | 从序列化后的对象中创建原始对象 | |
newArray(int size) | 创建指定长度的原始对象数组 | |
User(Parcel in) | 从序列化后的对象中创建原始对象 | |
writeToParcel(Parcel out,int flags) | 将当前对象写入序列化结构中 | PARCALABLE_WRITE_RETURN_VALUE |
describeContents | 返回当前对象的内容描述,几乎所有情况都返回0,仅在当前对象中存在文件描述符时返回1 | CONTENTS_FILE_DESCRIPTOR |
既然Parcelable和Serializable都可以实现序列化并且可以用于Intent间的数据传递,那么两者有什么区别呢?
区别 | Serializable | Parcelable |
所属API | JAVA API | Android SDK API |
原理 | 序列化和反序列化过程需要大量的I/O操作 | 序列化和反序列化过程不需要大量的I/O操作 |
开销 | 开销大 | 开销小 |
效率 | 低 | 很高 |
使用场景 | 序列化到本地或者通过网络传输 | 内存序列化 |