前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >专栏 >漫谈序列化—使用、原理、问题

漫谈序列化—使用、原理、问题

作者头像
码上积木
发布于 2021-02-08 11:13:52
发布于 2021-02-08 11:13:52
90800
代码可运行
举报
文章被收录于专栏:码上积木码上积木
运行总次数:0
代码可运行

前言

天天跟我说给我介绍对象对象,对象在哪里?哪里有对象?

你倒是把对象拿给我看看啊!

拿去拿去 :

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
{
 "name": "小丽",
 "age": "22",
 "sex": "女"
}

我去~

序列化概念

说到对象,是一个比较宽泛的概念,简单的说,他就是类的一个实例,有状态和行为,存活在内存中,一旦JVM停止运行,对象的状态也会丢失。

那么如何将这个对象当前状态进行一个记录,使其可以进行存储和传输呢?这就要用到序列化了:

序列化 (Serialization)是将对象的状态信息转换为可以存储或传输的形式的过程

比如一个User对象,名字为小丽,年龄22,性别为女。现在要把这个User对象保存下来,不然要是这个对象被别人改成了男可咋办。

所以我们就可以把它当前的状态信息转化成一种固定的格式,比如json格式

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
{
 "name": "小丽",
 "age": "22",
 "sex": "女"
}

所以上述的例子就是一个序列化过程,本身这个User对象存活在内存中,是无法直接进行数据持久化的,所以我们需要一些序列化的方式让它可以进行保存传输:

比如xml、JSON、Protobuf、Serializable、Parcelable,这些都是可以进行序列化的方式。

所以关于序列化我们就有很多问题了:

  • java有Serializable的前提下,Android为什么设计出了Parcelable?
  • Parcelable一定比Serializable快吗?
  • 为什么Java提供了Serializable的序列化方式,而不是直接使用json或者xml?
  • Serializable、Parcelable、Json等序列化方式我们该怎么选择?

带着这些问题,我们去看看序列化的世界。

Serializable

先说说Java中自带的序列化方式——Serializable

Serializable是java.io包中定义的、用于实现Java类的序列化操作而提供的一个语义级别的接口

只要我们实现Serializable接口,那么这个类就可以被ObjectOutputStream转换为字节流,也就是进行了序列化。

使用

java:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
public class User implements Serializable {
    private static final long serialVersionUID=519067123721561165l;
    
    private int id;

    public int getId() {
        return id;
    }

    public void setId(int id) {
        this.id = id;
    }
}

kotlin:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
data class User( 
    val id: Int
) : Serializable

serialVersionUID

这个变量如果不写,系统也会自动生成。它的作用在于标示这个数据对象的一致性。

当序列化的时候,系统会把当前类的serialVersionUID写入序列化的文件中,当反序列化的时候会去检测这个serialVersionUID,看他是否和当前类的serialVersionUID一致,一样则可以正常反序列化,如果不一样就会报错了。

如果我们不写的话,在我们修改类的某些属性之后,serialVersionUID就会改变。

所以我们手动指定serialVersionUID后,就能在修改类之后,让系统认识序列化的过程中标示这是同一个类,从而保证最大限度来恢复数据。

原理

在Serializable的注释中有提到,如果要想在序列化过程中做一些特殊的操作,可以实现这几个特殊方法:

  • writeObject(),负责写入对象的特定类,以便相应的readObject方法可以恢复它
  • readObject(),负责从流中读取并恢复类字段

所以这两个方法其实就是Serializable实现的关键。首先看看写入方法writeObject(伪代码):

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
private void writeObject(){
    //获取类的描述信息ObjectStreamClass(里面包含了类名称、类字段、serialVersionUID等,用到大量反射)
     desc = ObjectStreamClass.lookup(cl, true);
     //写入元数据TC_OBJECT,代表是一个新对象
     bout.writeByte(TC_OBJECT);
     //写入描述信息(从父类写到子类)
     writeClassDesc(desc, false);
     //写入serialVersionUID,serialVersionUID为空的情况下,序列化机制就会调用一个函数根据类内部的属性等计算出一个hash值
     getSerialVersionUID();
     //执行JVM的序列化操作
     defaultWriteFields();
}


