Java Serializable、Android Parcelable序列化学习记录


Serializable序列化源码解析:Java Serializable序列化源码解析(ObjectOutputStream、ObjectInputStream)
Parcelable序列化源码解析:Android Parcelable序列化源码解析
ヾ(◍°∇°◍)ノ゙欢迎大家去康康

什么是序列化

序列化:将数据结构对象转换成二进制串的过程

反序列化:将二进制串转换成数据结构对象的过程


序列化有什么用

在系统底层数据传输形式是简单的字节序列传递,系统不认识对象这种那么高级的东西,只认识字节序列。而为了达到进程通讯的目的,需要先将数据序列化,而序列化就是将对象转化字节序列的过程。

所以无论是网络传输进程通信数据持久化存储,都需要用到序列化。


常见的序列化协议

XML

具有跨机器、跨语言、可读性强等优点。SOAP协议(简单对象访问协议)就是基于XML实现的。SOAP协议与Http协议区别是Http传输格式是文本,SOAP传输格式是XML。

JSON

JSON起源于弱类型语言 Javascript。JSON是Key:Value的结构非常符合工程师对对象的理解。它的优点是:保持了XML的可读性、结构比XML更加简洁(解析速度更快)、具备Javascript先天性支持、具备可扩展性和兼容性等。

ProtoBuff

Google推出的协议。序列化数据非常简洁,解析速度非常快,就是可读性有点差。

学习文章:Android Protobuf应用及原理


序列化怎么用

Android中想要对象支持序列化,只有两种方式。一种是实现Serializable接口,另外一种是实现Parcelable接口。Serializable是Java为我们提供的一个标准化的序列化接口,而已Parcelable是Android sdk的接口。

两者对比

Parcelable相对于Serializable的使用相对复杂一些;
Parcelable性能比Serializable要高;(Serializable序列化协议较为复杂,并且在序列化过程中用了大量反射)
Parcelable不适合用于持久化存储;因为其协议较为简单,没法进行类的签名校验,一旦类信息变更,无法确保所有的数据能正常读取。
使用场景持久化存储、网络传输上使用Serializable。进程间通讯使用Parcelable。


Java的Serializable
Serializable接口的使用
serialVersionUID

JAVA序列化的机制是通过判断类的serialVersionUID(long 型)来验证的版本一致的。如果你修改了此类要修改此值,否则以前用老版本的类序列化的类恢复时会报错: InvalidClassException。JVM规范强烈建议我们手动声明一个serialVersionUID。

如果没有声明serialVersionUID,序列化处理器也会帮我们根据类信息字段自动生成一个。如果改变了类的信息,该生成的suid也会改变。

//查看类的suid方法
ObjectStreamClass c1 = ObjectStreamClass.lookup(A.class);
long a_suid = c1.getSerialVersionUID();
System.out.println("A suid=" + a_suid);
System.out.println("A Hex suid=" + Long.toHexString(a_suid));
//输出->
//	A suid=-5651416281380266683
//	A Hex suid=b19222d5a6785d45

//需要注意的是,这里的suid是十进制的。序列化保存的是16进制,所以需要进行转换才能对应得上。

对象序列化只关注非静态成员变量

对象序列化保存的是对象的"状态",即它的成员变量。由此可知对象序列化不关注类中的静态变量。

class A implements Serializable{
        private String name;
        private int age;
        public static int i = 10;

        public A(String name, int age){
            this.name = name;
            this.age = age;
        }

        @Override
        public String toString() {
            return "A{" +
                    "name='" + name + '\'' +
                    ", age=" + age +
                    ", i=" + i +
                    '}';
        }
    }

try {
	File file = new File("test_a");
	ObjectOutputStream oos = new ObjectOutputStream(
		new FileOutputStream(file));
	oos.writeObject(new A("a", 1));
	oos.close();
  
  A.i = 9527; //改变A中静态变量i的值

	ObjectInputStream ois = new ObjectInputStream(new FileInputStream(file));
	A a = (A) ois.readObject();
	ois.close();
	System.out.println(a); //输出-> A{name='a', age=1, i=9527}
} catch (Exception e) {
	e.printStackTrace();
}

