重学系列---序列化

1,386 阅读9分钟

序列化我们都不陌生了,但是对于它又了解了多少?

温故知新,今天重学梳理下序列化的知识,来人,上图!

大纲

先了解序列化的基本知识,然后去学习下JSON,最后看下我们常用的GSON

序列化基础

概念

  • 序列化

将数据结构或者是对象转换成二进制串的一个过程

  • 反序列化

将已被序列化产生的二进制串转换成数据结构或者是对象的过程

目的

  • 序列化

主要用于数据的网络传输,将数据存储到磁盘上,实现数据的永久保存

  • 反序列化

主要从网络,磁盘上读取字节数组还原成原始对象

特征

简单罗列一下

通用性
强健性
可读性
兼容性
安全性

常见方式

  • XML & SOAP
  • Protobuf
  • JSON

序列化和持久化的关系

通过序列化的方式可以实现数据持久化的功能

Android中的序列化

Serializable

是Java提供的一个序列化接口,是一个空接口
```java
public interface  Serializable{}
```
 ```java
public interface Extranalizable extends Serializable{
 	void writeExternal(ObjectOutput var1) throws IOException;
    void readExternal(ObjectInput var1) throw IOException,ClassNotFoundException
}
```
* 实现方式
继承Serializable接口
 ```java
public class Student implements  Serializable{
	//定义一个serialVersionUID标记
	private static final long serialVersionUID=0x23242L;
    ......
}
```
继承Extranalizable接口,重写**writeExternal**和**readExternal**方法
 ```java
public class   Student  implements Extranalizable{
	@override
	public void writeExternal(ObjectOutput var1) throws IOException;
    
    @override
	public void readExternal(ObjectInput var1) throw IOException,ClassNotFoundException
}
```
  • Serializable实现原理

Serializable的序列化和反序列化分别通过ObjectOutputStream和ObjectInputStream实现

  • writeObject实现原理

Parcelable

先来看个图,从图中看到Parcel应用在用户空间和内核空间的一个数据传递

  • 实现方式
 public  class Course implements Parcelable{
 	@override
     public int describeContents(){
     	return 0;//一般返回0即可
     }
     //将对象转换成一个parcel对象
     @override 
     public void writeToParcel(Parcel dest,int flags){
     }
     //实现类需要添加一个Creator属性,用于反序列化
     public static final Parcelable.Creator<Course> CREATE=new Parcelable.Creator<Course>(){
     	//......
     }
 
 }
  • 总结
1.Parcelable是Android中独有的序列化接口,相对于Serializable的使用更为复杂,但是其效率比Serializable高很多
2.Parcelable是Android SDK提供的,他是基于内存的,由于内存读写速度高于硬盘,因此Android中的跨进程对象的传递一般使用Parcelable
3.Parcelable通过Parcel实现了read和write的方法,从而实现了序列化和反序列化

JSON相关

先来看下熟悉的json数据格式

{
    "name":"nike",
    "number":123,
    "boolean":false,
    "null":null,
    "array":{
    	"address":"china",
        "ip":"192.168.0.1"
    }
}

定义

JSON(JavaScript Object Natation)是一种轻量级的数据交换格式

作用

数据标记,数据存储和数据传输

特点

1.读写速度快
2.解析简单
3.轻量级
4.具体独立性,独立于语言,独立于平台
5.具有自我描叙性

语法

JSON构建于两种结构

  • key-value键值对的形式(Object/字典/记录)
  • 值的有序列表,也就是array形式

常见解析方式

  • moshi (kotlin)
  • JackSon
  • FastJson
  • Android Studio自带的org.json

是一种文档驱动式的解析方式,首先需要把全部的文件读入到内存中,然后遍历所有的数据,根据需要检索出想要的数据

  • Gson

基于事件驱动式的解析方式,根据需要的数据建立一个对应于json数据的JavaBean类,然后通过简单的操作解析出想要的数据

聊到了常见的解析方式,那么现在说说我常用的一个解析类GSON

Gson

使用方式

Gson gson=new Gson();
gson.toJson();
gson.fromJson();

//也可以通过构建者的这种方式
GsonBuilder.create()

看到上面我们常用的代码,是不是觉得so easy,为什么我什么都不用干,简单的给了一个bean,他就能解析出我们想到的数据格式呢?下面且听我说

解析

可通过JSON官网了解具体的json相关信息