private void defaultWriteFields(Object obj, ObjectStreamClass desc){
    //写入基本数据类型
    bout.write(primVals, 0, primDataSize, false);

    //写入引用数据类型(又重新调用了writeObject方法)
    Object[] objVals = new Object[desc.getNumObjFields()];
    for (int i = 0; i < objVals.length; i++) {
        writeObject(objVals[i],fields[numPrimFields + i].isUnshared());
    }
}

写入数据的流程基本就这些,可以看到Serializable序列化的过程,其实就是一个写入流的过程。然后就可以根据情况将二进制流保持为文件,或者包装成ByteArrayOutStream写入到内存中进行传输。

所以Serializable使用的范围比较广,可以作为文件保存下来,也可以作为二进制流对象用于内存中的传输。但是由于用到反射、IO,而且大量的临时变量会引起频繁的GC,所以效率不算高。

所以,为了提高在Android中对象传输的效率呢,Android就采用了新的序列化方式——Parcelable

Parcelable

Parcelable是Android为我们提供的序列化的接口,是为了解决Serializable在序列化的过程中消耗资源严重,而Android本身的内存比较紧缺的问题,但是用法较为繁琐,主要用于内存中数据的传输。

使用

java:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
public class User implements Parcelable {
    private int id;

    protected User(Parcel in) {
        id = in.readInt();
    }

    @Override
    public void writeToParcel(Parcel dest, int flags) {
        dest.writeInt(id);
    }

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

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

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

    public int getId() {
        return id;
    }

    public void setId(int id) {
        this.id = id;
    }
}

kotlin:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
androidExtensions {
    experimental = true
}

@Parcelize
data class User(val name: String) : Parcelable

原理

先说说Parcelable写法中这几个方法参数的意思:

  • createFromParcel,User(Parcel in) ,代表从序列化的对象中创建原始对象
  • newArray,代表创建指定长度的原始对象数组
  • writeToParcel,代表将当前对象写入到序列化结构中。
  • describeContents,代表返回当前对象的内容描述。如果还有文件描述符,返回1,否则返回0。

好了,在了解Parcelable原理之前,我们先要了解下Parcel

Parcel是一个容器,它主要用于存储序列化数据,然后可以通过Binder在进程间传递这些数据

所以Parcel就是可以进行IPC通信的容器,同样底层也是用到了Binder。(Binder在Android中真是无处不在啊)

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
//写入数据
Parcel parcle = Parcel.Obtain();
parcel.writeString(String val);

//读取数据
parcel.setDataPosition(i);
parcel.readString();

再往底层就是Binder的原理了,也就是将数据写到内核的共享内存中,然后其他进程可以从共享内存中进行读取。

Parcelable的实现就是基于这个Parcel容器,还记得刚才的几个方法吗:

  • writeToParcel,写入数据到Parcel容器。
  • new User(in),从Parcel容器读取数据。

Parcelable的原理就是如此啦。

思考问题

介绍完了两种序列化方式,我们再来看看文章开头的这些问题。

在java有Serializable的前提下,Android为什么设计出了Parcelable?

java中的序列化方式Serializable效率比较低,主要有以下原因:

  • Serializable在序列化过程中会创建大量的临时变量,这样就会造成大量的GC。
  • Serializable使用了大量反射,而反射操作耗时。
  • Serializable使用了大量的IO操作,也影响了耗时。

所以Android就像重新设计了IPC方式Binder一样,重新设计了一种序列化方式,结合Binder的方式,对上述三点进行了优化,一定程度上提高了序列化和反序列化的效率。

Serializable、Parcelable、Json等序列化方式我们该怎么选择?

先说说序列化的用处,主要用在三个方面:

1、内存数据传输

内存传输方面,主要用Parcelable。一是因为Parcelable在内存传输的效率比Serializable高。二是因为在Android中很多传输数据的方法中,自带了对于Serializable、Parcelable类型的传输方法。比如:

  • Bundle.putParcelable,
  • Intent putExtra(String name, Parcelable value)

等等吧,基本上对象传输的方法都支持了,所以这也是Parcelable的优势。

2、 数据持久化(本地存储)

如果只针对Serializable和Parcelable两种序列化方式,需要选择Serializable。

首先,Serializable本身就是存储到二进制文件,所以用于持久化比较方便。而Parcelable序列化是在内存中操作,如果进程关闭或者重启的时候,内存中的数据就会消失,那么Parcelable序列化用来持久化就有可能会失败,也就是数据不会连续完整。而且Parcelable还有一个问题是兼容性,每个Android版本可能内部实现都不一样,知识用于内存中也就是传递数据的话是不影响的,但是如果持久化可能就会有问题了,低版本的数据拿到高版本可能会出现兼容性问题。