子类与父类的序列化关系

① 父类实现了序列化接口,子类也能享有序列化

class A implements Serializable{
        protected String name;
        protected int age;

        public A(String name, int age){
            this.name = name;
            this.age = age;
        }
    }

class B extends A{
        private String string;

        public B(){
            super("b", 1);
            this.string = "string";
        }

        @Override
        public String toString() {
            return "B{" +
                    "name='" + name + '\'' +
                    ", age=" + age +
                    ", string='" + string + '\'' +
                    '}';
        }
    }

try {
	File file = new File("test_b");
	ObjectOutputStream oos = new ObjectOutputStream(
		new FileOutputStream(file));
	oos.writeObject(new B());
	oos.close();

	ObjectInputStream ois = new ObjectInputStream(new FileInputStream(file));
	Object obj = ois.readObject();
	ois.close();
	System.out.println(obj); //输出-> B{name='b', age=1, string='string'}
} catch (Exception e) {
	e.printStackTrace();
}

② 子类实现了序列化接口,而父类没有实现的情况。父类必须有默认实现默认构造函数,否则会报错。

//A类的implements Serializable去掉
//B类加上implements Serializable后再运行
报错:java.io.InvalidClassException: com.llk.kt.s.B; no valid constructor
  
//在A类中加上默认构造函数的实现后再运行
输出 -> B{name='null', age=0, string='string'} //父类A的字段没有被序列化

transient关键字的使用
让字段不参与序列化
class C implements Serializable{
        private String name;
        transient private int age;

        public C(){
            this.name = "c";
            this.age = 1;
        }

        @Override
        public String toString() {
            return "C{" +
                    "name='" + name + '\'' +
                    ", age=" + age +
                    '}';
        }
    }

try {
	File file = new File("test_c");
	ObjectOutputStream oos = new ObjectOutputStream(
		new FileOutputStream(file));
	oos.writeObject(new C());
	oos.close();

	ObjectInputStream ois = new ObjectInputStream(new FileInputStream(file));
	Object obj = ois.readObject();
	ois.close();
	System.out.println(obj); //输出-> C{name='c', age=0}
} catch (Exception e) {
	e.printStackTrace();
}

字段加了transient,还要序列化它
//增加两个私有方法writeObject、readObject。在这两个方法中手动地进行序列化跟反序列化操作。
//这两个方法不是随便写的。方法名、传参、作用域都是固定的。因为序列化过程中会反射判断是否有这两个方法,有就调用。
private void writeObject(ObjectOutputStream out) throws IOException {
	System.out.println("=== writeObject");
	out.defaultWriteObject();
	out.writeInt(age);
}

private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException {
	System.out.println("=== readObject");
	in.defaultReadObject();
	age = in.readInt();
}

//增加这两个方法后,在里边对transient private int age;进行手动序列化/反序列化。后再运行
输出 -> 
  === writeObject
	=== readObject
	C{name='c', age=1}

Externalizable接口的使用

Externalizable接口是Serializable的子接口。实现Externalizable接口后序列化的细节需要我们自己去完成

反序列化的过程,会先调用类的无参构造函数创建新对象然后再填充数据。

class D implements Externalizable{
        private String name;
        transient private int age;

        public D(){
            System.out.println("=== call D()");
        }

        public D(String name, int age){
            this.name = name;
            this.age = age;
        }

        @Override
        public void writeExternal(ObjectOutput out) throws IOException {
            System.out.println("=== writeExternal");
            out.writeObject(name);
            out.writeInt(age);
        }

        @Override
        public void readExternal(ObjectInput in) throws ClassNotFoundException, IOException {
            System.out.println("=== readExternal");
            name = (String) in.readObject();
            age = in.readInt();
        }

        @Override
        public String toString() {
            return "D{" +
                    "name='" + name + '\'' +
                    ", age=" + age +
                    '}';
        }
    }

