Android——序列化与反序列化
序列化:由于存在于内存中的对象都是暂时的,无法长期驻存,为了把对象的状态保持下来,这时需要把对象写入到磁盘或者其他介质中,这个过程就叫做序列化。
反序列化:反序列化恰恰是序列化的反向操作,也就是说,把已存在在磁盘或者其他介质中的对象,反序列化(读取)到内存中,以便后续操作,而这个过程就叫做反序列化。
一个对象要实现序列化操作,该类就必须实现了Serializable接口或者Parcelable接口,其中Serializable接口是在java中的序列化抽象类,而Parcelable接口则是android中特有的序列化接口,在某些情况下Parcelable接口实现的序列化更为高效。
主要应用场景1)内存中的对象写入到硬盘;
2)用套接字在网络上传送对象;
3)跨进程通信中AIDL接口中传输对象
Serializable是java提供的一个序列化接口,它是一个空接口,专门为对象提供标准的序列化和反序列化操作,使用Serializable实现类的序列化比较简单,只要在类声明中实现Serializable接口即可,同时强烈建议声明序列化标识。如下:
public class User implements Serializable { private static final long serialVersionUID = -2083503801443301445L; private int id; private String name; public int getId() { return id; } public void setId(int id) { this.id = id; } public String getName() { return name; } public void setName(String name) { this.name = name; } }
123456789101112131415161718192021222324User类实现的Serializable接口并声明了序列化标识serialVersionUID,该ID由编辑器生成,也可以自定义,但建议使用编译器生成的。实际上我们不声明serialVersionUID也是可以的,因为在序列化过程中会自动生成一个serialVersionUID来标识序列化对象。既然如此,那我们还需不需要要指定呢?原因是serialVersionUID是用来辅助序列化和反序列化过程的,原则上序列化后的对象中serialVersionUID只有和当前类的serialVersionUID相同才能够正常被反序列化,也就是说序列化与反序列化的serialVersionUID必须相同才能够使序列化操作成功。具体过程是这样的:序列化操作的时候系统会把当前类的serialVersionUID写入到序列化文件中,当反序列化时系统会去检测文件中的serialVersionUID,判断它是否与当前类的serialVersionUID一致,如果一致就说明序列化类的版本与当前类版本是一样的,可以反序列化成功,否则失败。报出如下UID错误:
Exception in thread "main" java.io.InvalidClassException: com.zejian.test.Client; local class incompatible: stream classdesc serialVersionUID = -2083503801443301445, local class serialVersionUID = -4083503801443301445 123
因此强烈建议指定serialVersionUID,这样的话即使微小的变化也不会导致crash的出现,如果不指定的话只要这个文件多一个空格,系统自动生成的UID就会截然不同的,反序列化也就会失败。
实例:
public class Demo { public static void main(String[] args) throws Exception { // 构造对象 User user = new User(); user.setId(1000); user.setName("韩梅梅"); // 把对象序列化到文件 ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("/serializable/user.txt")); oos.writeObject(user); oos.close(); // 反序列化到内存 ObjectInputStream ois = new ObjectInputStream(new FileInputStream("/serializable/user.txt")); User userBack = (User) ois.readObject(); System.out.println("read serializable user:id=" + userBack.getId() + ", name=" + userBack.getName()); ois.close(); } }
123456789101112131415161718192021这里有两点特别注意的是如果反序列类的成员变量的类型或者类名,发生了变化,那么即使serialVersionUID相同也无法正常反序列化成功。其次是静态成员变量属于类不属于对象,不会参与序列化过程,使用transient关键字标记的成员变量也不参与序列化过程。
另外,系统的默认序列化过程是可以改变的,通过实现如下4个方法,即可以控制系统的默认序列化和反序列过程:
public class User implements Serializable { private static final long serialVersionUID = -4083503801443301445L; private int id; private String name; public int getId() { return id; } public void setId(int id) { this.id = id; } public String getName() { return name; } public void setName(String name) { this.name = name; } /** * 序列化时, * 首先系统会先调用writeReplace方法,在这个阶段, * 可以进行自己操作,将需要进行序列化的对象换成我们指定的对象. * 一般很少重写该方法 */ private Object writeReplace() throws ObjectStreamException { System.out.println("writeReplace invoked"); return this; } /** *接着系统将调用writeObject方法, * 来将对象中的属性一个个进行序列化, * 我们可以在这个方法中控制住哪些属性需要序列化. * 这里只序列化name属性 */ private void writeObject(java.io.ObjectOutputStream out) throws IOException { System.out.println("writeObject invoked"); out.writeObject(this.name == null ? "默认值" : this.name); } /** * 反序列化时,系统会调用readObject方法,将我们刚刚在writeObject方法序列化好的属性, * 反序列化回来.然后通过readResolve方法,我们也可以指定系统返回给我们特定的对象 * 可以不是writeReplace序列化时的对象,可以指定其他对象. */ private void readObject(java.io.ObjectInputStream in) throws IOException, ClassNotFoundException { System.out.println("readObject invoked"); this.name = (String) in.readObject(); System.out.println("got name:" + name); } /** * 通过readResolve方法,我们也可以指定系统返回给我们特定的对象 * 可以不是writeReplace序列化时的对象,可以指定其他对象. * 一般很少重写该方法 */ private Object readResolve() throws ObjectStreamException { System.out.println("readResolve invoked"); return this; } }
1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768 Parcelable鉴于Serializable在内存序列化上开销比较大,而内存资源属于android系统中的稀有资源(android系统分配给每个应用的内存开销都是有限的),为此android中提供了Parcelable接口来实现序列化操作,Parcelable的性能比Serializable好,在内存开销方面较小,所以在内存间数据传输时推荐使用Parcelable,如通过Intent在activity间传输数据,而Parcelable的缺点就使用起来比较麻烦。
public class User implements Parcelable { public int id; public String name; public User friend; /** * 当前对象的内容描述,一般返回0即可 */ @Override public int describeContents() { return 0; } /** * 将当前对象写入序列化结构中 */ @Override public void writeToParcel(Parcel dest, int flags) { dest.writeInt(this.id); dest.writeString(this.name); dest.writeParcelable(this.friend, 0); } public NewClient() {} /** * 从序列化后的对象中创建原始对象 */ protected NewClient(Parcel in) { this.id = in.readInt(); this.name = in.readString(); //friend是另一个序列化对象,此方法序列需要传递当前线程的上下文类加载器,否则会报无法找到类的错误 this.friend=in.readParcelable(Thread.currentThread().getContextClassLoader()); } /** * public static final一个都不能少,内部对象CREATOR的名称也不能改变,必须全部大写。 * 重写接口中的两个方法: * createFromParcel(Parcel in) 实现从Parcel容器中读取传递数据值,封装成Parcelable对象返回逻辑层, * newArray(int size) 创建一个类型为T,长度为size的数组,供外部类反序列化本类数组使用。 */ 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]; } }; }
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960从代码可知,在序列化的过程中需要实现的功能有序列化和反序列以及内容描述。其中writeToParcel方法实现序列化功能,其内部是通过Parcel的一系列write方法来完成的,接着通过CREATOR内部对象来实现反序列化,其内部通过createFromParcel方法来创建序列化对象并通过newArray方法创建数组,最终利用Parcel的一系列read方法完成反序列化,最后由describeContents完成内容描述功能,该方法一般返回0,仅当对象中存在文件描述符时返回1。同时由于User是另一个序列化对象,因此在反序列化方法中需要传递当前线程的上下文类加载器,否则会报无法找到类的错误。
简单用一句话概括来说就是通过writeToParcel将我们的对象映射成Parcel对象,再通过createFromParcel将Parcel对象映射成我们的对象。也可以将Parcel看成是一个类似Serliazable的读写流,通过writeToParcel把对象写到流里面,在通过createFromParcel从流里读取对象,这个过程需要我们自己来实现并且写的顺序和读的顺序必须一致。ok~,到此Parcelable接口的序列化实现基本介绍完。
那么在哪里会使用到Parcelable对象呢?其实通过Intent传递复杂类型(如自定义引用类型数据)的数据时就需要使用Parcelable对象,如下是日常应用中Intent关于Parcelable对象的一些操作方法,引用类型必须实现Parcelable接口才能通过Intent传递,而基本数据类型,String类型则可直接通过Intent传递而且Intent本身也实现了Parcelable接口,所以可以轻松地在组件间进行传输。
除了以上的Intent外系统还为我们提供了其他实现Parcelable接口的类,再如Bundle、Bitmap,它们都是可以直接序列化的,因此我们可以方便地使用它们在组件间进行数据传递,当然Bundle本身也是一个类似键值对的容器,也可存储Parcelable实现类,其API方法跟Intent基本相似,由于这些属于android基础知识点,这里我们就不过多介绍了。
Serializable的设计初衷是为了序列化对象到本地文件、数据库、网络流、RMI以便数据传输,当然这种传输可以是程序内的也可以是两个程序间的。而Android的Parcelable的设计初衷是由于Serializable效率过低,消耗大,而android中数据传递主要是在内存环境中(内存属于android中的稀有资源),因此Parcelable的出现为了满足数据在内存中低开销而且高效地传递问题。两者效率选择
Serializable使用IO读写存储在硬盘上。序列化过程使用了反射技术,并且期间产生临时对象,优点代码少,在将对象序列化到存储设置中或将对象序列化后通过网络传输时建议选择Serializable。
Parcelable是直接在内存中读写,我们知道内存的读写速度肯定优于硬盘读写速度,所以Parcelable序列化方式性能上要优于Serializable方式很多。所以Android应用程序在内存间数据传输时推荐使用Parcelable,如activity间传输数据和AIDL数据传递。大多数情况下使用Serializable也是没什么问题的,但是针对Android应用程序在内存间数据传输还是建议大家使用Parcelable方式实现序列化,毕竟性能好很多,其实也没多麻烦。
Parcelable也不是不可以在网络中传输,只不过实现和操作过程过于麻烦并且为了防止android版本不同而导致Parcelable可能不同的情况,因此在序列化到存储设备或者网络传输方面还是尽量选择Serializable接口。 AndroidStudio中的快捷生成方式 AndroidStudio快捷生成Parcelable代码
android studio 提供了自动实现Parcelable接口的方法的插件,相当实现,我们只需要打开Setting,找到plugin插件,然后搜索Parcelable插件,最后找到android Parcelable code generator 安装即可。
使用:
在正常情况下,AS是默认关闭serialVersionUID生成提示的,我们需要打开setting,找到检测(Inspections选项),开启 Serializable class without serialVersionUID 检测即可,如下:
相关知识
Java IO 序列化流实现注册(Register)与登录(Login)
花秆绿竹试管快速繁殖(2008年)资源
private static final long serialVersionUID=1L 是什么意思
高效安全的支付宝Java SDK:助力你的支付集成
html玫瑰花表白代码
如何使用Java Swing和AWT创建一个带有动画效果的表白玫瑰花程序?请结合代码示例详细说明。
频率与时间换算器
python制作自己的字库
十二个常见的Web安全漏洞总结及防范措施
【免费】我自己写的网络聊天系统资源
网址: Android——序列化与反序列化 https://www.huajiangbk.com/newsview1293822.html
上一篇: 如何初始化Android端OSS |
下一篇: Android架构体系化学习与面 |
推荐分享

- 1君子兰什么品种最名贵 十大名 4012
- 2世界上最名贵的10种兰花图片 3364
- 3花圈挽联怎么写? 3286
- 4迷信说家里不能放假花 家里摆 1878
- 5香山红叶什么时候红 1493
- 6花的意思,花的解释,花的拼音 1210
- 7教师节送什么花最合适 1167
- 8勿忘我花图片 1103
- 9橄榄枝的象征意义 1093
- 10洛阳的市花 1039