面试之Java序列化

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/qq_17338093/article/details/78827840
序列化作用:
序列化将对象编码成字节流,主要用于对象的持久化,远程通信,跨进程访问等地方。
java中的序列化机制能够 将一个实例对象(只序列化对象的属性值,而不会去序列化什么所谓的方法。)的状态信息写入到一个字节流中使其可以通过socket进行传输、或者持久化到存储数据库或文件系统中; 然后在需要的时候通过字节流中的信息来重构一个相同的对象。

对象序列化:就是将Obj转换成byte序列;
{
并不是所有对象都需要实例化;
java中关键字 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操作
开销
开销大
开销小
效率
很高
使用场景
序列化到本地或者通过网络传输
内存序列化




猜你喜欢

转载自blog.csdn.net/qq_17338093/article/details/78827840