简介
序列化是指把 Java 对象转换为字节序列并存储到一个存储媒介的过程。反之,把字节序列恢复为 Java 对象的过程则称之为反序列化。
作用
- 永久性保存对象,保存对象的字节序列到本地文件中;
- 通过序列化对象在网络中传递对象;
- 通过序列化在进程间传递对象。
使用
1.Serializable
只需要实现 Serializable 接口,并提供一个序列化版本id (serialVersionUID) 即可。
public class User implements Serializable{
private static final long serialVersionUID = -2457171891775313955L;
private String name;
private int age;
public User(String name, int age) {
this.name = name;
this.age = age;
}
/**
* Getter and Setter 方法
*/
}
/**
* 存储Serializable数据到本地
*/
private void storageSerializableData() {
List<User> mUsers = new ArrayList<>();
mUsers.add(new User("a", 3));
mUsers.add(new User("b", 4));
FileOutputStream fos = null;
ObjectOutputStream oos = null;
try {
File file = new File(Environment.getExternalStorageDirectory().toString() + "/" + "users.dat");
if (!file.exists()) {
file.createNewFile();
}
fos = new FileOutputStream(file.toString());
oos = new ObjectOutputStream(fos);
oos.writeObject(mUsers);
} catch (Exception e) {
Log.i("TAG",e.toString());
} finally {
try {
if (oos != null)
oos.close();
if (fos != null)
fos.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
/**
* 读取本地的Seriablizable数据
*/
private void getSerializableData() {
FileInputStream fis=null;
ObjectInputStream ois=null;
File file = new File(Environment.getExternalStorageDirectory().toString()
+ "/" + "users.dat");
if (file.exists()){
try {
fis=new FileInputStream(file.toString());
ois=new ObjectInputStream(fis);
List<User> mUsers = (List<User>) ois.readObject();
for (User user : mUsers){
Log.i("User",user.getName());
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
即使不指定 serialVersionUID 也可以实现序列化,那为什么还要求指定呢?
这个 serialVersionUID 是用来辅助序列化和反序列化过程的,原则上序列化后的数据中的 serialVersionUID 只有和当前类的serialVersionUID 相同才能够正常地被反序列化。
serialVersionUID 的详细工作过程是这样的:序列化的时候系统会把当前类的 serialVersionUID 写入序列化的二进制文件中,当反序列化的时候系统会检测文件中的 serialVersionUID 是否和当前类的 serialVersionUID 一致,如果一致就说明序列化的类的版本和当前类的版本是相同的,这个时候可以成功反序列化;否则说明当前类和反序列化的类相比发生了某些变化,比如成员变量的数量、类型发生了变化,这个时候是无法正常反序列化的。
一般来说,我们应该手动指定 serialVersionUID 的值,比如1L,也可以让 IDE 根据当前类的结构自动去生成它的 hash 值,这样序列化和反序列化时两者的 serialVersionUID 是相同的,因此可以正常进行反序列化操作。如果不手动指定 serialVersionUID 的值,反序列化时当前类有些改变,比如增加或者删除了某些成员变量,那么系统就会重新计算当前类的hash值并把它赋值给serialVersionUID,这个时候当前类的 serialVersionUID 就和反序列化数据中的 serialVersionUID 不一致,就会造成反序列化失败的结果。所以,手动指定 serialVersionUID 可以在很大程度上避免反序列化的失败。比如当版本升级后,我们可能删除了某个成员变量也可能增加了一些新的成员变量,这个时候我们的反序列化过程依然能够成功,程序仍然能够最大限度地回复数据;相反,如果不指定serialVersionUID的话,程序会发生Crash。
当然,我们还需要考虑一种情况,如果类结构发生了非城规改变,比如修改了类名,修改了成员变量的类型,这个时候尽管serialVersionUID 验证通过了,但是反序列化过程仍然会失败,因为类的结构有了毁灭性的改变,根本无法从老版本的数据中还原出一个新的类结构的对象。
对于使用序列化还有两点需要注意:
- 静态成员变量属于类不属于对象,所以不参与序列化过程
- 用transient关键字标记的成员变量不参与序列化过程
2.Parcelable
Parcelable接口是Android SDK提供的一种专门用于Android应用中对象的序列化和反序列化的方式,相比于Seriablizable具有更好的性能。实现Parcelable接口的对象就可以实现序列化并可以通过 Intent 和 Binder 传递。
public class User implements Parcelable{
private String name;
private int age;
//从序列化后的对象中创建原始对象
protected User(Parcel in) {
name = in.readString();
age = in.readInt();
}
public User(String name, int age) {
this.name = name;
this.age = age;
}
/**
* Getter and Setter 方法
*/
public static final Creator<User> CREATOR = new Creator<User>() {
//从序列化后的对象中创建原始对象
@Override
public User createFromParcel(Parcel in) {
return new User(in);
}
//创建指定长度的原始对象数组
@Override
public User[] newArray(int size) {
return new User[size];
}
};
//返回当前对象的内容描述,几乎所有情况都返回0,仅在当前对象中存在文件描述符时返回1
@Override
public int describeContents() {
return 0;
}
//将当前对象写入序列化结构中
@Override
public void writeToParcel(Parcel parcel, int i) {
parcel.writeString(name);
parcel.writeInt(age);
}
}
实现Parcelable接口主要可以分为一下几步:
1)implements Parcelable。
2)重写writeToParcel方法,将你的对象序列化为一个Parcel对象,即:将类的数据写入外部提供的Parcel中,打包需要传递的数据到Parcel容器保存,以便从Parcel容器获取数据。
3)重写describeContents方法,内容接口描述,默认返回0即可。
4)实例化静态内部对象CREATOR实现接口Parcelable.Creator 。
注意 : 若将Parcel看成是一个流,则先通过writeToParcel把对象写到流里面,再通过createFromParcel从流里读取对象,因此类实现的写入顺序和读出顺序必须一致。
Serializable 和 Serializable 区别
Android的Parcelable的设计初衷是因为Serializable效率过慢,为了在程序内不同组件间以及不同Android程序间(AIDL)高效的传输数据而设计,这些数据仅在内存中存在,Parcelable是通过IBinder通信的消息的载体。
区别 | Serializable | Parcelable |
所属API | JAVA API | Android SDK API |
原理 | 序列化和反序列化过程需要大量的I/O操作 | 序列化和反序列化过程不需要大量的I/O操作 |
开销 | 开销大 | 开销小 |
效率 | 低 | 很高 |
使用场景 | 序列化到本地或者通过网络传输 | 内存序列化 |