Android中序列化与反序列化

这几天在看到设计模式时,看到有关于序列化的知识,发现自己之前这块知识很欠缺,所以这花了两天仔细研究了一下,感觉这个东西还是很有趣的(当然也很有用-。+),今天给大家带来这篇文章,希望对大家有帮助。

序列化概念和应用

首先我们需要知道序列化是个什么意思。

  • 序列化:将对象转换为可传输的二进制流的过程。
  • 反序列化:把字节序列恢复成对象的过程。

我举个栗子:我们都进行过文件操作吧。我们在文件中写数字,或者写汉字,再或者写英文,这些东西都是可以直接转为二进制字节的,我们把需要的数据存储在File中,实现了数据存储到设备中,实现了数据持久化。

但是现在我有一个对象需要暂时存储到文件中,这个跟刚才的基本类型不一样了哦。所以说怎么办?


还有这个情况:我们在进行网络传输,传输账号、密码等,这些都是基本数据。那我如果想传输一个对象过去呢?到了对方电脑上之后,鬼知道你这是个什么东西。。。。

根据这两种案例,相信大家已经对序列化的应用有所了解了,主要可以分为以下几类:

  1. 存储到文件或本地数据库(如SQLite)这类持久化数据操作。
  2. 通过网络请求传输对象。
  3. 内存中数据调用(Intent,下面会多次用到)。

一句话概括,就是对象进行存储和传递的一种方法:转化为二进制流。

而进行序列化主要有两个重要的类:Serializable类和Parcelable类。我们分别来看一下。

Serializable类

首先说一下这个类,相信在Java中大家可能已经见到过这个类了。这里简单介绍一下:

扫描二维码关注公众号,回复: 1618537 查看本文章
public interface Serializable {
}

Serializable是一个标记接口,从上面的类中也可以看出来,他是一个空的接口。

我们传输时候,各种各样的类,我们需要有一个类来对他们进行统一。但是Java中是单根继承,而接口恰好弥补了这个地方。我们可以将实现的借口看做父类,这样通过Serializable类就实现了多种类的统一,同时为类打上了标签,可以通过instanceof Serializable判断是否能序列化(个人理解)。

而它使用起来也很简单,只将需要序列化的类实现Serializable类就好了,其他的我们完全不用操作。

Serializable在Java中就有了,我们先来看一下在Java中它是如何使用:

Java中使用Serializable

盗取了一张大神的图,我们在学习IO流中,有两个类是用于读写对象的——ObjectInputStream和ObjectOutputStream。

ObjectOutputStream负责将内存中的对象写入存储中。ObjectInputStream负责从本地存储中读取存入的对象。而是用这两个类,那么传入的这个类必须是序列化的。我们看一下代码就知道了:

import java.io.Serializable;

public class StudentBean implements Serializable{

	private int age;
	private String name;
	private int id;
	
	public StudentBean(int age, String name, int id) {
		super();
		this.age = age;
		this.name = name;
		this.id = id;
	}
	
	public int getAge() {
		return age;
	}
	public void setAge(int age) {
		this.age = age;
	}
	public String getName() {
		return name;
	}
	public void setName(String name) {
		this.name = name;
	}
	public int getId() {
		return id;
	}
	public void setId(int id) {
		this.id = id;
	}
	@Override
	public String toString() {
		return "StudentBean [age=" + age + ", name=" + name + ", id=" + id + "]";
	}
	
}
这是一个Bean类,实现了get和set方法,构造方法和重写了toString方法。

public static ObjectOutputStream serilizeData(StudentBean studentBean) {
	ObjectOutputStream objectOutputStream = null ;
		
	try {
		objectOutputStream = new ObjectOutputStream(
			new FileOutputStream(
				      new File("/Users/jibai/Desktop/" + StudentBean.class.getSimpleName() + ".txt")
				)
			);
			
		objectOutputStream.writeObject(studentBean);
			
	} catch (IOException e) {
		// TODO Auto-generated catch block
		e.printStackTrace();
	}
		
	return objectOutputStream;
}

这是我们将序列化的Bean类通过类输入流写入文件的方法。