但是实际情况,对于Android中的对象本地化存储,一般是以数据库、SP的方式进行保存。

3、 网络传输

而对于网络传输的情况,一般就是使用JSON了。主要有以下几点原因:

  • 1、轻量级,没有多余的数据。
  • 2、与语言无关,所以能兼容所有平台语言。
  • 3、易读性,易解析。

Parcelable一定比Serializable快吗?

正常情况下,对象在内存中进行传输确实是Parcelable比较快,但是Serializable是有缓存的概念的,有人做了一个比较有趣的实验:

当序列化一个超级大的对象图表(表示通过一个对象,拥有通过某路径能访问到其他很多的对象),并且每个对象有10个以上属性时,并且Serializable实现了writeObject()以及readObject(),在平均每台安卓设备上,Serializable序列化速度大于Parcelable 3.6倍,反序列化速度大于1.6倍.

具体原因就是因为Serilazable的实现方式中,是有缓存的概念的,当一个对象被解析过后,将会缓存在HandleTable中,当下一次解析到同一种类型的对象后,便可以向二进制流中,写入对应的缓存索引即可。但是对于Parcel来说,没有这种概念,每一次的序列化都是独立的,每一个对象,都当作一种新的对象以及新的类型的方式来处理。

具体过程可以看看这篇:https://juejin.cn/post/6854573218334769166

为什么Java提供了Serializable的序列化方式,而不是直接使用json或者xml?

我觉得是历史遗留问题。

有的人可能会想到各种理由,比如可以标记哪些类可以被序列化。又或者可以通过UID来标示反序列化为同一个对象。等等。

但是我觉得最大的问题还是历史遗留问题,在以前,json还没有成为大家认同的数据结构,所以Java就设计出了Serializable的序列化方式来解决对象持久化和对象传输的问题。然后Java中各种API就会依赖于这种序列化方式,这么些年过去了,Java体系的庞大也造成难以改变这个问题,牵一发而动全身。

为什么我这么说呢?

主要有两点依据:

  • 一是曾经Oracle Java平台组的架构师说过,删除Java的序列化机制并且提供给用户可以选择的序列化方式(比如json)是他们计划中的一部分,因为Java序列化也造成了很多Java漏洞。具体可以参见文章:https://www.infoworld.com/article/3275924/oracle-plans-to-dump-risky-java-serialization.html
  • 二是因为在Serializable类的介绍注释中,明确说到推荐大家选择JSON 和 GSON库,因为它简洁、易读、高效。
代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
 * <h3>Recommended Alternatives</h3>
 * <strong>JSON</strong> is concise, human-readable and efficient. Android
 * includes both a {@link android.util.JsonReader streaming API} and a {@link
 * org.json.JSONObject tree API} to read and write JSON. Use a binding library
 * like <a href="http://code.google.com/p/google-gson/">GSON</a> to read and
 * write Java objects directly.

Android体系架构

连载文章、脑图、面试专题:

https://github.com/JiMuzz/Android-Architecture

参考

