[Android]序列化原理Serializable

news2024/9/28 21:27:11

引入

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

 

 

 
 

 

 


                

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/139383.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【服务器数据恢复】断电导致服务器故障的数据恢复案例

服务器数据恢复环境: 某品牌服务器,12块硬盘组成raid5磁盘阵列,存储普通文件。 服务器故障: 机房供电不稳定导致服务器断电,管理员重启服务器后发现服务器无法正常使用。 根据用户描述,北亚服务器数据恢复工…

使用vscode在CMake工程中集成gtest共享库进行单元测试

使用vscode在CMake工程中集成gtest共享库做单元测试一、概述二、工程内容清单三、CMakeLists.txt内容说明四、构建工程一、概述 本文主要介绍如何在一个多层次目录结构的CMAKE工程中以共享库的形式集成gtest进行单元测试。 关于如何使用CMake管理多层次目录结构的CMake工程&a…

FFmpeg 解复用实战

1.封装格式相关函数 avformat_alloc_context():负责申请一个AVFormatContext结构的内存,并进行简单初始化,这个函数可以不用手动调用,内部会自动调用。 avformat_free_context():释放该结构里的所有东西以及该结构本身&#xff0…

再学C语言24:分支和跳转——逻辑运算符和条件运算符

一、多重选择else if 功能&#xff1a;在两个以上的语句中作出选择 示例代码&#xff1a; #include <stdio.h> int main(void) {float score;printf("Please enter your score: \n");scanf("%f", &score);if(score < 60)printf("不及…

SSM基础整合

SSM基础整合 文章目录SSM基础整合[toc]SSM基础整合SSM整合流程表现层数据封装设置统一数据返回结果类定义code类优化Controller优化后的返还结果异常处理器异常处理项目异常处理方案项目异常分类处理SSM基础整合 SSM整合流程 创建工程 SSM整合 Spring SpringConfig Configur…

CAS 的使用场景 CAS的ABA问题的优化 以及 synchronized 的三大优化

目录 &#x1f388;专栏链接:多线程相关知识详解 一.什么是CAS 二.CAS最常用的两个场景 Ⅰ.实现原子类 Ⅱ.实现自旋锁 三.CAS的ABA问题 四.优化解决CAS的ABA问题 五.synchronized的优化 Ⅰ.锁升级/锁膨胀 Ⅱ.锁消除 Ⅲ.锁粗化 一.什么是CAS CAS&#xff08;Compar…

p2深度学习基本概念简介下笔记

3. 第 1 讲&#xff1a;深度学习基本概念简介下_哔哩哔哩_bilibili 线性模型太简单&#xff0c;我们需要比较复杂的模型。 不管怎样弄&#xff0c;蓝色描述不出红色的线 强调model bias 无法模拟真实的状况 写出一个有未知参数更复杂的model 观察红色的曲线 &#xff1a;可…

你知道数据在内存中是如何存储的嘛?

&#x1f996;作者&#xff1a;学写代码的恐龙 &#x1f996;博客主页&#xff1a;学写代码的恐龙博客主页 &#x1f996;专栏&#xff1a;【初级c语言】 &#x1f996;语录&#xff1a;❀未来的你&#xff0c;一定会感谢现在努力奋斗的自己❀ 深度剖析数据在内存中的存储一、…

数据结构精讲——排序(二)

数据结构精讲——排序&#xff08;二&#xff09; 排序的分类 上节课我们已经了解过插入排序和选择排序&#xff0c;这节课主要讲的是快排排序&#xff0c;冒泡排序应该都很熟悉&#xff0c;少提一下吧。 冒泡排序 冒泡排序是我们刚接触编程时就学习的排序方法&#xff08;还…

达梦数据库实施、运维与使用经验(一)

一、使用Xshell输入Insert语句&#xff0c;回车执行系统没反应 问题解决思路 查下数据库的活动会话数并登录数据库服务器看数据库的资源负载 select * from v$sessions where stateACTIVE;在数据库Xshell中输入语句查询数据库磁盘是否满了 df -lh上图中的第一个命令是用来查…

C++的IO流