public static StudentBean reverseSerilizeData(String path) {
		
	StudentBean bean = null;
		
	try {
		ObjectInputStream objectInputStream = new ObjectInputStream(
				new FileInputStream(
						new File(path)
						)
				);
			
		bean = (StudentBean) objectInputStream.readObject();
	} catch (Exception e) {
		// TODO Auto-generated catch block
		e.printStackTrace();
	}
		
	return bean;
}

这个是将本地存储对象的文件读取出来的方法。

最后看一下main方法:

public static void main(String[] args) {
	// TODO Auto-generated method stub
		
	serilizeData(new StudentBean(18, "tom", 01));
	StudentBean bean = reverseSerilizeData("/Users/jibai/Desktop/" + StudentBean.class.getSimpleName() + ".txt");
	System.out.println(bean.toString());
		
}

首先把一个StudentBean的对象存储到文件中,然后再将这个文件中的对象读取出来,我们看一下结果:


桌面确实有StudentBean这个文件,而且控制台也成功得把这个文件中的对象读出来了。

如果我们没有让StudentBean类实现Serializable接口,会怎么样。

java.io.NotSerializableException: StudentBean
	at java.io.ObjectOutputStream.writeObject0(ObjectOutputStream.java:1184)
	at java.io.ObjectOutputStream.writeObject(ObjectOutputStream.java:348)
	at MyProject.serilizeData(MyProject.java:30)
	at MyProject.main(MyProject.java:14)
java.io.WriteAbortedException: writing aborted; java.io.NotSerializableException: StudentBean
	at java.io.ObjectInputStream.readObject0(ObjectInputStream.java:1575)
	at java.io.ObjectInputStream.readObject(ObjectInputStream.java:431)
	at MyProject.reverseSerilizeData(MyProject.java:51)
	at MyProject.main(MyProject.java:15)
Caused by: java.io.NotSerializableException: StudentBean
	at java.io.ObjectOutputStream.writeObject0(ObjectOutputStream.java:1184)
	at java.io.ObjectOutputStream.writeObject(ObjectOutputStream.java:348)
	at MyProject.serilizeData(MyProject.java:30)
	at MyProject.main(MyProject.java:14)
Exception in thread "main" java.lang.NullPointerException
	at MyProject.main(MyProject.java:16)

第一行很明显,抛出了一个异常,说StudentBean类没有序列化。

Android下Serializable

关于这个实在没什么特别需要讲的,因为使用也只是实现这个接口就好了。等会在Parcelable中会顺便提一下。

小细节

其实关于Serializable还有一个知识点需要了解:我们在实现Serializable时,类会有一个序列化的值,这个值默认的情况下会随我们Bean类中属性成员变化而变化。

如果在eclipse中没有定义这个值,那么类名会有一个警告:

public class StudentBean implements Serializable{

	/**
	 * 
	 */
	private int age;
	private String name;
	private int id;

添加了UID之后:

private static final long serialVersionUID = 630907180238371889L;
	/**
	 * 
	 */
	private int age;
	private String name;
	private int id;

如果对类成员属性进行了修改,那么再次读取这个对象时,会因为UID不同而抛出异常:

java.io.InvalidClassException: StudentBean; local class incompatible: stream classdesc serialVersionUID = -8088515121892865631, local class serialVersionUID = 630907180238371889
	at java.io.ObjectStreamClass.initNonProxy(ObjectStreamClass.java:687)
	at java.io.ObjectInputStream.readNonProxyDesc(ObjectInputStream.java:1883)
	at java.io.ObjectInputStream.readClassDesc(ObjectInputStream.java:1749)
	at java.io.ObjectInputStream.readOrdinaryObject(ObjectInputStream.java:2040)
	at java.io.ObjectInputStream.readObject0(ObjectInputStream.java:1571)
	at java.io.ObjectInputStream.readObject(ObjectInputStream.java:431)
	at MyProject.reverseSerilizeData(MyProject.java:51)
	at MyProject.main(MyProject.java:15)
Exception in thread "main" java.lang.NullPointerException
	at MyProject.main(MyProject.java:16)

所以我们在实现Serializable接口同时,在类中要声明UID。


Parcelable类

Parcelable类相比于Serializable,要复杂多了,Parcelable类是Android推出的高效的序列化接口(据说和Serializable性能对比,是其10倍速度,真的假的我不知道)。

先看一下这个接口的结构:

public class RectBean implements Parcelable {


