先来了解一下什么是序列化与反序列化。
由于存在于内存中的对象都是暂时的,无法长期驻存,为了把对象的状态保持下来,这时需要把对象写入到磁盘或者其他介质中,这个过程就叫做序列化。
总结一句就是:将对象的状态信息转换为可以存储或传输的形式的过程。
反序列化是序列化的反向操作,也就是说,把已存在在磁盘或者其他介质中的对象,反序列化(读取)到内存中,以便后续操作,而这个过程就叫做反序列化。
总结一句就是:将序列化后的数据重新恢复成目标对象的过程(创建出与原对象完全相同的副本)。
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;
}
}
如上述代码所示,User类实现的Serializable接口并声明了序列化标识serialVersionUID,该ID由编辑器生成,也可以自定义,如1L,5L,不过还是建议使用编辑器生成唯一标识符。
那么serialVersionUID有什么作用呢?
实际上我们不声明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
因此强烈建议指定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();
}
}
输出结果:
read serializable user:id=1000, name=韩梅梅
从代码可以看出只需要ObjectOutputStream和ObjectInputStream就可以实现对象的序列化和反序列化操作,通过流对象把user对象写到文件中,并在需要时恢复userBack对象,但是两者并不是同一个对象了,反序列化后的对象是新创建的。
这里有两点特别注意的点:
另外,系统的默认序列化过程是可以改变的,通过实现如下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;
}
}
通过上面的4个方法,我们就可以随意控制序列化的过程了,由于在大部分情况下我们都没必要重写这4个方法,因此这里我们也不过介绍了,只要知道有这么一回事就行。
由于Serializable在内存序列化上开销比较大,而内存资源属于android系统中的稀有资源(android系统分配给每个应用的内存开销都是有限的),为此android中提供了Parcelable接口来实现序列化操作,Parcelable的性能比Serializable好,在内存开销方面较小,所以在内存间数据传输时推荐使用Parcelable,如通过Intent在activity间传输数据,而Parcelable的缺点就使用起来比较麻烦,下面给出一个Parcelable接口的实现案例:
class User implements Parcelable {
String name;
int age;
User friend;
public User() {
}
/**
* 当前对象的内容描述,一般返回0即可
* @return
*/
@Override
public int describeContents() {
return 0;
}
/**
* 将当前对象写入序列化结构中
* @param dest
* @param flags
*/
@Override
public void writeToParcel(Parcel dest, int flags) {
dest.writeString(this.name);
dest.writeInt(this.age);
dest.writeParcelable(this.friend, flags);
}
/**
* 从序列化后的对象中创建原始对象
* @param in
*/
protected User(Parcel in) {
this.name = in.readString();
this.age = in.readInt();
this.friend = in.readParcelable(User.class.getClassLoader());
}
public static final Creator<User> CREATOR = new Creator<User>() {
/**
* 从序列化后的对象中创建原始对象
* @param source
* @return
*/
@Override
public User createFromParcel(Parcel source) {
return new User(source);
}
/**
* 创建指定长度的原始对象数组
* @param size
* @return
*/
@Override
public User[] newArray(int size) {
return new User[size];
}
};
}
从上面代码可知,在序列化的过程中需要实现的功能有序列化和反序列以及内容描述。其中writeToParcel方法实现序列化功能,其内部是通过Parcel的一系列write方法来完成的,接着通过CREATOR内部对象来实现反序列化,其内部通过createFromParcel方法来创建序列化对象并通过newArray方法创建数组,最终利用Parcel的一系列read方法完成反序列化,最后由describeContents完成内容描述功能,该方法一般返回0,仅当对象中存在文件描述符时返回1。
这个过程需要我们自己来实现并且写的顺序和读的顺序必须一致。
Serializable的实现,只需要实现Serializable接口即可。这只是给对象打了一个标记(UID),系统会自动将其序列化。而Parcelabel的实现,不仅需要实现Parcelabel接口,还需要在类中添加一个静态成员变量CREATOR,这个变量需要实现 Parcelable.Creator 接口,并实现读写的抽象方法。
Serializable的设计初衷是为了序列化对象到本地文件、数据库、网络流、RMI以便数据传输,当然这种传输可以是程序内的也可以是两个程序间的。而Android的Parcelable的设计初衷是由于Serializable效率过低,消耗大,而android中数据传递主要是在内存环境中(内存属于android中的稀有资源),因此Parcelable的出现为了满足数据在内存中低开销而且高效地传递问题。
android studio 提供了自动实现Parcelable接口的方法的插件,相当实现,我们只需要打开Setting,找到plugin插件,然后搜索Parcelable插件,最后找到android Parcelable code generator 安装即可。
然后使用刚刚安装的插件协助我们生成实现Parcelable接口的代码:
在正常情况下,AS是默认关闭serialVersionUID生成提示的,我们需要打开setting,找到检测(Inspections选项),开启 Serializable class without serialVersionUID 检测即可,如下:
然后新建User类实现Serializable接口,右侧会提示添加serialVersionUID,鼠标放在类名上,Alt+Enter(Mac:cmd+Enter),快捷代码提示,生成serialVersionUID即可。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。