序列化和反序列化

提前声明

序列化和反序列化,transient修饰和static修饰的类和成员变量,不参加序列化过程,static修饰的会保存在方法区

转载:https://blog.csdn.net/justin_1107/article/details/72903006

通过源码中的介绍 可以知道,Parcelable接口的实现类是可以通过Parcel写入和恢复数据的,并且必须要有一个非空的静态变量 CREATOR, 而且还给了一个例子,这样我们写起来就比较简单了,但是简单的使用并不是我们的最终目的,通过查看Android源码中Parcelable可以看出,Parcelable实现过程主要分为序列化,反序列化,描述三个过程,下面分别介绍下这三个过程

Parcel的简介

在介绍之前我们需要先了解Parcel是什么?Parcel翻译过来是打包的意思,其实就是包装了我们需要传输的数据,然后在Binder中传输,也就是用于跨进程传输数据

简单来说,Parcel提供了一套机制,可以将序列化之后的数据写入到一个共享内存中,其他进程通过Parcel可以从这块共享内存中读出字节流,并反序列化成对象,下图是这个过程的模型。

1.3.2 序列化

我们通过writeToParcel方法实现序列化,writeToParcel返回了Parcel,所以我们可以直接调用Parcel中的write方法,基本的write方法都有,对象和集合比较特殊下面单独讲,基本的数据类型除了boolean其他都有,Boolean可以使用int或byte存储

举个例子:我们将上面的User对象实现序列化,User对象包含三个字段 age,name,isMale

 /**
 * 该方法负责序列化
 * @param dest
 * @param flags
 */
@Override
public void writeToParcel(Parcel dest, int flags) {
    dest.writeInt(age);
    dest.writeString(name);
    // boolean 可以使用int或byte方式进行存储,怎么存就怎么取
    dest.writeInt(isMale ? 1 : 0);
}

1.3.3 反序列化

反序列化需要定义一个CREATOR的变量,上面也说了具体的做法,这里可以直接复制Android给的例子中的,也可以自己定义一个(名字千万不能改),通过匿名内部类实现Parcelable中的Creator的接口

/**
 * 负责反序列化
 */
public static final Creator<User> CREATOR = new Creator<User>() {
    /**
     * 从序列化后的对象中创建原始对象
     */
    @Override
    public User createFromParcel(Parcel source) {
        return new User(source);
    }

    /**
     * 创建指定长度的原始对象数组
     */
    @Override
    public User[] newArray(int size) {
        return new User[size];
    }
};

public User(Parcel parcel) {
    age = parcel.readInt();
    name = parcel.readString();
    isMale = parcel.readInt() == 1;
}

Parcelable中对象和集合的处理

如果实现Parcelable接口的对象中包含对象或者集合,那么其中的对象也要实现Parcelable接口
如果包含对象Author和集合:

public class Book implements Parcelable {

  public int id;
  public String name;
  public boolean isCell;
  public ArrayList<String> tags;
  public Author author;
  // ***** 注意: 这里如果是集合 ,一定要初始化 *****
  public ArrayList<Author> authors = new ArrayList<>();

  public Book() {
  }
}

这里包含一个Author对象和Author集合

首先Author需要先实现Parcelable,实现步骤就不贴出来了,和普通的对象一样,实现三个过程

针对上面Book对象的序列化和反序列化的实现:

反序列化:

//反序列化
public static final Creator<Book> CREATOR = new Creator<Book>() {
    @Override
    public Book createFromParcel(Parcel in) {
        return new Book(in);
    }

    @Override
    public Book[] newArray(int size) {
        return new Book[size];
    }
};

protected Book(Parcel in) {
    id = in.readInt();
    name = in.readString();
    isCell = in.readByte() != 0;

    tags = in.createStringArrayList();

    // 读取对象需要提供一个类加载器去读取,因为写入的时候写入了类的相关信息
    author = in.readParcelable(Author.class.getClassLoader());

    //读取集合也分为两类,对应写入的两类
    //这一类需要用相应的类加载器去获取
    //in.readList(authors, Author.class.getClassLoader()); // 对应writeList

    //这一类需要使用类的CREATOR去获取
    in.readTypedList(authors, Author.CREATOR); //对应writeTypeList
    //authors = in.createTypedArrayList(Author.CREATOR);//对应writeTypeList

    //这里获取类加载器主要有几种方式
    getClass().getClassLoader();
    Thread.currentThread().getContextClassLoader();
    Author.class.getClassLoader();
}

序列化:

