[Android]序列化原理Serializable

简介: [Android]序列化原理Serializable

引入

我们知道,当一个程序终止时,这个程序创建的对象也会随着程序终止,那么我需要如何做才能不受其他程序的状态影响并且可以得到其他程序创建的对象状态呢?这时候我们就可以使用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开发艺术探索

 

 


目录
相关文章
|
2月前
|
安全 Android开发 Kotlin
Android经典实战之SurfaceView原理和实践
本文介绍了 `SurfaceView` 这一强大的 UI 组件,尤其适合高性能绘制任务,如视频播放和游戏。文章详细讲解了 `SurfaceView` 的原理、与 `Surface` 类的关系及其实现示例,并强调了使用时需注意的线程安全、生命周期管理和性能优化等问题。
160 8
|
22天前
|
缓存 Java 数据库
Android的ANR原理
【10月更文挑战第18天】了解 ANR 的原理对于开发高质量的 Android 应用至关重要。通过合理的设计和优化,可以有效避免 ANR 的发生,提升应用的性能和用户体验。
49 8
|
1月前
|
XML 前端开发 Android开发
Android View的绘制流程和原理详细解说
Android View的绘制流程和原理详细解说
35 3
|
2月前
|
ARouter 测试技术 API
Android经典面试题之组件化原理、优缺点、实现方法?
本文介绍了组件化在Android开发中的应用,详细阐述了其原理、优缺点及实现方式,包括模块化、接口编程、依赖注入、路由机制等内容,并提供了具体代码示例。
45 2
|
2月前
|
编解码 前端开发 Android开发
Android经典实战之TextureView原理和高级用法
本文介绍了 `TextureView` 的原理和特点,包括其硬件加速渲染的优势及与其他视图叠加使用的灵活性,并提供了视频播放和自定义绘制的示例代码。通过合理管理生命周期和资源,`TextureView` 可实现高效流畅的图形和视频渲染。
224 12
|
1月前
|
Java 调度 Android开发
Android面试题之Kotlin中async 和 await实现并发的原理和面试总结
本文首发于公众号“AntDream”,详细解析了Kotlin协程中`async`与`await`的原理及其非阻塞特性,并提供了相关面试题及答案。协程作为轻量级线程,由Kotlin运行时库管理,`async`用于启动协程并返回`Deferred`对象,`await`则用于等待该对象完成并获取结果。文章还探讨了协程与传统线程的区别,并展示了如何取消协程任务及正确释放资源。
24 0
|
3月前
|
Java
JDK序列化原理问题之Hessian框架不支持writeObject/readObject方法如何解决
JDK序列化原理问题之Hessian框架不支持writeObject/readObject方法如何解决
|
3月前
|
自然语言处理 JavaScript 前端开发
JDK序列化原理问题之FuryJDK序列化性能问题的如何解决
JDK序列化原理问题之FuryJDK序列化性能问题的如何解决
|
3月前
|
存储 安全 Java
揭秘Java序列化神器Serializable:一键解锁对象穿越时空的超能力,你的数据旅行不再受限,震撼登场!
【8月更文挑战第4天】Serializable是Java中的魔术钥匙,开启对象穿越时空的能力。作为序列化的核心,它让复杂对象的复制与传输变得简单。通过实现此接口,对象能被序列化成字节流,实现本地存储或网络传输,再通过反序列化恢复原状。尽管使用方便,但序列化过程耗时且存在安全风险,需谨慎使用。
48 7
|
3月前
|
存储 监控 数据库
Android经典实战之OkDownload的文件分段下载及合成原理
本文介绍了 OkDownload,一个高效的 Android 下载引擎,支持多线程下载、断点续传等功能。文章详细描述了文件分段下载及合成原理,包括任务创建、断点续传、并行下载等步骤,并展示了如何通过多种机制保证下载的稳定性和完整性。
107 0