【Android IPC】序列化

前言

对象的序列化也是有着特殊的应用场景的,我想大概有如下场景需要对象的序列化:

  • 对象本地持久化
  • 对象通过Socket 进行网络传输
  • 深拷贝

序列化方式:

  • Serializable:java提供
  • Parcelable:安卓提供

一 、Serializable

1、简介:

Serializable是java提供的一个序列化的接口,这个接口是个空实现。为对象提供标准的序列化和反序列化操作。

2、栗子

定义一个类实现Serializable接口,然后声明个serialVersionUID即可。甚至serialVersionUID也不是必须的。我们不声明这个值也可以完成序列化,但是会对反序列化产生影响。

  public class User implements Serializable {
  
    private static final long serialVersionUID = 1L;
    public String userName;
    public int userId;
    public boolean isMale;

    public User(int userId, String userName, boolean isMale) {
        this.userId = userId;
        this.userName = userName;
        this.isMale = isMale;
    }    
}
}

    private void serializableTest() throws Exception {
        User user = new User(0, "Tom", true);
        //对象写入文件 ---序列化过程
        ObjectOutputStream oos = new ObjectOutputStream(openFileOutput("cache.txt",MODE_PRIVATE));
        oos.writeObject(user);
        oos.close();
        //  对象读取 --- 反序列化过程
        ObjectInputStream ois = new ObjectInputStream(openFileInput("cache.txt"));
        User newUser = (User) ois.readObject();
        ois.close();

        Log.i(TAG, "newUser:"+newUser.userName);//newUser:Tom
        Log.i(TAG, "newUser:"+newUser.userId);//newUser:0
        Log.i(TAG, "newUser:"+newUser.isMale);//newUser:true
    }
3、serialVersionUID

1、serialVersionUID的工作机制:
序列化的时候系统会把当前类的serialVersionUID写入序列化的文件中,当反序列化的时候系统会检测这个文件中的serialVersionUID是否和当前类的serialVersionUID一致,如果一致这个时候可以反序列化成功,否则就说明当前类和序列化的类相比发生了某些变化。比如成员变量的数量、类型可能发生了改变,这个时候是无法正常反序列化的,序列化异常(InvalidClassException ).

2、serialVersionUID手动指定区别

1、手动指定serialVersionUID时:
这样序列化和反序列化时的serialVersionUID是相同的,因此可以正常反序列化。即使当类结构发生变化时,系统也会尽可能的恢复原有类结构,也不至于InvalidClassException 异常
2、未指定serialVersionUID的值时:
反序列化时当前类有所改变,比如增加或减少了某些成员变量,那么系统会重新计算当前类的hash值并把它赋值给serialVersionUID,这个时候当前类的serialVersionUID就和序列化数据中的serialVersionUID不一致,于是反序列化失败,程序就会出现crash。
所以当我们手动指定了serialVersionUID的值,就可以很大程度上避免了反序列化的失败。但是如果类结构发生了非常规性改变,比如修改了类名,修改了成员变量的类型,这个时候尽管serialVersionUID验证通过了,但是反序列化还是会失败,因为类结构发生了毁灭性的改变。

4、serialVersionUID生成方式:
  • 一是默认的1L,比如:private static final long serialVersionUID = 1L;
  • 二是根据类名、接口名、成员方法及属性等来生成一个64位的哈希字段。这点可利用编译器提示自动生成如下图。

二者区别:
1L这种主要是用于区分系统版本号的,随着版本号的增加,可以改成2L、3L等。但程序很有可能升级版本,如果使用了1L、2L导致序列号不同,有可能第二版会不兼容第一版的程序,但是64位的哈希字段方式就可以,只要这个类本身没有修改过,两个相同的序列号都会指向同一个类。

在这里插入图片描述

