欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

Android中序列化与反序列化

程序员文章站 2024-02-26 21:18:40
...

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

序列化概念和应用

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

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

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

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


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

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

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

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

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

Serializable类

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

public interface Serializable {
}

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

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

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

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

Java中使用Serializable

Android中序列化与反序列化

盗取了一张大神的图,我们在学习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,两种内属性只要有对象,那么对应对象的类一定也要实现序列化。



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