一、概述
对象序列化的意思就是将对象的状态转换成字节流,以后可以通过这些值在生成相同状态的对象,对象序列化就是对象持久化的一种实现方法,它是将对象的属性和方法转化为一种序列化的形式用于存储和传输,反序列化就是根据这些保存的信息重建对象的过程。
总之:
- 序列化就是将对象转化为字节流。
- 反序列化就是将字节流转换为对象。
二、序列化的应用场景
1、永久性保存对象,保存对象的字节序列到本地文件中;
2、通过序列化对象在网络中传递对象;
3、通过序列化在进程间传递对象;
三、Serializable接口(java 自带)
只需要让需要序列化的对象实现Serializable,并提供一个序列化版本id(serialVersionUID)即可。剩下的系统会帮我们自动的序列化。
下面是代码示例,通过Serializable接口来实现深克隆。
import java.io.Serializable;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
public class TestSeriazableClone {
public static void main(String[] args) {
SPerson p1 = new SPerson(1, "ConstXiong", new SFood("米饭"));
SPerson p2 = (SPerson)p1.cloneBySerializable();
p2.setName("其不答");
p2.getFood().setName("面条");
System.out.println(p1);
System.out.println(p2);
}
}
class SPerson implements Cloneable,Serializable{
private static final long serialVersionUID = -7710144514831611031L;
private int pid;
private String name;
private SFood food;
public SPerson(int pid, String name, SFood food) {
this.pid = pid;
this.name = name;
this.food = food;
System.out.println("Person constructor call");
}
public int getPid() {
return pid;
}
public void setPid(int pid) {
this.pid = pid;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
/**
* 通过序列化完成克隆
* @return
*/
public Object cloneBySerializable() {
Object obj = null;
try {
ByteArrayOutputStream baos = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(baos);
oos.writeObject(this);
ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray());
ObjectInputStream ois = new ObjectInputStream(bais);
obj = ois.readObject();
} catch (ClassNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
return obj;
}
@Override
public String toString() {
return "Person [pid:"+pid+", name:"+name+", food:"+food.getName()+"]";
}
public SFood getFood() {
return food;
}
public void setFood(SFood food) {
this.food = food;
}
}
class SFood implements Serializable {
private static final long serialVersionUID = -3443815804346831432L;
private String name;
public SFood(String name) {
this.name = name;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
注意点:使用 static、transient 修饰的属性不会被序列化。
如果需要定制序列化,则需要自己实现 writeObject 和readObject 方法。
private void writeObject(ObjectOutputStream out) throws IOException {
//invoke default serialization method
}
private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException {
//invoke default serialization method
}
在反序列化时,如果类的定义发生了变化,版本号就会变化,与流中的版本号就会不匹配,反序列化就会抛出异常,类型为java.io.InvalidClassException。通常情况下,我们希望自定义这个版本号,而非让Java自动生成,一方面是为了更好的控制,另一方面是为了性能,因为Java自动生成的性能比较低。
四、Parcelable接口(Android专用)
实现Parcelable步骤
1、implements Parcelable
2、重写writeToParcel方法,将你的对象序列化为一个Parcel对象,即:将类的数据写入外部提供的Parcel中,打包需要传递的数据到Parcel容器保存,以便从 Parcel容器获取数据
3、重写describeContents方法,内容接口描述,默认返回0就可以
4、实例化静态内部对象CREATOR实现接口Parcelable.Creator
public static final Parcelable.Creator CREATOR
注:其中public static final一个都不能少,内部对象CREATOR的名称也不能改变,必须全部大写。需重写本接口中的两个方法:createFromParcel(Parcel in) 实现从Parcel容器中读取传递数据值,封装成Parcelable对象返回逻辑层,newArray(int size) 创建一个类型为T,长度为size的数组,仅一句话即可(return new T[size]),供外部类反序列化本类数组使用。
简而言之:通过writeToParcel将你的对象映射成Parcel对象,再通过createFromParcel将Parcel对象映射成你的对象。也可以将Parcel看成是一个流,通过writeToParcel把对象写到流里面,在通过createFromParcel从流里读取对象,只不过这个过程需要你来实现,因此写的顺序和读的顺序必须一致。
示例代码:
import android.os.Parcel;
import android.os.Parcelable;
/**
* @author mumuxi
* @version 2022/4/29
*/
public class TestParcelable implements Parcelable {
private int age;
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
private String name;
@Override
public int describeContents() {
return 0;
}
@Override
public void writeToParcel(Parcel dest, int flags) {
dest.writeInt(this.age);
dest.writeString(this.name);
}
public void readFromParcel(Parcel source) {
this.age = source.readInt();
this.name = source.readString();
}
public TestParcelable() {
}
protected TestParcelable(Parcel in) {
this.age = in.readInt();
this.name = in.readString();
}
public static final Parcelable.Creator CREATOR =
new Parcelable.Creator() {
@Override
public TestParcelable createFromParcel(Parcel source) {
return new TestParcelable(source);
}
@Override
public TestParcelable[] newArray(int size) {
return new TestParcelable[size];
}
};
}
补充:可以通过Android Studio插件一键生成Parcelable代码(Android Parcelable code generator插件)。写完实体类后在Generate界面(Alt + Insert键)选择Parcelable一键生成。
五、Parcelable和Serializable的区别
1、作用
Serializable的作用是为了保存对象的属性到本地文件、数据库、网络流、RMI(Remote Method Invocation)以方便数据传输,当然这种传输可以是程序内的也可以是两个程序间的。而Android的Parcelable的设计初衷是因为Serializable效率过慢,为了在程序内不同组件间以及不同Android程序间高效的传输数据而设计,这些数据仅在内存中存在,Parcelable是通过IBinder通信的消息的载体。
2、效率及选择
Serializable是Java中的序列化接口,其使用起来简单但开销较大(因为Serializable在序列化过程中使 用了反射机制,故而会产生大量的临时变量,从而导致频繁的GC),并且在读写数据过程中,它是通 过IO流的形式将数据写入到硬盘或者传输到网络上。Parcelable的性能比Serializable好,在内存开销方面较小,所以在内存间数据传输时推荐使用Parcelable,如activity间传输数据。而Serializable可将数据持久化方便保存,Parcelable不能很好的保证数据的持续性,使用相对复杂,所以在需要保存或网络传输数据时选择Serializable。