https://developer.android.google.cn/reference/android/os/Parcel?hl=en https://blog.csdn.net/lwj_zeal/article/details/90743500 https://juejin.cn/post/6854573218334769166#heading http://blog.sina.com.cn/s/blog_6e07f1eb0100rsax.html https://www.zhihu.com/question/283510695 https://www.infoworld.com/article/3275924/oracle-plans-to-dump-risky-java-serialization.html

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2021-01-20,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 码上积木 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
暂无评论
推荐阅读
剑指Offer(五十)-- 数组中重复的数字
在一个长度为n的数组里的所有数字都在0到n-1的范围内。数组中某些数字是重复的,但不知道有几个数字是重复的。也不知道每个数字重复几次。请找出数组中第一个重复的数字。例如,如果输入长度为7的数组[2,3,1,0,2,5,3],那么对应的输出是第一个重复的数字2。没有重复的数字返回-1。
秦怀杂货店
2022/02/15
3130
剑指Offer(五十)-- 数组中重复的数字
【剑指Offer】3. 数组中重复的数字
在一个长度为 n 的数组里的所有数字都在 0 到 n-1 的范围内。数组中某些数字是重复的,但不知道有几个数字是重复的,也不知道每个数字重复几次。请找出数组中任意一个重复的数字。
瑞新
2020/12/07
2260
剑指Offer-数组中重复的数字
package Array; /** * 数组中重复的数字 *在一个长度为n的数组里的所有数字都在0到n-1的范围内。 * 数组中某些数字是重复的,但不知道有几个数字是重复的。也不知道每个数字重复几次。请找出数组中任意一个重复的数字。 * 例如,如果输入长度为7的数组{2,3,1,0,2,5,3},那么对应的输出是第一个重复的数字2。 * 思路: * 数组中的数字都在0到n-1的数字范围内。如果数组中没有重复出现的数字,那么当数组排序后数字i就出现在数组中下标为i的元素处。那么数组中如果存在重
武培轩
2018/04/18
9120
【剑指offer】数组中重复的数字
版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
喜欢ctrl的cxk
2019/11/08
7190
剑指50-数组中重复的数字
在一个长度为n的数组里的所有数字都在0到n-1的范围内。 数组中某些数字是重复的,但不知道有几个数字是重复的。也不知道每个数字重复几次。请找出数组中任意一个重复的数字。 例如,如果输入长度为7的数组{2,3,1,0,2,5,3},那么对应的输出是第一个重复的数字2。
opencode
2022/12/26
2250
剑指offer(二):不修改数组找出重复的数字
在一个长度为 n+1 的数组里的所有数字都在 1 到 n 的范围内,所以数组中至少有一个数字是重复的。请找出数组中任意一个重复的数字,但不能修改输入的数组。例如,如果输入长度为 8 的数组 {2, 3, 5, 4, 3, 2, 6, 7},那么对应的输出是重复的数字 2 或者 3。
程序员小浩
2020/08/04
8140
剑指offer(二):不修改数组找出重复的数字
面试题03. 数组中重复的数字
找出数组中重复的数字 在一个长度为 n 的数组 nums 里的所有数字都在 0~n-1 的范围内。数组中某些数字是重复的,但不知道有几个数字重复了,也不知道每个数字重复了几次。请找出数组中任意一个重复的数字。
lexingsen
2022/02/24
1530
剑指 offer 面试题精讲图解 | 03 . 数组中重复的数字
今天分享的题目来源于 LeetCode 上的剑指 Offer 系列 面试题03. 数组中重复的数字。
五分钟学算法
2020/05/29
5100
剑指 offer 面试题精讲图解 | 03 . 数组中重复的数字
每日一题《剑指offer》数组篇之数组中重复的数字
在一个长度为n的数组里的所有数字都在0到n-1的范围内。 数组中某些数字是重复的,但不知道有几个数字是重复的。也不知道每个数字重复几次。请找出数组中任意一个重复的数字。 例如,如果输入长度为7的数组[2,3,1,0,2,5,3],那么对应的输出是2或者3。存在不合法的输入的话输出-1
终有救赎
2023/11/08
2220
每日一题《剑指offer》数组篇之数组中重复的数字
剑指offer(一):找出数组中重复的数字
在一个长度为 n 的数组里的所有数字都在 0 到 n-1 的范围内。数组中某些数字是重复的,但不知道有几个数字重复了,也不知道每个数字重复了几次。请找出数组中任意一个重复的数字。例如,如果输入长度为 7 的数组 {2, 3, 1, 0, 2, 5, 3},那么对应的输出是重复的数字 2 或者 3。
程序员小浩
2020/08/04
6720
画解算法:面试题3. 数组中重复的数字
https://leetcode-cn.com/problems/shu-zu-zhong-zhong-fu-de-shu-zi-lcof/
FunTester
2020/04/03
5030
画解算法:面试题3. 数组中重复的数字
剑指offer——数组中重复的数字
题目描述 在一个长度为n的数组里的所有数字都在0到n-1的范围内。 数组中某些数字是重复的,但不知道有几个数字是重复的。也不知道每个数字重复几次。请找出数组中任意一个重复的数字。 例如,如果输入长度为7的数组{2,3,1,0,2,5,3},那么对应的输出是第一个重复的数字2。 示例1 输入 复制 输出 复制
AI那点小事
2020/04/18
5860
数组面试题-大力出奇迹?
一般做法可能是吧数组排序,然后只需从头到尾扫描排序后的数组就可以了,复杂度是 。还可以借助哈表,判断是否存在重复数字,时间复杂度是 但是也需要 大小的空间。我们来看一种时间复杂度是 且空间复杂度是 的做法。因为数字范围是0~n-1,当没有重复数字时,数字i将出现在下标为i的位置,当有重复数字时有些位置就可能存在多个数字。从头到尾扫描这个数字中的每个数字,当扫描到下标为i的数字是,比较这个数字(设为m)是否和i相同,若相同则继续扫描下一个数字;否则拿它和下标为m的数字比较,如果相同就找到了一个重复的数字,否则交换这两个数字。
唔仄lo咚锵
2022/05/09
6110
数组面试题-大力出奇迹?
Sword To Offer 050 - 数组中重复的数字
在一个长度为n的数组里的所有数字都在0到n-1的范围内。 数组中某些数字是重复的,但不知道有几个数字是重复的。也不知道每个数字重复几次。请找出数组中任意一个重复的数字。 例如,如果输入长度为7的数组{2,3,1,0,2,5,3},那么对应的输出是第一个重复的数字2。
Reck Zhang
2021/08/11
4250
《剑指 offer》刷题记录之:数组
题目中的限制可以让我们不用去判断数组是否为空。一种比较简单的方法是先把输入的数组「排序」,再从排序的数组中找出重复的数字。但是排序一个长度为 n 的数组一般需要较大的时间与空间复杂度,以归并排序为例,其时间复杂度为
口仆
2020/08/14
8870
[PHP] 算法-数组重复数字统计的PHP实现
在一个长度为n的数组里的所有数字都在0到n-1的范围内。 数组中某些数字是重复的,但不知道有几个数字是重复的。也不知道每个数字重复几次。请找出数组中任意一个重复的数字。 例如,如果输入长度为7的数组{2,3,1,0,2,5,3},那么对应的输出是第一个重复的数字2。 1.哈希法 2.定义备用数组,作为哈希使用,key是原数组每个元素 value是出现的次数 3.遍历哈希数组 hash for i=0i;<arr.length;i++ hash[arr[i]]++; foreach hash as
唯一Chat
2019/09/10
1.2K0
每日一题《剑指offer》数组篇之调整数组顺序使奇数位于偶数前面
输入一个长度为 n 整数数组,实现一个函数来调整该数组中数字的顺序,使得所有的奇数位于数组的前面部分,所有的偶数位于数组的后面部分,并保证奇数和奇数,偶数和偶数之间的相对位置不变。
终有救赎
2023/10/16
1980
每日一题《剑指offer》数组篇之调整数组顺序使奇数位于偶数前面
数组中重复的数字---java版做的么啥意思
在一个长度为n的数组里的所有数字都在0到n-1的范围内。 数组中某些数字是重复的,但不知道有几个数字是重复的。也不知道每个数字重复几次。请找出数组中任意一个重复的数字。 例如,如果输入长度为7的数组{2,3,1,0,2,5,3},那么对应的输出是第一个重复的数字2。
名字是乱打的
2022/05/13
3200
LeetCode题解—重复数字
本来今天应该继续说Android系统方面的知识,但是我发现内容有点多,写不完了?。 那,为了保证文章的质量,所以今天就发一篇算法题顶上了~❤️ 算法题也是面试常考的项,之前也说过,虽然用到的比较少,但
码上积木
2021/01/25
4790
剑指 03— 数组中重复的数字
在一个长度为 n 的数组 nums 里的所有数字都在 0~n-1 的范围内。数组中某些数字是重复的,但不知道有几个数字重复了,也不知道每个数字重复了几次。请找出数组中任意一个重复的数字。
Java架构师必看
2021/05/14
6580
推荐阅读
相关推荐
剑指Offer(五十)-- 数组中重复的数字
更多 >
LV.0
这个人很懒,什么都没有留下~
目录
  • 序列化概念
  • Serializable
    • 使用
  • serialVersionUID
    • 原理
  • Parcelable
    • 使用
  • 原理
  • 思考问题
    • 在java有Serializable的前提下,Android为什么设计出了Parcelable?
    • Serializable、Parcelable、Json等序列化方式我们该怎么选择?
    • Parcelable一定比Serializable快吗?
    • 为什么Java提供了Serializable的序列化方式,而不是直接使用json或者xml?
  • Android体系架构
  • 参考
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档