@Override
public void writeToParcel(Parcel dest, int flags) {
    //下面三种是基本类型就不多说
    dest.writeInt(id);
    dest.writeString(name);
    dest.writeByte((byte) (isCell ? 1 : 0));

    //序列化一个String的集合
    dest.writeStringList(tags);

    // 序列化对象的时候传入要序列化的对象和一个flag,
    // 这里的flag几乎都是0,除非标识当前对象需要作为返回值返回,不能立即释放资源
    dest.writeParcelable(author, flags);

    // 序列化一个对象的集合有两种方式,以下两种方式都可以

    //这些方法们把类的信息和数据都写入Parcel,以使将来能使用合适的类装载器重新构造类的实例.所以效率不高
    dest.writeList(authors);

    //这些方法不会写入类的信息,取而代之的是:读取时必须能知道数据属于哪个类并传入正确的Parcelable.Creator来创建对象
    // 而不是直接构造新对象。(更加高效的读写单个Parcelable对象的方法是:
    // 直接调用Parcelable.writeToParcel()和Parcelable.Creator.createFromParcel())
    dest.writeTypedList(authors);
}

写入和读取集合有两种方式,
一种是写入类的相关信息,然后通过类加载器去读取, –> writeList | readList
二是不用类相关信息,创建时传入相关类的CREATOR来创建 –> writeTypeList | readTypeList | createTypedArrayList
第二种效率高一些

一定要注意如果有集合定义的时候一定要初始化 like this –>
public ArrayList authors = new ArrayList<>();

4. Parcelable和Serializable的区别和比较

Parcelable和Serializable都是实现序列化并且都可以用于Intent间传递数据,Serializable是Java的实现方式,可能会频繁的IO操作,所以消耗比较大,但是实现方式简单 Parcelable是Android提供的方式,效率比较高,但是实现起来复杂一些 , 二者的选取规则是:内存序列化上选择Parcelable, 存储到设备或者网络传输上选择Serializable(当然Parcelable也可以但是稍显复杂)

java内存区域的分布//  https://www.cnblogs.com/wangdaqian/p/7062079.html

读了《深入理解Java虚拟机》之后,当时理解了,过段时间又忘记了,在此做下记录,方便自我回顾,也希望能帮到想要学习虚拟机的同学。

Java虚拟机在执行java程序时会把它所管理的内存分为5个不同的数据区域(也可以细分为7个不同的区域,后续做介绍):程序计数器、Java虚拟机栈、本地方法栈、Java堆、方法区

          java虚拟机运行时数据区

  1、程序计数器:用来记录正在执行的虚拟机字节码指令地址。可通过改变这个计数器的值来选取下一条需要执行的字节码指令。

    • 线程私有:为了线程切换后能恢复到正确的位置,每条线程都需要有一个独立的程序计数器,各条线程之间计数器互不影响。

  2、Java虚拟机栈:线程私有,生命周期与线程相同,每当创建一个线程时,JVM就会为这个线程创建一个对应的java栈。在这个java栈中又会包含多个栈帧,每运行一个方法就创建一个栈帧,用于存储局部变量表、操作栈、方法返回值等。每一个方法从调用直至执行完成的过程,就对应一个栈帧在java栈中入栈到出栈的过程。

    • 局部变量表:存放编译器可知的各种基本数据类型、对象引用。

  3、本地方法栈:与虚拟机栈发挥的作用相似,区别在于:虚拟机栈为虚拟机执行java方法(也就是字节码)服务,本地方法栈为虚拟机使用到的Native方法服务。

  4、java堆:存储对象实例以及数组。是垃圾收集器管理的主要区域。

    • 线程共享。java堆可以处于物理上不连续的内存空间,只要逻辑上连续即可。

  5、方法区:用于存放类信息(如类名、访问修饰符等)、常量池、静态变量、即时编译器便宜后的代码等。虽然JVM规范把方法区描述为堆的一个逻辑部分, 但它却有个别名non-heap(非堆),目的应该是与Java堆区分开来。    

  6、运行时常量池:方法区的一部分,用于存放编译期生成的各种字面量和符号引用。

    • 常量池:指的是编译期被确定并被保存在已编译的.class文件中的一些数据,除了包含代码中所定义的各种基本类型(如int、long等)和对象类型(如String和数组)的常量值(final),还包含一些以文本形式出现的一些符号引用。

  7、直接内存:堆外的内存。它不是虚拟机运行时数据区的一部分,也不是java虚拟机规范中定义的内存区域。




  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值