目录 1. 流是什么 2. CIO流 2.1 C标准IO流 2.2 C文件IO流 2.2.1 简单读取文件流 2.2.2 C读取文件巧妙之处 3. stringstream的简单介绍 1. 流是什么 “流”即是流动的意思&#xff0c;是物质从一处向另一处流动的过程&#xff0c;是对一种有序连续且具有方向性的数据&am…

【我的渲染技术进阶之旅】关于OpenGL纹理压缩的相关资料

文章目录一、为啥要了解压缩纹理1.1 为啥要使用压缩纹理1.2 如何自定义压缩纹理以及使用压缩纹理的效果1.2.1 使用压缩纹理节省显存1.2.2 自定义压缩纹理&#xff1a;将压缩好的纹理数据保存在本地1.2.3 使用自定义的压缩纹理1.2.3 示例原理二、纹理压缩相关知识2.0 什么是压缩…

FOLOLane论文阅读

论文地址&#xff1a;https://arxiv.org/pdf/2105.13680.pdf 一、论文创新点 主流的车道线检测方法使用语义分割和聚类实现&#xff0c;但像素级的输出存在信息冗余&#xff0c;同时会带来大量噪声。而该论文使用两个分支&#xff0c;一个输出heatmap用于判断像素是否是关键点…

干货 | 初窥 Pytest 测试框架,基础薄弱也能轻松 hold 住

pytest 是一个成熟的全功能Python测试工具&#xff0c;可以帮助您编写更好的程序。它与 python 自带的 unittest 测试框架类似&#xff0c;但 pytest 使用起来更简洁和高效&#xff0c;并且兼容 unittest 框架。pytest 能够支持简单的单元测试和复杂的功能测试&#xff0c;pyte…

java中修改List的对象元素时碰到的坑

坑 case1 case1:Data class Person {Integer age;String name;public Person(Integer age, String name) {this.age age;this.name name;} } List<Person> v1List new ArrayList<>(); v1List.add(new Person(11, "小刚")); v1List.add(new Person(1…

前端基础(五)_运算符(算术运算符、赋值运算符、比较运算符、逻辑运算符、三目运算符、运算符优先级和结合性、数据类型的隐式转换)

一、算术运算符 算术运算符即&#xff1a;加&#xff08;&#xff09;减&#xff08;-&#xff09;乘&#xff08;*&#xff09;除&#xff08;/&#xff09;取余&#xff08;%&#xff09;加加&#xff08;&#xff09;减减&#xff08;–&#xff09; 算术运算符里比较特殊的…

CleanMyMacX软件有哪些优缺点?值不值得下载

CleanMyMac X 2023是一款可靠且功能强大的Mac清洁工具工具&#xff0c;他可以让你随时检查Mac电脑的健康情况&#xff0c;并删除电脑中的垃圾文件&#xff0c;来腾出存储空间&#xff0c;保持Mac系统的整洁。至问世以来&#xff0c;CleanMyMac 系统倍受国内外用户推崇&#xff…

【4】SCI易中期刊推荐——神经科学研究(中科院4区)

🚀🚀🚀NEW!!!SCI易中期刊推荐栏目来啦 ~ 📚🍀 SCI即《科学引文索引》(Science Citation Index, SCI),是1961年由美国科学信息研究所(Institute for Scientific Information, ISI)创办的文献检索工具,创始人是美国著名情报专家尤金加菲尔德(Eugene Garfield…

C#使用迷宫地图来模拟新冠疫情的传播速度(一)

国家开始发布疫情放开政策&#xff0c;本人于2022-12-21开始感染并发高烧。 最近才康复。有感于此 我们用初始感染源来影响九宫网格来查看新冠的传播速度 小游戏规则如下&#xff1a; 一个感染源 可以传播附近相邻的8个网格【类似于扫雷】&#xff0c;假如每个感染源一天只…

Vue--》setup、ref、reactive函数使用讲解

目录 setup ref函数 reactive函数 Vue3中的响应式原理 setup Vue3中的一个新的配置项&#xff0c;值为一个函数。组件中所用到的数据、方法等等&#xff0c;均要配置在setup中。setup函数的两种返回值&#xff0c;如下&#xff1a; 若返回一个对象&#xff0c;则对象中的…