其实,当我们传入一个json字符串格式的时候,它经过了一个解析过程,一般来说,解析过程包括了语法分析和词义分析的两个阶段 词义分析:就是按照构词规则将json字串符解析成一个Token流。比如下面的json字符串解析

{"key","value",}

通过词义分析后得到一组Token,如下:

{key,value,}

在得到了Token后就将会进行语法分析,语法分析的目的就是要根据Json格式来检查Token序列中构成的Json格式是否合法

  • 总结
1.通过词义分析将字符串解析成一组Token序列
2.然后通过语法分析检查输入的Token序列是否合法

了解了json的构成后,我们就知道其实Gson是内部帮我们做了这些操作(说白了就是一个解析器的角色),抱着学习的目的来说的话,还是有必要去了解其内部的实现原理

常用的几个类

  • JsonElements
public abstract class JsonElement {
 
  public abstract JsonElement deepCopy();

  public boolean isJsonArray() {
    return this instanceof JsonArray;
  }

  public boolean isJsonObject() {
    return this instanceof JsonObject;
  }

  public boolean isJsonPrimitive() {
    return this instanceof JsonPrimitive;
  }

  public boolean isJsonNull() {
    return this instanceof JsonNull;
  }

  public JsonObject getAsJsonObject() {
    if (isJsonObject()) {
      return (JsonObject) this;
    }
    throw new IllegalStateException("Not a JSON Object: " + this);
  }

  public JsonArray getAsJsonArray() {
    if (isJsonArray()) {
      return (JsonArray) this;
    }
    throw new IllegalStateException("Not a JSON Array: " + this);
  }

  public JsonPrimitive getAsJsonPrimitive() {
    if (isJsonPrimitive()) {
      return (JsonPrimitive) this;
    }
    throw new IllegalStateException("Not a JSON Primitive: " + this);
  }

  public JsonNull getAsJsonNull() {
    if (isJsonNull()) {
      return (JsonNull) this;
    }
    throw new IllegalStateException("Not a JSON Null: " + this);
  }

  public boolean getAsBoolean() {
    throw new UnsupportedOperationException(getClass().getSimpleName());
  }

  public Number getAsNumber() {
    throw new UnsupportedOperationException(getClass().getSimpleName());
  }

  public String getAsString() {
    throw new UnsupportedOperationException(getClass().getSimpleName());
  }

  public double getAsDouble() {
    throw new UnsupportedOperationException(getClass().getSimpleName());
  }

  public float getAsFloat() {
    throw new UnsupportedOperationException(getClass().getSimpleName());
  }

  public long getAsLong() {
    throw new UnsupportedOperationException(getClass().getSimpleName());
  }

  public int getAsInt() {
    throw new UnsupportedOperationException(getClass().getSimpleName());
  }

  public byte getAsByte() {
    throw new UnsupportedOperationException(getClass().getSimpleName());
  }

  @Deprecated
  public char getAsCharacter() {
    throw new UnsupportedOperationException(getClass().getSimpleName());
  }

  
  public BigDecimal getAsBigDecimal() {
    throw new UnsupportedOperationException(getClass().getSimpleName());
  }


  public BigInteger getAsBigInteger() {
    throw new UnsupportedOperationException(getClass().getSimpleName());
  }

  public short getAsShort() {
    throw new UnsupportedOperationException(getClass().getSimpleName());
  }
  @Override
  public String toString() {
    try {
      StringWriter stringWriter = new StringWriter();
      JsonWriter jsonWriter = new JsonWriter(stringWriter);
      jsonWriter.setLenient(true);
      Streams.write(this, jsonWriter);
      return stringWriter.toString();
    } catch (IOException e) {
      throw new AssertionError(e);
    }
  }
}

从源码中我们发现这是一个抽象类,它代表着json串的某一个元素,这个元素可以是一个基本类型(javaPrimitive),可以是null,可以是Object,可以是Array等类型,其实啊,JsonElement是提供了一系列的方法来判断当前的JsonElement的类型

  • TypeAdapter 在gson中我们发现不管是new gson的方式,还是new GsonBuilder的方法,我们都能找到TypeAdapter的踪影,并重写其中write方法和read实现json数据的转换操作
public abstract class TypeAdapter<T>{
    public abstract void write(JsonWriter out,T value) throws IOException;
    public abstract void write(JsonReader in) throws IOException
}

流程

我们简单看下流程