5、注意
  • 静态成员变量不属于对象,所以不会参与序列化过程。其次用transient关键字标记的成员变量不参与序列化过程。
  • 一个子类实现了 Serializable 接口,而父类没有实现 Serializable 接口,则父类不具有序列化功能。
  • 父类实现序列化,子类则也可以具有序列化功能。
  • 当对一个对象进行序列化两次写入文件。第二次写入文件只增加了 5 个字节,并且两个对象是相等的。Java 序列化机制为了节省磁盘空间,具有特定的存储规则,当写入文件是同一个对象时,并不会再将对象的内容进行存储,而只是再次存储一份引用,上面增加的 5 字节的存储空间就是新增引用和一些控制信息的空间。反序列化时,恢复引用关系,所以两者引用是相等的。
6、原理简介

1、Serializable 的原理是通过 ObjectInputStream 和 ObjectOutputStream 来实现的,整个序列化过程使用了大量的反射和临时变量,而且在序列化对象的时候,不仅会序列化对象本身,还需要递归序列化对象引用的其他对象。

7、序列化相关方法
  • writeObject &readObject
  • writeReplace &readResovle

1、在序列化过程中,如果实现序列化接口的类中定义了 writeObject 和 readObject 方法,虚拟机会试图调用对象类里的 writeObject 和 readObject 方法,进行用户自定义的序列化和反序列化。如果没有这样的方法,则默认调用的是 ObjectOutputStream 的 defaultWriteObject 方法和 ObjectInuputStream 的 defaultReadObject 方法。
2、如果实现序列化接口类中了定义writeReplace 和 readResovle 方法。这两个方法代理序列化的对象,可以实现自定义返回的序列化实例。通过它们我们可以实现对象序列化的版本兼容,在反序列化生成新的对象的时候会破坏单例,我们可以添加一个 readResolver 方法直接返回单例对象即可。例如【java设计模式】单例设计模式(Singleton)中第三小节。
ps:自定义序列化可对字段做特殊处理,例如实现序列化加密。

二、Parcelable接口

1、栗子
/**
 * Create by SunnyDay on 2019/03/31
 */
public class User implements Parcelable {


    public int userId;
    public String userName;
    public boolean isMale;
    public Book book;


    public User(int userId, String userName, boolean isMale) {
        this.userId = userId;
        this.userName = userName;
        this.isMale = isMale;
    }

    protected User(Parcel in) {
        userId = in.readInt();
        userName = in.readString();
        isMale = in.readByte() != 0;
        book = in.readParcelable(Thread.currentThread().getContextClassLoader());
    }

    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];
        }
    };

    @Override
    public int describeContents() {
        return 0;
    }

    @Override
    public void writeToParcel(Parcel dest, int flags) {
        dest.writeInt(userId);
        dest.writeString(userName);
        dest.writeByte((byte) (isMale ? 1 : 0));
        dest.writeParcelable((Parcelable) book, 0);
    }
}

定义类实现Parcelable接口即可,根据编译器提示实现Parcelable接口中的方法即可,再根据提示实现CREATOR完成固定逻辑。

2、重载方法
方法功能
createFromParcel(Parcel in)从序列化后的对象中创建原始对象
writeToParcel(Parcel dest, int flags)将当前对象写入序列化结构中,其中flags的标识有0或者1,为1标识当前对象需要作为返回值返回,不能立即释放资源,几乎所有情况都为0。
newArray(int size)创建指定长度原始类型数组。
User(Parcel in)从序列化后的对象中创建原始对象。此构造编译器提示帮你生成。
describeContents返回对象的内容描述,如果含有文件描述符返回1否则返回0,一般为0
3、序列化对象最为成员变量

在 User(Parcel in)中book是另一个可序列化对象(作为本类的成员),所以他的反序列化过程需要传递当前线程上下文的类加载器。否则报无法找到类的错误。

三、 Parcelable和Serializable区别:

序列化方式优点缺点使用场景
Serializable使用简单、不需要手动去处理序列化和反序列化的过程开销非常大,序列化和反序列化过程需要大量io操作将对象序列化到设备中或者将对象序列化后通过网络传输
Parcelable效率高使用起来麻烦、需要手动去处理序列化和反序列化的过程内存上进行序列化

The end

参考: Java如何对一个对象进行深拷贝?

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值