    public RectBean() {
    }

    @Override
    public int describeContents() {
        return 0;
    }

    @Override
    public void writeToParcel(Parcel dest, int flags) {
    }

    protected RectBean(Parcel in) {
    }

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

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

序列化方法和步骤

这是我们实现Parcelable接口的一系列方法。主要可以分为四步:

  1. 创建私有化构造方法(或者protected)
  2. 重写describeContents方法。
  3. 重写writeToParcel方法,这个方法是我们将对象序列化的方法。
  4. 实现Creator类,并实现createFromParcel方法和newArray方法,newArray方法不是很重要,主要看createFromParcel方法,这个方法是我们反序列化得到对象的方法。
这是几个重要的方法:

方法

作用

describeContents

返回当前对象的描述,01(大多数情况都返回0

writeToParcelParcel outint flag

将当前对象写入序列化结构,flag标识有011时标识当前对象作为返回值返回(大多数情况返回0

createFromParcelParcel in

从序列化后的对象中创建原始对象


Parcel类

在这里要介绍一下里面出现很多的一个类——Parcel。这个类翻译过来就是打包的意思,而我们序列化后的对象全都存储在了这个里面。

其实Parcelable类只是一个外壳,而真正实现了序列化的,是Parcel这个类,它里面有大量的方法,对各种数据进行序列化(下面我们会用到)。


以上是Parcel中的一部分方法(太多太多了)。

关于Parcel我们现在不用了解太多,我们只需要知道是它实现序列化和反序列化功能就够了。

不同类型属性序列化操作

下面我们向RectBean中添加属性,而这里面属性可以分为四种:

  • 除boolean之外的基本类型(int、float、char、)
  • boolean类型
  • 对象类型
  • 集合类型(List、。。。)

首先说一下,我们在通过Parcel类进行序列化和反序列化操作时,对应的不同类型的属性,需要调用不同的方法,在上面我们也看到了,Parcel众多类型的方法,就是为了对应不同类型的属性。

但是看了这么多方法,发现没有关于boolean类型的方法。所以才把boolean单拿出来。

接下来我们一个一个的看:

1.除boolean外的基本类型

public class RectBean implements Parcelable {

    private float x,y;
    
    public RectBean() {
    }

    @Override
    public int describeContents() {
        return 0;
    }

    @Override
    public void writeToParcel(Parcel dest, int flags) {
        dest.writeFloat(this.x);
        dest.writeFloat(this.y);
    }

    protected RectBean(Parcel in) {
        this.x = in.readFloat();
        this.y = in.readFloat();
    }

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

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

我给RectBean类添加了两个float的属性:x、y。看一下哪些方法需要修改。

在writeToParcel中我们调用了Parcel的readFloat方法,将两个属性序列化,存储到Parcel中。

然后在createFromParcel中,又通过readFloat中,将序列化的对象还原成原对象。

很简单,没有难度对吧,只是两个方法而已。

2.boolean类型

由于没有boolean类型存储,我们可以用byte或者int类型来进行存储:

public class RectBean implements Parcelable {

    private float x,y;
    private boolean isVisible;

    public RectBean() {
    }

    @Override
    public int describeContents() {
        return 0;
    }

    @Override
    public void writeToParcel(Parcel dest, int flags) {
        dest.writeFloat(this.x);
        dest.writeFloat(this.y);
        dest.writeByte(this.isVisible ? (byte) 1 : (byte) 0);
    }

    protected RectBean(Parcel in) {
        this.x = in.readFloat();
        this.y = in.readFloat();
        this.isVisible = in.readByte() != 0;
    }

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

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

通过一个条件运算符就解决了,让true值为1,false值为0。

3.属性为对象类型。

如果说上面的两中都是基本类型,那么这个是一个对象类型,怎么解决?

首先第一步,我们添加的对象属性对应的类,一定也要被序列化。这个应该很容易理解:

public class Point implements Parcelable {
    protected Point(Parcel in) {
    }

    public static final Creator<Point> CREATOR = new Creator<Point>() {
        @Override
        public Point createFromParcel(Parcel in) {
            return new Point(in);
        }

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

    @Override
    public int describeContents() {
        return 0;
    }

    @Override
    public void writeToParcel(Parcel dest, int flags) {
    }
}

创建一个Point类,并让它实现Parcelable接口。

public class RectBean implements Parcelable {

    private float x,y;
    private boolean isVisible;
    private Point point;

    public RectBean() {
    }

    @Override
    public int describeContents() {
        return 0;
    }

    @Override
    public void writeToParcel(Parcel dest, int flags) {
        dest.writeFloat(this.x);
        dest.writeFloat(this.y);
        dest.writeByte(this.isVisible ? (byte) 1 : (byte) 0);
        dest.writeParcelable(this.point, flags);
    }

    protected RectBean(Parcel in) {
        this.x = in.readFloat();
        this.y = in.readFloat();
        this.isVisible = in.readByte() != 0;
        this.point = in.readParcelable(Point.class.getClassLoader());
    }

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

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

然后在RectBean中添加Point类的属性。这次调用的是Parcel的readParcelable方法和writeParcelable方法。但是在反序列化时候,需要用到属性类的类加载器。

this.point = in.readParcelable(Point.class.getClassLoader());

4.集合类型的属性。

集合类型的属性切记,一定要初始化!!!!!

public class RectBean implements Parcelable {

    private float x,y;
    private boolean isVisible;
    private Point point;
    private List<Point> points  = new ArrayList<>();

    public RectBean() {
    }

    @Override
    public int describeContents() {
        return 0;
    }

    @Override
    public void writeToParcel(Parcel dest, int flags) {
        dest.writeFloat(this.x);
        dest.writeFloat(this.y);
        dest.writeByte(this.isVisible ? (byte) 1 : (byte) 0);
        dest.writeParcelable(this.point, flags);
        dest.writeTypedList(this.points);
    }

    protected RectBean(Parcel in) {
        this.x = in.readFloat();
        this.y = in.readFloat();
        this.isVisible = in.readByte() != 0;
        this.point = in.readParcelable(Point.class.getClassLoader());
        this.points = in.createTypedArrayList(Point.CREATOR);
    }

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

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

这里需要说明一下:

我们如果说List存放的是String,那么在序列化和反序列化对应的方法就是writeStringList和createStringArrayList:

dest.writeStringList(this.points);
.....
this.points = in.createStringArrayList();

如果是系统中自带的类,那么会调用writeList和readList方法,而在readList中也需要系统类的类加载器:

dest.writeList(this.points);
...
in.readList(this.points, Thread.class.getClassLoader());

而如果是自定义的类,那么会调用writeTypedList和createTypedArrayList,在createTypedArrayList中将自定义类的CREATOR传入。(说明该自定义类也需要序列化

dest.writeTypedList(this.points);
...
this.points = in.createTypedArrayList(Point.CREATOR);


以上便是Parcelable的主要使用方法了。下面我们来对两种序列化做一个总结:

Serializable和Parcelable总结

  1. 两者都是实现序列化得接口,都可以用Intent传递数据。
  2. Serializable使用时会产生大量的临时变量,进行IO操作频繁,消耗比较大,但是实现方式简单。
  3. Parcelable是Android提供轻量级方法,效率高,但是实现复杂。
  4. 一般在内存中序列画传递时选用Parcelable。在设备或网络中传递选用Serializable。
  5. 无论是Serializable还是Parcelable,两种内属性只要有对象,那么对应对象的类一定也要实现序列化。



以上便是今天的全部内容,希望对大家有所帮助,喜欢的朋友希望多多支持一下,有不同意见或者有错误的地方希望大家留言评论,谢谢大家支持!!


猜你喜欢

转载自blog.csdn.net/zy_jibai/article/details/80700710