我们发现gson利用了反射和泛型的方式,通过判断Type的类型来获取到对应的TypeAdapter适配器,完成Json字符串的一个解析

解析原理

不解释,直接上流程图 这里需要了解factory加载顺序的加载顺序

先加载的是排除器的Adapter,然后是自定义TypeAdapter,最后才是基本的TypeAdapter

  List<TypeAdapterFactory> factories = new ArrayList<TypeAdapterFactory>();

    // built-in type adapters that cannot be overridden
    factories.add(TypeAdapters.JSON_ELEMENT_FACTORY);
    factories.add(ObjectTypeAdapter.FACTORY);

    // the excluder must precede all adapters that handle user-defined types
    factories.add(excluder);

    // users' type adapters
    factories.addAll(factoriesToBeAdded);

    // type adapters for basic platform types
    factories.add(TypeAdapters.STRING_FACTORY);
    factories.add(TypeAdapters.INTEGER_FACTORY);
    factories.add(TypeAdapters.BOOLEAN_FACTORY);
    factories.add(TypeAdapters.BYTE_FACTORY);
    factories.add(TypeAdapters.SHORT_FACTORY);
    ......

反射原理

常见错误

Expected BEGIN_ARRAY but was STRING at line 1 column 27

出现类似这种错误,都是因为服务端给的数据格式不符合JSON格式要求导致的,那么问题来了,是不是出现这种问题我们就无解了呢?

当然不是,有两种解决方式:

  • 让返回null即可以解决
  • 用Gson自带的解决方法,可以自定义JsonDeserializer来处理,重写里面的方法即可
 public class ErrorJsonDeserializer implements JsonDeserializer<ErroJson>{
 }
 
 //使用GsonBuilder方法注册TypeAdapter
 GsonBuilder builder=new GsonBuilder();
 builder.registerTypeAdapter(ErrorJson.class,new ErrorJsonDeserializer());
 Gson gson=builder.create();
 gson.toJson();

面试题

  • 什么是serialVersionUID?

简单的说serialVersionUID是用来确保序列化后的对象能正确的反序列化出来

  • 如果可序列化类中的一个成员变量未实现可序列接口,会发生什么?

在可序列化类中,未实现Serializable的属性无法被序列化和反序列化

  • 在进行序列化操作的时候,希望某些成员不被序列化应该要怎么做?

可以添加瞬态关键字transient

  • Java的序列化和反序列过程中使用了哪些方法?

readObject/writeObject

  • Android里面为什么要用Bundle而不是Map结构传递数据?
Android中使用的Intent传递的数据大部分都是小数据的使用场景					
Bundle的内部是由ArrayMap实现的,ArrayMap的内部是两个数组,一个int数组用于存储对象数据对应的下标,一个对象数组保存key和value,内部使用了二分法对key进行了排序,所以在添加、删除、查找的过程中,都采用到了二分法查找,因此只适用于小数据量的操作(1000条之内)
如果在数据量大的情况采用HashMap操作(HashMap内部是数组+链表的结构)
HashMap在数据量小的情况下操作速度比ArrayMap慢,且占用内存空间也会比ArrayMap大
  • Android中Intent/Bundle通信原理以及大小限制?
Intent中的Bundle是通过binder机制进行数据传递的,能使用binder的缓冲区是有限制的,而一个进程默认有16个binder线程,因此每个线程能占用的缓冲区就更小了
  • 为什么intent不能直接在组件间传递对象而要通过序列化机制?
Intent在启动其他组件的时候,会进入到AMS进程中,这也就是说Intent所携带的数据需要跨进程间调用,Android是基于Linux系统,不同进程之间的Java对象时无法进行传输,因此只有通过序列化操作的对象才可以在应用程序和AMS进程间通信
  • 序列化过的对象属于深拷贝还是浅拷贝?
回答这个问题之前,我们先了解下什么是深拷贝?什么是浅拷贝?
浅拷贝:上在堆内存中创建了一个新的的对象,复制了基本类型的值,但是复杂数据类型也就是对象则是拷贝相同的地址,
深拷贝:对于复杂数据类型在堆内存中开辟了一块内存地址用于存放复制的对象并且把原有的对象复制过来,这2个对象是相互独立的,也就是2个不同的地址
而我们序列化过的对象已经存储在了磁盘中,因此属于深拷贝
  • Serializable和Parcelable的区别?