引入
我们知道,当一个程序终止时,这个程序创建的对象也会随着程序终止,那么我需要如何做才能不受其他程序的状态影响并且可以得到其他程序创建的对象状态呢?这时候我们就可以使用Serializable来进行序列化把对象持久化到存储设备上或者通过网络传输给其他客户端,下面我们来探索一下如何使用Serializable来完成序列化的。
序列化和反序列化的概念
所谓的序列化就是把内存中的某个对象转换成字节流的形式,而反序列化就是把字节流转换回内存的某个对象。
也就是说:
序列化:对象->字节流
反序列化:字节流->对象
序列化的作用:把对象持久化,网络上传输数据,跨进程通信
Serializable接口
Serializable是一个序列化接口,但它是一个空接口,为对象提供标准的序列化和反序列化操作。
定义可序列化的类
定义一个类,实现Serializable接口并且声明serialVersionUID(下面会说这个的作用)即可,那么这个类就可以序列化了。
class Person(val age:Int,var name:String):Serializable {
companion object{//相当于static
private const val serialVersionUID:Long=1L
}
}
一个实现序列化的类,它的子类也是可序列化的。
实现序列化保存在数组中
实现序列化,要使用到ObjectOutputStream和ObjectInputStream的方法,序列化就是用ObjectOutputStream的把对象转换成字节流,然后把字节流放进Byte数组里,反序列化就是把Byte数组转换成对象。
//序列化的基本方法
fun test1(){
//序列化的过程
var person:Person= Person(1,"xiao")
val out = ByteArrayOutputStream()
val oos= ObjectOutputStream(out)
oos.writeObject(person)
val bs=out.toByteArray()
oos.close()
//反序列化过程
val ois=ObjectInputStream(ByteArrayInputStream(bs))
val person1:Person=ois.readObject() as Person
println("反序列化Person:person1.age=${person1.age},person1.name=${person1.name}")
}
这是运行结果:
事实上,虽然序列化出来后的person和person1的内容完全一样,但是person和person1是两个不同的对象。
实现序列化保存在文件中
使用ObjectOutputStream的writeObject方法把对象转换成字节流保存在data1文件中,然后通过ObjectInputStream的readObject方法把文件中的字节流转换成对象。
//序列化到文件
fun test2() {
try {
var person = Person(2, "xiao2",Tag("string"))
val out=ObjectOutputStream(openFileOutput("data1",Context.MODE_PRIVATE))
out.writeObject(person)
out.close()
val input=ObjectInputStream(openFileInput("data1"))
var person1=input.readObject() as Person
input.close()
println("反序列化Person:person1.age=${person1.age},person1.name=${person1.name}")
}catch (e:IOException){
e.printStackTrace()
}
}
快捷键Ctrl+Shift+A打开搜索功能,在搜索框中输入"Device File Explorer"即可找到这个工具,在这里,我们找/data/data/com.example.filepersistencetest/file/目录,打开这里的data1文件,如图所示为字节流:
serialVersionUID
serialVersionUID一般给它一个固定的值就行了
private const val serialVersionUID:Long=1L
serialVersionUID的作用:
serialVersionUID是用来辅助序列化和反序列化的过程的,原则上序列化后的数据中的serialVersionUID只有和当前类的serialVersionUID相同才能正常地被反序列化。serialVersionUID可以起到控制版本的作用。
比如:如果服务端更新了,把serialVersionUID改成了2L,那么当客户端没有更新,客户端的serialVersionUID还是为1L,客户端去进行反序列化获取服务端的对象时就会报错,那客户端只能更新版本。
可序列化类中,成员都要实现Serializable的属性状态。
我们来个例子:
定义一个类Tag不实现Serializable接口,在Person的成员里加Tag类的对象tag,那么这个tag对象就是未实现Serializable的属性状态,当你运行时就会出现报错。
data class Tag(var string:String)
class Person(val age:Int,var name:String,var tag: Tag):Serializable {//tag类没有实现Serializable接口,就会报错
companion object{//相当于static
private const val serialVersionUID:Long=1L
}
}
那,如果你让Tag类实现Serializable接口,就会运行成功。
data class Tag(var string:String):Serializable
class Person(val age:Int,var name:String,var tag: Tag):Serializable {//tag类没有实现Serializable接口,就会报错
companion object{//相当于static
private const val serialVersionUID:Long=1L
}
}
可以会有人问:既然是要求可序列化类的成员都实现Serializable,那是不是说明String类也实现Serializable?是的,String类在java中也是实现了Serializable。
有没有办法在可序列化的类中设置某个成员不进行序列化呢?
1.@Transient
当某个成员被@Transient声明后,默认序列化机制就会无视这个成员,不对这个这个成员进行序列化。
class Person(val age:Int, @Transient var name:String, @Transient var tag: Tag):Serializable {//tag类没有实现Serializable接口,就会报错
companion object{//相当于static
private const val serialVersionUID:Long=1L
}
}
成员name和tag的值为null,可见name和tag未被序列化。
2.静态变量不会被序列化
序列化的是对象,但是静态变量是属于类,静态变量优先于对象在内存中产生。
下面我们来看一下序列化过程
writeObject->writeObject0->writeOrdinaryObject->writeSerialData()(如果是实现Serializable接口)->defaultWriteFields->writeObject0(如果是写入String)->writeString
自定义序列化
序列化里面有四个方法:readObject,writeObject,readResolve,writeReplace。虽然序列化接口里面没有让你实现的方法,但是我们在使用序列化的过程中可以把这四个方法当作重载方法来使用,他们有什么用呢?在序列化的过程中,如果你想要对每个字段进行特殊处理,比如你想要在name中加点什么,就可以用这几个方法来加一些操作。
class Person(var age:Int, var name:String, var tag: Tag):Serializable {//tag类没有实现Serializable接口,就会报错
companion object{//相当于static
private const val serialVersionUID:Long=1L
}
private fun readObject(inputStream: ObjectInputStream){
println("readObject")
age=inputStream.readObject() as Int
name=inputStream.readObject() as String
tag=inputStream.readObject() as Tag
}
private fun writeObject(outputStream:ObjectOutputStream){
println("writeObject")
outputStream.writeObject(age)
outputStream.writeObject(name)
outputStream.writeObject(tag)
}
private fun readResolve():Any{
println("readResolve")
return Person(22,"${name} readResolve",Tag("string"))
}
private fun writeReplace():Any{
println("writeReplace")
return Person(21,"${name} writereplace",Tag("string"))
}
}
运行结果如图,可见:这四个方法的先后顺序:writeReplace,writeObject,readObject,readResolve。最后序列化出来的对象是readResolve方法返回的对象。得出对象的字段name为xiao writeReplace readResolve,说明了序列化的过程中先后调用了writeReplace,readResolve方法。
明明我们调用的是ObjectOutputStream的writeObject,为什么在类中定义的方法会被自动调用呢?其实,在writeObject是方法中,会查看你是否在类中写了writeObject方法,如果写了,那么就会通过反射调用类中的writeObject来写,而不是调用默认的方法。
单例对象问题
单例对象a->序列化之后->发序列化出来->单例对象b
a和b还是同一个单例对象吗?不是同一个了。
该如何让a和b是同一个单例对象呢?
反序列化的时候,直接把这个单例返回,这样保证单例被序列化时得到的还是这个单例。
class Single:Serializable{
companion object{
private const val serialVersionUID:Long=1L
private var flag=false
var single:Single?=null
@Synchronized fun getInstance():Single{
if(single==null){
single= Single()
}
return single!!
}
}
init {
if(single==null){
synchronized(Single::class){
if(!flag){
flag=true
}
else{
throw RuntimeException("单例模式被侵犯")
}
}
}
}
//解决单例序列化的问题
private fun readResolve():Any{
println("readResolve")
//反序列化的时候,直接把这个单例返回,这样保证单例被序列化时得到的还是这个单例
return single!!
}
}
接下来我们看Externalizable接口
Externalizable接口实现Serializable接口,里面有两个方法需要我们重写,一个是writeExternal方法,怎么把对象写到序列化里面去,另一个是readExternal方法,怎么把这个对象从序列化中读取出来。
定义一个类实现Externalizable接口
class Person2:Externalizable{
lateinit var name:String
var age:Int=0
//需要一个无参的构造方法
constructor()
constructor(age:Int,name: String):this(){
this.age=age
this.name=name
}
companion object{//相当于static
private const val serialVersionUID:Long=1L
}
override fun writeExternal(p0: ObjectOutput?) {
println("writeExternal")
if (p0 != null) {
p0.writeObject(name)
p0.writeObject(age)
}
}
override fun readExternal(p0: ObjectInput?) {
println("readExternal")
if (p0!=null){
name=p0.readObject() as String
age=p0.readObject() as Int
}
}
}
参考:
B站:jaryjun:的序列化基本概念 01-金狮.___1
Serializable原理以及面试点 02-金狮.___1
书籍:Android开发艺术探索