是什么?为啥用?怎么用?——魂魄三连问
1、序列化和反序列化是什么?
- 序列化:把对象变革为字节序列的过程称为对象的序列化。
- 反序列化:把字节序列规复为对象的过程称为对象的反序列化。
2、对象序列化的用途
- 永世的生存对象数据(将对象数据生存在文件大概磁盘中)
- 是对象数据可以大概在网络上传输(由于网络传输是以字节流的方式来完成对数据的传输的,因此序列化的目的是将对象数据转换成字节流的情势)。
- 使对象可以大概在历程间举行通报(底子范例数据除外,对象范例数据必须举行序列化操纵后才气举行传输)。
- 在Android intent之间,底子数据范例可以直接通报,但是通报复杂数据范例的时间,必须举行序列化。
序列化对象的时间只针对属性举行序列化,不针对方法序列化。
3、Android实现序列化的两种方式
3.1、实现Serializable接口
Serializable是java提供的一个序列化接口,它是一个空接口,专门为对象提供标准的序列化和反序列化操纵,使用Serializable实现类的序列化比力简单,只要在类声明中实现Serializable接口即可,同时猛烈发起声明序列化标识。
3.1.1 序列化举例
public class S_Shop implements Serializable { private static final long serialVersionUID = -1399695071515887643L; public String mShopName; public int mShopId; public String mShopPhone; public static int STATIC_VALUE = 100;//静态值 public transient int TRANSIENT_VALUE;//被transient修饰 不能序列化 @NonNull @Override public String toString() { return "Serializable: mShopName is " + mShopName + ",mShopId is " + mShopId + ",mShopPhone is " + mShopPhone + ",STATIC_VALUE is " + STATIC_VALUE + ",TRANSIENT_VALUE is " + TRANSIENT_VALUE; }}实行序列化和反序列化过程:
public static void main(String[] args) throws IOException { //------------------Serializable------------------ S_Shop shop = new S_Shop(); shop.mShopName = "商品名"; shop.mShopId = 2022; shop.mShopPhone = "15700000000"; shop.TRANSIENT_VALUE = 1000; saveObject(shop); //序列化 readObject();//反序列化 } //序列化 private static void saveObject(S_Shop shop) { ObjectOutputStream outputStream = null; try { outputStream = new ObjectOutputStream(new FileOutputStream("shop.obj")); outputStream.writeObject(shop); System.out.println("write-hashCode: " + shop.hashCode()); outputStream.close(); } catch (IOException e) { e.printStackTrace(); } finally { if (outputStream != null) { try { outputStream.close(); } catch (IOException e) { e.printStackTrace(); } } } } private static void readObject() { //反序列化 ObjectInputStream inputStream = null; try { inputStream = new ObjectInputStream(new FileInputStream("shop.obj")); S_Shop shop = (S_Shop) inputStream.readObject(); System.out.println(shop.toString()); System.out.println("read-hashCode: " + shop.hashCode()); } catch (IOException e) { e.printStackTrace(); } catch (ClassNotFoundException e) { e.printStackTrace(); } finally { if (inputStream != null) { try { inputStream.close(); } catch (IOException e) { e.printStackTrace(); } } } }实行结果:
Serializable: mShopName is 商品名,mShopId is 2022,mShopPhone is 15700000000,STATIC_VALUE is 100,TRANSIENT_VALUE is 0
结果看到反序列化乐成,从序列化布局中又重新天生了对象,这里留意一点,类中的变量TRANSIENT_VALUE是由transient修饰的,不能被序列化,以是反序列化时得到的是默认值。别的STATIC_VALUE由static修饰,也不加入序列化过程
3.1.2 特殊变量序列化
- 静态变量的序列化
序列化并不生存静态变量,序列化生存的是对象的状态,而静态变量是类的状态。
- Transient关键字
transient关键字的作用就是控制变量的序列化,在变量声明前加上该关键字,可以制止该变量序列化到文件中,在反序列化后,transient变量会被设为初始值,如int型的为0,对象型的为null。
- 父类的序列化特性
如果子类实现了Serializable接口而父类没有实现,那么父类不会被序列化,但是**父类必须有默认的无参构造方法,否则会抛出InvalidClassException非常。如下图所示
办理方案:想要将父类对象也序列化,就必要让父类也实现Serializable接口;如果父类不实现的话,就必要有默认的无参构造函数,而且父类的变量值都是默认声明的值。
在父类没有实现Serializable接口时,假造机不会序列化父对象,而一个Java对象的初始化必须先初始化父对象,再初始化子对象,反序列化也不例外。以是在反序列化时,为了构造父对象,只能调用父类对象的无参构造函数作为默认的父对象。因此当我们取父对象的变量值时,它的值是调用父类无参构造函数后的值
3.1.3 序列化举例序列化步调:
- 将对象实例干系的类元数据输出
- 递归地输出类的超类形貌直到不再有超类
- 类元数据完了之后,开始从最顶层的超类开始输出对象实例的现实数据值
- 从上至下递归输出实例的数据
3.2 、实现Parcelable接口
Parcelable是Android SDK API,其序列化操纵完全由底层实现,可以在历程内、历程间(AIDL)高效传输数据。差别版本的API实现方式可能差别,不宜做本地持久化存储。
3.2.1 序列化举例
public class P_Shop implements Parcelable { public P_Shop(){} public String mShopName; public int mShopId; public String mShopPhone; public static int STATIC_VALUE = 100;//静态值 public transient int TRANSIENT_VALUE;//被transient修饰 不能序列化 /** * 从序列化布局中创建原始对象 */ protected P_Shop(Parcel in) { mShopName = in.readString(); mShopId = in.readInt(); mShopPhone = in.readString(); } /** * 反序列化 */ public static final Creator<_Shop> CREATOR = new Creator<_Shop>() { /** * 从序列化对象中创建原始对象 */ @Override public P_Shop createFromParcel(Parcel in) { return new P_Shop(in); } /** * 创建指定长度的原始对象数组 */ @Override public P_Shop[] newArray(int size) { return new P_Shop[size]; } }; /** * 序列化:将当前对象写入序列化布局中 */ @Override public void writeToParcel(Parcel dest, int flags) { dest.writeString(mShopName); dest.writeInt(mShopId); dest.writeString(mShopPhone); } /** * 当前对象的内容形貌,存在文件形貌符时返回1 别的全返回0 */ @Override public int describeContents() { return 0; } @NonNull @Override public String toString() { return "arcelable: mShopName is " + mShopName + ",mShopId is " + mShopId + ",mShopPhone is " + mShopPhone + ",STATIC_VALUE is " + STATIC_VALUE + ",TRANSIENT_VALUE is " + TRANSIENT_VALUE; }}实行序列化/反序列化:
//------------------Parcelable------------------ Context context = this; P_Shop shopP = new P_Shop(); shopP.mShopName = "商品名"; shopP.mShopId = 2022; shopP.mShopPhone = "15700000000"; shopP.TRANSIENT_VALUE = 1000; //序列化过程 byte[] bytes = PUtil.marshall(shopP);//Parcel->bytes[] PUtil.save(context, bytes);//生存bytes[] //反序列化过程 Object object = PUtil.getParcel(context);//bytes[]->arcel->Object if (object == null) return; if (object instanceof P_Shop) { P_Shop shop = (P_Shop) object; Log.e("TTT", shop.toString()); }实行结果:
Parcelable: mShopName is 商品名,mShopId is 2022,mShopPhone is 15700000000,STATIC_VALUE is 100,TRANSIENT_VALUE is 0
3.2.2 实现原理
Parcelable 序列化过程中会用到Parcel,Parcel可以被认为是一个包罗数据大概对象引用的容器,可以大概支持序列化及在跨历程之后的反序列化。Parcelable 的序列化操纵在Native层实现,通过write内存写入及read读内存数据重新天生对象。Parcelable 将对象举行分解,且分解后每一部门都是支持可通报的数据范例。
4、对象序列化的用途Parcelable、Serializable比力
Serializable序列化和反序列化会颠末大量的I/O操纵,产生大量的临时变量引起GC;Parcelable是基于内存实现的封装息争封(marshalled& unmarshalled),服从比Serializable快许多
下面的测试来自非官方测试,通过Parcelable和Serializable分别实行序列化/反序列化过程,循环1000次取均匀值,实行结果如下:
数据来自 parcelable-vs-serializable,实行结果对比Parcelable的服从比Serializable快10倍以上。
5、总结
对比SerializableParcelable所属APIJava APIAndroid SDK API特点序列化和反序列化会颠末大量的I/O操纵,产生大量的临时变量引起GC,且反序列化时必要反射基于内存拷贝实现的封装息争封(marshalled& unmarshalled),序列化基于Native层实现,差别版本的API实现可能差别开销相对高相对低服从相对低相对高实用场景序列化到本地、网络传输紧张内存序列化别的序列化过程中的几个留意点:
下面两种成员变量不会加入到默认序列化过程中:
1、static静态变量属于类而不属于对象
2、transient标记的成员变量
加入序列化的成员变量自己也是必要可序列化的
反序列化时,非可序列化的(如被transient修饰)变量将会调用自身的无参构造函数重新创建,因此也要求此成员变量的构造函数必须是可访问的,否则会报错。 |