try {
	File file = new File("test_d");
	ObjectOutputStream oos = new ObjectOutputStream(
		new FileOutputStream(file));
	oos.writeObject(new D("d", 1));
	oos.close();

	ObjectInputStream ois = new ObjectInputStream(new FileInputStream(file));
	Object obj = ois.readObject();
	ois.close();
	System.out.println(obj);
} catch (Exception e) {
	e.printStackTrace();
}

输出 ->
=== writeExternal
=== call D()
=== readExternal


单例模式与序列化
try {
  C c = new C("c", 1);
  
	File file = new File("test_c");
	ObjectOutputStream oos = new ObjectOutputStream(
		new FileOutputStream(file));
	oos.writeObject(c);
	oos.close();

	ObjectInputStream ois = new ObjectInputStream(new FileInputStream(file));
	Object obj = ois.readObject();
	ois.close();
  System.out.println(obj); //输出 -> C{name='c', age=1}
	System.out.println("!!!!!!!! " + (c == obj)); //输出 -> !!!!!!!! false
} catch (Exception e) {
	e.printStackTrace();
}

反序列化的过程是创建一个新对象后再填充数据的,所上边例子中的比较才为false。

如果有在单例模式下使用序列化的场景的话,就需要额外告诉序列化处理器需要填充到哪个对象(使用readResolve方法)
删除线的话语不严谨。readResolve方法谨慎使用,因为我们返回的实例给到序列化器,它会在反序列化后直接将我们返回的实例替换掉序列化出来实例对象。(详情推荐大家去康康我写的源码解析的帖子)

//将C类改造成单例类
class C implements Serializable {
        private String name;
        private int age;

        private volatile static C instance = null;

        public static C get() {
            if (instance == null) {
                synchronized (C.class) {
                    if (instance == null) {
                        instance = new C();
                    }
                }
            }
            return instance;
        }

        public C(){
            this.name = "c";
            this.age = 1;
        }

  			//增加readResolve方法,返回指定的对象
  			//跟writeObject、readObject一样,序列化处理器会反射该方法,如果有的话就调用。
        private Object readResolve() throws ObjectStreamException {
            return instance;
        }

        @Override
        public String toString() {
            return "C{" +
                    "name='" + name + '\'' +
                    ", age=" + age +
                    '}';
        }
    }

输出 -> 
  C{name='c', age=1}
	!!!!!!!! true

Android的Parcelable

实现Parcelable接口,并且需要重写describeContents、writeToParcel方法。还需要创建。。。唉好麻烦。放弃了,集成个Parcelable插件自动生成。。。

public class A implements Parcelable {
  	//我自己写的==================start
    private String name;
  
  	public A(String name) {
    	this.name = name;
    }

    @Override
    public String toString() {
    	return "A{" +
    		"name='" + name + '\'' +
    		'}';
    }
    //我自己写的==================end

  	//都是插件生成的=====================
    @Override
    public int describeContents() {
        return 0;
    }

    @Override
    public void writeToParcel(Parcel dest, int flags) {
        dest.writeString(this.name);
    }

    public void readFromParcel(Parcel source) {
        this.name = source.readString();
    }

    public A() {}

    protected A(Parcel in) {
        this.name = in.readString();
    }

