Android 序列化与反序列化

一、概述

对象序列化的意思就是将对象的状态转换成字节流,以后可以通过这些值在生成相同状态的对象,对象序列化就是对象持久化的一种实现方法,它是将对象的属性和方法转化为一种序列化的形式用于存储和传输,反序列化就是根据这些保存的信息重建对象的过程。

总之:

  • 序列化就是将对象转化为字节流。
  • 反序列化就是将字节流转换为对象。

二、序列化的应用场景

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。

你可能感兴趣的:(Android 序列化与反序列化)