1. 什么是序列化和反序列化,首先看一下维基百科的定义:
在数据储存与传送的部分是指将一个对象存储至一个储存媒介,例如档案或是记亿体缓冲等,或者透过网络传送资料时进行编码的过程,可以是字节或是XML等格式。而字节的或XML编码格式可以还原完全相等的对象。程序被应用在不同应用程序之间传送对象,以及将对象储存到档案或数据库。相反的过程又称为反序列化。
简单的来说就是将java对象转变成字节序列或者xml格式的数据,进而来进行网络传输或者存储到文件中(也可以在内存中传输),从而达到持久化的目的(对象只有在jvm运行时才有,序列化就是考虑到jvm不在运行时也能保存对象)。之所以还有xml格式的序列化数据是因为考虑要兼容不同平台和语言(不同平台和语言都认识xml格式的文件);本篇博客我们只考虑在java和android中的序列化,所以只探讨java对象的字节序列化与反序列化。
2 举个栗子: 首先写一个实体类,实现了Serializable接口(java中,只要一个类实现了该接口,就可以被序列化),该接口不含任何方法和字段:
public class User implements Serializable { private String name; private int age; private Gender gender; public User(String name, String password, int age, Gender gender) { this.name = name; this.password = password; this.age = age; this.gender = gender; System.out.println("有参数构造函数"); } public String getName() { return name; } public void setName(String name) { this.name = name; } public String getPassword() { return password; } public void setPassword(String password) { this.password = password; } public int getAge() { return age; } public void setAge(int age) { this.age = age; } public Gender getGender() { return gender; } public void setGender(Gender gender) { this.gender = gender; } @Override public String toString() { return "{ name: "+name+" passwd: "+password+" age: "+age+" gender: "+gender+" }"; }}其中Gender是枚举类型,因为枚举是继承类java.lang.Enum,该类实现了Serialiazble接口,所以枚举也可以被序列化;
public enum Gender { Male("男"),Female("女"); private String string; Gender(String sex){ string = sex; } @Override public String toString() { return super.toString()+"("+string+")"; } }接下来,我们编写一个场景类,用来将User对象保存到user.txt中,然后再从中读取出来,并打印看看。场景类如下:
public class SerializableTest { public static void main(String[] args) throws IOException,ClassNotFoundException{ File file = new File("user.txt"); ObjectOutputStream objectOutputStream = new ObjectOutputStream(new FileOutputStream(file)); User user = new User("Mike","123456",18,Gender.Male); objectOutputStream.writeObject(user); objectOutputStream.close(); ObjectInputStream objectInputStream = new ObjectInputStream(new FileInputStream(file)); User newUser = (User) objectInputStream.readObject(); System.out.println(newUser); System.out.println(user == newUser); objectInputStream.close(); } }打印结果如下: 有参数构造函数 { name: Mike passwd: 123456 age: 18 gender: Male(男) } False 可以看到,User对象被顺利序列化了,并且也被顺利反序列化了,但是user和newUser两个对象并不是同一个值,只是他们的内容相同,这点要注意,此外,在反序列化时,并没有调用User类的构造函数。
3 Serialiazble作用
为啥实现了Serializable接口就可以被序列化呢,点击objectOutputStream.writeObject(user),我们看一下源码
public final void writeObject(Object obj) throws IOException { if (enableOverride) { writeObjectOverride(obj); return; } try { writeObject0(obj, false); } catch (IOException ex) { if (depth == 0) { /* ----- BEGIN android ----- writeFatalException(ex);*/ try { writeFatalException(ex); } catch (IOException ex2) { } throw ex; } }然后接着点击writeObject0(obj, false);
private void writeObject0(Object obj, boolean unshared) throws IOException { //省略部分代码… if (obj instanceof Class) { writeClass((Class) obj, unshared); } else if (obj instanceof ObjectStreamClass) { writeClassDesc((ObjectStreamClass) obj, unshared); // ----- END android ----- } else if (obj instanceof String) { writeString((String) obj, unshared); } else if (cl.isArray()) { writeArray(obj, desc, unshared); } else if (obj instanceof Enum) { writeEnum((Enum) obj, desc, unshared); } else if (obj instanceof Serializable) { writeOrdinaryObject(obj, desc, unshared); } else { if (extendedDebugInfo) { throw new NotSerializableException( cl.getName() + "\n" + debugInfoStack.toString()); } else { throw new NotSerializableException(cl.getName()); } } } finally { depth--; bout.setBlockDataMode(oldMode); } }可以看到,如果对象是Class类型,String类型ObjectStreamClass类型,数组,枚举或者Serizable类型的话,就进行对象的序列化写入工作;当然,Class、String和ObjectStreamClass等类型也实现了Serizable接口,之所以还要分别判断一下,是因为他们的序列化写入方式不同,要分别处理,这点我们从源码也能看出来。
4 transient
假如说User类中有个字段我不想序列化,比如那个password字段,他保存的是用户的密码,这是敏感数据,如果序列化之后,万一泄漏了怎么办,这不要出事了,那我们怎么办呢,其实很简单,只需要在这个字段之前加transient关键字就可以了: private transient String password; 那我们再来运行一下我们的场景类:打印如下: { name: Mike passwd: null age: 18 gender: Male(男) } Passwd字段为null了,说明没有序列化进来。
5 Externalizable接口
Java还提供了一个Externalizable接口,让我们随意序列化对象,就是我们想序列那个字段就序列哪个字段,灵活度很高,该接口继承Serializable接口,并新增两个方法; void writeExternal(ObjectOutput out) throws IOException; void readExternal(ObjectInput in) throws IOException, ClassNotFoundException; 现在让我们的User类实现Externalizable接口,当然同时要覆写上面两个方法,
@Override public void writeExternal(ObjectOutput out) throws IOException { out.writeBytes(name); out.writeObject(gender); } @Override public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException { name = in.readLine(); gender = (Gender) in.readObject(); }同时,我们在User类中添加一个无参构造函数,否则编译不通过。
public User() { System.out.println(“无参数构造函数”); } 打印结果如下: 有参数构造函数 无参数构造函数 { name: Mike passwd: null age: 0 gender: Male(男) } False 可以看到,这种方式的反序列化中调用了无参的构造函数。自由度很高,想保持哪个字段就在上面两个方法里写。
6 writeObject()和readObject()
如果我们还是实现Serializable接口,也可以达到上面那种自由序列化的效果吗,答案是肯定,我们还让User实现Serializable,然后在User中添加两个方法,
private void writeObject(ObjectOutputStream out)throws IOException{ //out.defaultWriteObject();//这句意思是按默认方法写, out.writeBytes(name); out.writeObject(gender); } private void readObject(ObjectInputStream in)throws IOException,ClassNotFoundException{ //in.defaultReadObject();//这句意思是按默认方式读 name = in.readLine(); gender = (Gender) in.readObject(); }7 serialVersionUID
一般在实体类中,我们最好再定义一个serialVersionUID字段,值可以随便写一个,比如: private static final long serialVersionUID = 1L; 写这句有啥用呢,我举个例子,比如,我们的User类,之前已经被序列化到文件里,现在由于需求变更,需要在User类中再加一个字段userId,那么,加好之后,此时再去反序列化之前已经保存在文件里的对象,会抛出异常,就是因为java检测到现在的serialVersionUID与保存在文件里的serialVersionUID不一致,由于我们之前没有手动定义serialVersionUID这个字段,所以这个字段是java自己算的一个值,每次增加或者删除字段,这个值都会变化,如果,我们手动写死的话,这个值就不变了,也不会报异常了,这个读者可以自己实践一下。
至此,java的序列化与反序列化方式到此结束了,既然java已经提供了序列化方法,那安卓为啥也搞个自己的序列化方法呢,其实安卓的序列化主要是在内存中进行序列化,效率更高。下面我们先开一下安卓的序列化方式。
8 安卓的序列化方式,实现Parcelable接口
查阅谷歌官方文档,对该接口的解释是: Interface for classes whose instances can be written to and restored from a Parcel. Classes implementing the Parcelable interface must also have a non-null static field called CREATOR of a type that implements the Parcelable.Creator interface. 意思是这个 接口是为那些可以写入Parcel以及从中恢复的类的实例来使用的,实现该接口的类必须有一个非空静态字段CREATOR,这个字段实现了Parcelable.Creator接口。 简单来说,Parcel提供了一套机制,可以将序列化的数据写到一个共享内存中,其他进程通过Parcel可以从这块共享内存中读出字节流,并反序列化成对象。 再说明白点:parcel的中文意思是包裹,parcelable是可打包的意思,就是说,如果一个对象是可打包的(实现Parcelable接口),那么它就可以被打包进包裹里(parcel),而包裹可以在内存中传输,每个进程都认识,并且可以从包裹里拿出被打包的对象。 比如,如下:
User2 user = new User2("Tom","123456",88); @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_second); Parcel parcel = Parcel.obtain(); parcel.writeParcelable(user,0); User2 newUser = parcel.readParcelable(User2.class.getClassLoader()); Log.e("newuser",user.toString()); }下面我们试一下这个方法,新建一个User2类。并实现Parcelable接口:
public class User2 implements Parcelable{ private String name; private String password; private int age; public User2(String name, String password, int age) { this.name = name; this.password = password; this.age = age; } public String getName() { return name; } public void setName(String name) { this.name = name; } public String getPassword() { return password; } public void setPassword(String password) { this.password = password; } public int getAge() { return age; } public void setAge(int age) { this.age = age; } @Override public int describeContents() { return 0; } @Override public void writeToParcel(Parcel dest, int flags) { dest.writeString(name); dest.writeString(password); dest.writeInt(age); } public static final Creator<User2> CREATOR = new Creator<User2>() { @Override public User2 createFromParcel(Parcel source) { User2 user = new User2("","",0); user.name = source.readString(); user.password = source.readString(); user.age = source.readInt(); return user; } @Override public User2[] newArray(int size) { return new User2[size]; } }; @Override public String toString() { return "{ name: "+name+" passwd: "+password+" age: "+age+" }"; } }首先在writeToParcel方法里写上我们要序列化的对象,然后在createFromParcel方法里进行反序列化操作。这个是固定的套路。用起来就比较简单了:
在Activity中的页面跳转时一般这样用: User2 user = new User2(“Tom”,”123456”,88); Intent intent = new Intent(this,OtherActivity.class); intent.putExtra(“user”,user); startActivity(intent);
在另一个页面一般直接获取就行 User2 user = getIntent().getParcelableExtra(“user”);
总结:
android在内存序列化上,应尽量使用parcelable方式,效率更高,将一个完整的对象进行分解,或者说他只是把对象的字段进行序列化了,反序列化的时候,先手动创建一个对象,然后给对象的字段分别进行赋值,这样效率更高。android的跨进程通信传输的对象就是实现parcelableh接口;Serialiable将整个对象序列化,使用了反射机制,效率略低,这种机制会在序列化的时候创建许多的临时对象,容易触发垃圾回收Serialiable方式实现起来较为简单,parcelable方式实现较为复杂,序列化到存储设备上时,还使用Serialiable方式