    public static final Parcelable.Creator<A> CREATOR = new Parcelable.Creator<A>() {
        @Override
        public A createFromParcel(Parcel source) {
            return new A(source);
        }

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


持久化保存Parcelable

Parcelable是基于内存的,序列化到内存中。那有没有办法序列化到文件里边呢?有,但好麻烦,一起来康康。

public class PTest {
  	//写入方法
    public static void write(){
        try {
            File file = new File("sdcard/p_test.txt");
            if(!file.exists()){
                file.createNewFile();
            }

            FileOutputStream fos = new FileOutputStream(file); //创建一个文件流
          	//Parcelable序列化的核心类是Parcel
          	//通过obtain方法拿到一个空的Parcel对象(这里用了享元模式,这跟Handler中的取消息Message.obtain()类似)
            Parcel parcel = Parcel.obtain();
			//传入序列化的目标
            parcel.writeValue(new A("llk"));
			//marshall()是返回byte[]序列化结果(进程间通讯也是围绕这个返回数据进行的)
          	//然后我们再把byte[]写入到文件中
            fos.write(parcel.marshall());
            parcel.recycle(); //释放parcel

            fos.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

  	//读取方法
    public static void read(Context context){
        try {
            File file = new File("sdcard/p_test.txt");
            if(!file.exists()){
                Log.e("llk", "no file");
                return;
            }

            FileInputStream fis = new FileInputStream(file);
            Parcel parcel = Parcel.obtain();

            byte[] bytes = new byte[fis.available()]; //设置byte[]长度
            fis.read(bytes); //将文件流数据读取出来
						
          	//unmarshall()是反序列化方法,将byte[]数据序列化
            parcel.unmarshall(bytes, 0, bytes.length);
          	//注意!!!这里必须在反序列化之后恢复指针位置,不然read不到数据。
          	//因为在unmarshall()方法执行完后指针会移位。所有这里需要恢复。
            parcel.setDataPosition(0);

          	//parcel还需要传入类加载器。这里直接传入我们代码用的类加载器即可
            Object obj = parcel.readValue(context.getClassLoader());
            Log.e("llk", "read a=" + obj); //输出-> read a=A{name='llk'}

            parcel.recycle(); //释放parcel

            fis.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

Parcelable默认生成的东西有些不能去掉

怎么要实现Parcelable序列化要生成那么多东西,好麻烦啊。

就不能不要这些东西,ok那我们把它去掉康康。

		//把默认构造函数、readFromParcel方法去掉后运行。好像没啥问题。
		public void readFromParcel(Parcel source) {
        this.name = source.readString();
		}

		public A() {}

		//再把A(Parcel in)构造函数、CREATOR去掉后运行。卧槽,报错。。。
		//android.os.BadParcelableException: Parcelable protocol requires a Parcelable.Creator object called CREATOR on class com.llk.kt.s.PTest$A
		//惹,这两个不能去。
 		protected A(Parcel in) {
        this.name = in.readString();
    }

    public static final Parcelable.Creator<A> CREATOR = new Parcelable.Creator<A>() {
        @Override
        public A createFromParcel(Parcel source) {
            return new A(source);
        }

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

子类与父类的序列化关系
//将A中的name字段作用域改为protected
//B继承A,A类有实现Parcelable
static class B extends A{
        private int age;

        public B(){
            super("llk");
            this.age = 18;
        }

        @Override
        public String toString() {
            return "B{" +
                    "name='" + name + '\'' +
                    ", age=" + age +
                    '}';
        }
    }

使用上边持久化的代码
输出->
  read a=A{name='llk'} 

没错,Parcelable序列化没有继承关系,子类不能享用父类的实现。


Parcelable使用的一些坑

① 序列化与反序列化变量的顺序必须一致

				//假设name="llk",type=10086, l=1110L
				@Override
        public void writeToParcel(Parcel dest, int flags) {
            dest.writeString(this.name);
            dest.writeInt(this.type);
            dest.writeLong(this.l);
        }				

				protected A(Parcel in) {
            this.name = in.readString();
            this.type = in.readInt();
            this.l = in.readLong();
        }

				输出 ->
  				read a=A{name='llk', type=10086, l=1110}

				//如果把反序列化时,读取的顺序改变。后运行
				protected A(Parcel in) {
            this.l = in.readLong();
          	this.type = in.readInt();
          	this.name = in.readString();
        }

				输出 -> 
          read a=A{name='null', type=0, l=12884911974}

从输出看出,读不到正确的数据了。因为Parcelable序列化是按照writeToParcel方法里边的执行,按顺序一个个取写入的,不存在key:value的格式,你是什么数据就直接写什么。读取的时候也是按照A(Parcel in)里边的顺序一个个读出来,顺序变了就会读不到正确的数据。


Serializable 与 Parcelable的比较


序列化后的大小

以上边最简单的A类为例(变量只有name一个)

用serializable序列化方式保存到文件

在这里插入图片描述

用parcelable序列化方式保存到文件

在这里插入图片描述

可以看出,serializable序列化方式会更大一些,不过两者差别也不算特别大。


序列化的速度

在同一台机器,用不同方式的序列化写入文件、读取文件进行1000次测试计算耗时。

serializable序列化方式的测试代码

public static void test(){
        String path = "sdcard/s_test/";
        File pF = new File(path);
        if (!pF.exists()){
            pF.mkdir();
        }

        new Thread(new Runnable() {
            @Override
            public void run() {
                try {
                    long w_s = System.currentTimeMillis();
                    for (int i = 0; i < 1000; i++){
                        File file = new File(path + "s_" + i + ".txt");
                        if (!file.exists()){
                            file.createNewFile();
                        }

                        //写入
                        ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream(file));
                        oos.writeObject(new A("llk"));
                        oos.close();
                    }
                    Log.e("llk", "Serializable write end " + (System.currentTimeMillis() - w_s));

                    long r_s = System.currentTimeMillis();
                    for (int i = 0; i < 1000; i++){
                        File file = new File(path + "s_" + i + ".txt");

                        //读取
                        ObjectInputStream ois = new ObjectInputStream(new FileInputStream(file));
                        Object obj = ois.readObject();
                        Log.e("llk", "read s_" + i + "=" + obj);

                        ois.close();
                    }
                    Log.e("llk", "Serializable read end " + (System.currentTimeMillis() - r_s));
                } catch (IOException | ClassNotFoundException e) {
                    e.printStackTrace();
                }
            }
        }).start();
    }

parcelable序列化方式的测试代码

public static void test(Context context){
        String path = "sdcard/p_test/";
        File pF = new File(path);
        if (!pF.exists()){
            pF.mkdir();
        }

        new Thread(new Runnable() {
            @Override
            public void run() {
                try {
                    long w_s = System.currentTimeMillis();
                    for (int i = 0; i < 1000; i++){
                        File file = new File(path + "p_" + i + ".txt");
                        if (!file.exists()){
                            file.createNewFile();
                        }

                        //写入
                        FileOutputStream fos = new FileOutputStream(file);
                        Parcel parcel = Parcel.obtain();
                        parcel.writeValue(new A("llk"));
                        fos.write(parcel.marshall());
                        parcel.setDataPosition(0);
                        parcel.recycle();
                        fos.close();
                    }
                    Log.e("llk", "Parcelable write end " + (System.currentTimeMillis() - w_s));

                    long r_s = System.currentTimeMillis();
                    for (int i = 0; i < 1000; i++){
                        File file = new File(path + "p_" + i + ".txt");

                        //读取
                        FileInputStream fis = new FileInputStream(file);
                        Parcel parcel = Parcel.obtain();

                        byte[] bytes = new byte[fis.available()];
                        fis.read(bytes);
                        parcel.unmarshall(bytes, 0, bytes.length);
                        parcel.setDataPosition(0);

                        Object obj = parcel.readValue(context.getClassLoader());
                        Log.e("llk", "read p_" + i + "=" + obj);

                        parcel.recycle();

                        fis.close();
                    }
                    Log.e("llk", "Parcelable read end " + (System.currentTimeMillis() - r_s));
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }).start();
    }

测试结果

04-06 11:48:09.380 1763-1811/com.llk.kt E/llk:Serializable write end 1952
04-06 11:48:09.735 1763-1811/com.llk.kt E/llk:Serializable read end 355

04-06 11:46:40.276 1763-1796/com.llk.kt E/llk:Parcelable write end 962
04-06 11:46:40.516 1763-1796/com.llk.kt E/llk:Parcelable read end 240

可以看出serializable在序列化过程的耗时是比较长的(serializable在序列化过程用了大量反射,怎么可能不耗时)。而两者的反序列化耗时相差不大。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值