一篇博客教会你写序列化工具

news2024/9/9 7:22:24

文章目录

    • 什么是序列化?
    • 序列化格式
    • JSON序列化
    • 精简序列化数据
    • 总结
    • 源码

什么是序列化?

总所周知,在Java语言中,所有的数据都是以对象的形式存在Java堆中。

但是Java对象如果要存储在别的地方,那么单纯的Java对象就无法满足了,必须要将Java对象转为一种可以存储的格式,这个转换的过程就是序列化。

同理而言,将一种存储的格式转换为Java对象的过程,就是反序列化。

序列化格式

序列化是一种通用的称呼,对于序列化之后转成的数据格式并没有硬性的要求,但是一般都会将格式定为字节数组,或者字符串之类通用的数据格式。

例如在Java中存在这样一个类:

public class User implements Serializable {

    private int id;
    private String username;
    private String password;
    private float money;

    public User() {
    }

    public User(int id, String username, String password, float money) {
        this.id = id;
        this.username = username;
        this.password = password;
        this.money = money;
    }

    // get、set、toString等方法省略……

使用JDK自带的序列化工具,可以将这样一个Java对象转为字节数组:

User user = new User(1, "次时代小羊", "222222", 99.44F);

ByteArrayOutputStream bos = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(bos);
oos.writeObject(user);
byte[] bytes = bos.toByteArray();
System.out.println("length:" + bytes.length);
System.out.println("bytes:" + Arrays.toString(bytes));

Java对象序列化后得到的字节数组可以写入数据库,或者文件系统中,以后如果需要使用这个Java对象,可以从数据库或者文件中将字节数组读取出来,重新反序列化为Java对象:

ByteArrayInputStream bis = new ByteArrayInputStream(bytes);
ObjectInputStream ois = new ObjectInputStream(bis);
Object object = ois.readObject();
System.out.println("object:" + object);

但是JDK自带的序列化工具虽然可以将Java对象转换为字节数组,但是这个字节数组是完全按照Java的格式来序列化的,反序列化也需要使用JDK的工具才行。

所以这种序列化方式只能在Java语言中通用。

(虽然理论上别的语言平台也可以按照JDK反序列化的方式实现这个工具,但是别人可不会惯着你!)

即便是在Java平台内部,这种序列化方式也非常笨重,因为在这个序列化得到的字节数组中序列化了非常多的与用户数据无关的对象数据。

例如上述的user对象中,用户真正关心的数据只有四个,分别是idusernamepasswordmoney,至于Java对象内部是一些数据,并不是用户真正关心的。

这个时候,我们就追求一种简洁明了,而且跨平台通用的序列化格式。

JSON序列化

在Java的早起,XML作为一种可扩展标记语言,因为它的平台无关性、可扩展性、数据遵循严格的格式,人类可读等优点,得到了Java开发者的青睐。

早期XML在Java中大行其道,很多Java对象最终都会被序列化为XML文本存储或者转发。

因为其具有平台无关性,很多语言平台或第三方库也纷纷实现了XML的标准。

不过伴随着JSON格式的数据的崛起,JSON很快就取代了XML的地位,XML具有的优点JSON都具有,而且比XML更加简洁,文本更小。

使用第三方类库Jackson,将一个Java对象序列化为字符串或者字节数组:

User user = new User(1, "次时代小羊", "222222", 99.44F);

ObjectMapper objectMapper = new ObjectMapper();
byte[] bytes1 = objectMapper.writeValueAsBytes(user);
System.out.println("length:" + bytes1.length);
System.out.println("bytes:" + Arrays.toString(bytes1));

String json = objectMapper.writeValueAsString(user);
System.out.println("length:" + json.length());
System.out.println("json:" + json);

Jackson序列化得到的字符串或者字节数组,同样可以存储到数据库,或者通过网络转发出去,并被支持JSON格式的语言平台解析。

Jackson序列化为字符串和字节数组本质上并没有区别,序列化为字节数组,其实就是将序列化得到的字符串转为字节数组。

User user1 = objectMapper.readValue(bytes1, User.class);
System.out.println("user1:" + user1);
User user2 = objectMapper.readValue(json, User.class);
System.out.println("user2:" + user2);

JSON序列化是目前一种比较理想的序列化方式,各种语言平台,甚至是数据库都对JSON格式的数据有支持。

精简序列化数据

我们先来看一下使用Jackson序列化得到的字符串和字节数组数据:

{"id":1,"username":"次时代小羊","password":"222222","money":99.44}
123341051003458494434117
{"id":1,"u
11510111411097109101345834
sername":"
-26-84-95-26-105-74-28-69-93-27
-113-84-25-66-11834443411297
","pa
11511511911111410034583450
ssword":"2
5050505050344434109111
22222","mo
11010112134585757465252
ney":99.44
125
}

以上就是使用Jackson序列化得到的字符串,以及字节数组和字符串字符的对应表,其中中文字符使用三个字节表示。

按照上面的对照表,我们可以知道序列化为字节数组的时候,JSON格式的字符串都序列化了哪些内容。

而我们前面也说过,用户真正关心的数据只有四个,分别是idusernamepasswordmoney,而这四个数据的名称(字段名)对于数据本身而言,只是做一个定位的作用。

如果我们可以预先确定序列化数据的字段顺序,而后反序列化的时候也已同样的顺序进行解析,是否就能够抛弃JSON格式中的字段名称,只将数据本身进行序列化?

比如将JSON格式的字符串缩减成下面的形式:

1次时代小羊22222299.44

但是这也有一个问题,那就是我们无法确定每个数据的长度,比如username这个字段,它对应的值到底是次时代小羊,还是次时代小羊222222,甚至可能还是次时代小羊22222299.44

所以为了确定数据的长度,我们还必须加入数据的长度作为表示,因为数据的长度都可以使用整形类型的数据进行表示。

比如我们可以约定,字符开始的第一个小于等于9的数字为数据长度,我们这样就可以很清晰的定位并分隔数据。

115次时代小羊6222222599.44

当然,这只是字符串可以这样表示,如果使用字节数组,那么我们可以根据单个数据的最大字节数,约定byte或者int类型的数据来表示长度。

byte支持单个数据的字节数组长度为255(2 ^ 8-1),int支持单个数据的字节数组长度为4294967295(2 ^ 32-1),因为数据长度只可能为正整数,所以使用无符号数可以最大程度支持。

而且一些特定类型的数据长度我们可以不需要确定,一些语言平台已经规定了这些数据类型的字节长度,比如在Java语言中,intfloat类型的数据长度为4,那么我们只需要规定一些不确定的数据的字节长度即可,比如字符串类型,字节数组类型等等。

我们可以重新设计简化格式:

长度数据类型是否需要确定数据长度说明
41int语言平台规定,不需要int类型的数据字节长度为4
15次时代小羊字符串需要UTF-8编码下一个中文字节长度为3(或者4)
6222222字符串需要UTF-8编码兼容ASCII编码,所以长度为6
499.44float语言平台规定,不需要float类型的数据字节长度为4

数据总长度为29,加上一共四个数据,每个数据对应的字节数组长度各占一个int类型数据的字节长度,所以最终长度为37(29+4+4)。

最终得到序列化后的字节数组:

100015000
115
-26-84-95-26-105-74-28-69
-93-27-80-113-25-66-1186
6
0005050505050
22222
5072-31-5866
299.44

得到序列化后的字节数组之后,反序列化只需要按照原定的顺序,即可正确读取数据。

比如:

  • 1、读取int类型的字段id数据,得到数据值:1

  • 2、读取字符串类型的字段username数据对应的字节数组长度,得到数据值:15

    • 2.1、向后读取长度为15的字节数组,得到数据值:次时代小羊
  • 3、读取字符串类型的字段password数据对应的字节数组长度,得到数据值:6

    • 3.1、向后读取长度为6的字节数组,得到数据值:222222
  • 4、读取float类型的字段money数据,得到数据值:99.44

至此,精简序列化数据的方式都可以正确序列化和反序列化,而且序列化得到的字节数组长度更小。

Google的ProtoBuf和开源的MessagePack其实都是使用了类似的精简序列化的方式,不过这些开源的序列化框架更加成熟可靠,内部的实现细节也更加全面。

总结

以上三种序列化方式,各有优点,也各有缺点,我们在这里总结一下:

序列化方式JDK序列化JSON序列化精简序列化
序列化结果字节数组字符串或者字节数组字节数组
是否支持跨平台不支持支持支持
是否需要额外约定不需要不需要需要
人类可读性优秀
优点JDK自带,无需第三方依赖,对Java语言开发者友好全平台通用,序列化结果简洁工整,人类可读性强全平台通用,序列化结果精简
缺点只支持JDK平台,序列化结果笨重一些语言平台不支持JSON格式,需要第三方库扩展性较差,在需要改动序列化对象的时候,序列化和反序列化方式也需要同时改动

以上三种序列化方式的优缺点已经一一列名,我们可以根据自身需要进行选择。

如果你进行的是一些通信软件、游戏等等对网络性能要求高,且通信格式并不会发生重大改变的开发工作,那么可以考虑选择第三种精简序列化的方式,开源平台上也有很多这种类型的序列化框架的实现,比如前面提到过的ProtoBufMessagePack等等。

如果你进行是一些Web网站等一些扩展性要求较高的开发工作,那么建议选择JSON序列化的方式,即便是一些不支持JSON格式的语言平台,同样有很多优秀的第三方库对其进行了支持,比如Jackson等等。

至于JDK序列化的方式,如果你有兴趣,或者开发的项目本身不支持其他序列化方式,那么也是一个不错的选择~~~

源码

在文章的最后,我在这里附上一个本人使用Java写的简单的序列化工具,有兴趣的同学可以参考一下。

public class Bytes {

    private static final int ONE_LENGTH = 1;
    private static final int TWO_LENGTH = ONE_LENGTH << 1;
    private static final int FOUR_LENGTH = ONE_LENGTH << 2;
    private static final int EIGHT_LENGTH = ONE_LENGTH << 3;

    private static final byte BOOLEAN_TRUE = 1;
    private static final byte BOOLEAN_FALSE = 0;

    private byte[] data;
    private int readIndex;
    private int writeIndex;

    public Bytes() {
        this.readIndex = 0;
        this.writeIndex = 0;
    }

    public Bytes(byte[] bytes) {
        this.data = bytes;
        this.readIndex = 0;
        this.writeIndex = bytes.length;
    }

    public byte[] getData() {
        return data;
    }

    public byte readByte() {
        byte value = data[readIndex];
        readIndex += ONE_LENGTH;
        return value;
    }

    public short readShort() {
        short value = 0;
        for (int i = 0; i < TWO_LENGTH; i++) {
            value += data[i + readIndex] << i * 8;
        }
        readIndex += TWO_LENGTH;
        return value;
    }

    public int readInt() {
        int value = 0;
        for (int i = 0; i < FOUR_LENGTH; i++) {
            value += data[i + readIndex] << i * 8;
        }
        readIndex += FOUR_LENGTH;
        return value;
    }

    public long readLong() {
        long value = 0;
        for (int i = 0; i < EIGHT_LENGTH; i++) {
            value += (long) data[i + readIndex] << i * 8;
        }
        readIndex += EIGHT_LENGTH;
        return value;
    }

    public boolean readBoolean() {
        byte value = data[readIndex];
        readIndex += ONE_LENGTH;
        return value == BOOLEAN_TRUE;
    }

    public char readChar() {
        char value = 0;
        for (int i = 0; i < TWO_LENGTH; i++) {
            value += data[i + readIndex] << i * 8;
        }
        readIndex += TWO_LENGTH;
        return value;
    }

    public float readFloat() {
        int intValue = readInt();
        return Float.intBitsToFloat(intValue);
    }

    public double readDouble() {
        long longValue = readLong();
        return Double.longBitsToDouble(longValue);
    }

    public byte[] readBytes(int length) {
        byte[] tempBytes = new byte[length];
        System.arraycopy(data, readIndex, tempBytes, 0, length);
        readIndex += length;
        return tempBytes;
    }

    public void writeByte(byte value) {
        expansion(ONE_LENGTH);
        data[writeIndex] = value;
        writeIndex += ONE_LENGTH;
    }

    public void writeShort(short value) {
        expansion(TWO_LENGTH);
        for (int i = 0; i < TWO_LENGTH; i++) {
            data[i + writeIndex] = (byte) (value >> i * 8);
        }
        writeIndex += TWO_LENGTH;
    }

    public void writeInt(int value) {
        expansion(FOUR_LENGTH);
        for (int i = 0; i < FOUR_LENGTH; i++) {
            data[i + writeIndex] = (byte) (value >> i * 8);
        }
        writeIndex += FOUR_LENGTH;
    }

    public void writeLong(long value) {
        expansion(EIGHT_LENGTH);
        for (int i = 0; i < EIGHT_LENGTH; i++) {
            data[i + writeIndex] = (byte) (value >> i * 8);
        }
        writeIndex += EIGHT_LENGTH;
    }

    public void writeBoolean(boolean value) {
        expansion(ONE_LENGTH);
        data[writeIndex] = value ? BOOLEAN_TRUE : BOOLEAN_FALSE;
        writeIndex += ONE_LENGTH;
    }

    public void writeChar(char value) {
        expansion(TWO_LENGTH);
        for (int i = 0; i < TWO_LENGTH; i++) {
            data[i + writeIndex] = (byte) (value >> i * 8);
        }
        writeIndex += TWO_LENGTH;
    }

    public void writeFloat(float value) {
        int intValue = Float.floatToIntBits(value);
        writeInt(intValue);
    }

    public void writeDouble(double value) {
        long longValue = Double.doubleToLongBits(value);
        writeLong(longValue);
    }

    public void writeBytes(byte[] bytes) {
        expansion(bytes.length);
        System.arraycopy(bytes, 0, data, writeIndex, bytes.length);
        writeIndex += bytes.length;
    }

    private void expansion(int length) {
        if (this.data == null) {
            data = new byte[length];
        } else {
            byte[] tempBytes = new byte[data.length + length];
            System.arraycopy(data, 0, tempBytes, 0, data.length);
            data = tempBytes;
        }
    }

}

再附带上一份序列化的代码。

User user = new User(1, "次时代小羊", "222222", 99.44F);

Bytes bytes3 = new Bytes();
bytes3.writeInt(user.getId());
byte[] usernameByte = user.getUsername().getBytes(StandardCharsets.UTF_8);
bytes3.writeInt(usernameByte.length);
bytes3.writeBytes(usernameByte);
byte[] passwordByte = user.getPassword().getBytes(StandardCharsets.UTF_8);
bytes3.writeInt(passwordByte.length);
bytes3.writeBytes(passwordByte);
bytes3.writeFloat(user.getMoney());
System.out.println("length:" + bytes3.getData().length);
System.out.println("bytes:" + Arrays.toString(bytes3.getData()));

User user3 = new User();
user3.setId(bytes3.readInt());
int usernameBytesLength = bytes3.readInt();
user3.setUsername(new String(bytes3.readBytes(usernameBytesLength)));
int passwordBytesLength = bytes3.readInt();
user3.setPassword(new String(bytes3.readBytes(passwordBytesLength)));
user3.setMoney(bytes3.readFloat());
System.out.println("user3:" + user3);

最后的最后,瑞思拜~~~

在这里插入图片描述

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

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

相关文章

我靠steam/csgo道具搬运实现财富自由

和莘莘学子一样&#xff0c;本着毕业对未来的憧憬&#xff0c;规划着漫漫人生&#xff0c;可是被残酷的事实打败。 直到一次偶然的同学聚会&#xff0c;谈及了现如今的生活才发现一片新大陆&#xff0c;通过信息差去赚取收益。 记得之前在校期间常常和哥几个通宵干CSGO,直到这…

Elasticsearch7.8.0版本高级查询—— 完全匹配查询文档

目录一、初始化文档数据二、完全匹配查询文档2.1、概述2.2、示例一、初始化文档数据 在 Postman 中&#xff0c;向 ES 服务器发 POST 请求 &#xff1a;http://localhost:9200/user/_doc/1&#xff0c;请求体内容为&#xff1a; { "name":"zhangsan", &quo…

【Nginx】使用Docker完成Nginx负载均衡+动静分离

前提是需要配置Nginx的反向代理&#xff0c;可以我看之前的文章 上篇Nginx配置动态代理的文章&#xff0c;我们在tomcat里写了两个简单html 这次我们依然采取同样的思路来演示负载均衡 一、负载均衡 1.在两个Tomcat容器&#xff08;我这里一个端口8081&#xff0c;一个8082…

Gradle vs Maven 基本对比(一)

Gradle 与Maven 的基本对比 对比目录&#xff1a; 1、工具包目录对比 2、创建项目结构对比 3、启动进程对比 4、性能对比 5、简洁性对比 什么是gradle: Gradle 是一个开源的运行在JVM上自动化构建工具&#xff0c;专注于灵活性和性能。Gradle 使用 Groovy 或 Kotlin DSL(领…

低代码平台飞速创软完成3000万元A+轮融资

疫情形势下&#xff0c;云原生全场景低代码及数字化基础设施提供商珠海飞速创软科技有限公司&#xff08;以下简称&#xff1a;飞速创软&#xff09;依然发展迅速&#xff0c;逆势而上。继2021年中获得珠海正菱创投、炼金术资本等机构A轮数千万融资之后&#xff0c;于2022年底&…

【手写 Vue2.x 源码】第三十一篇 - diff算法-比对优化(下)

一&#xff0c;前言 上篇&#xff0c;diff算法-比对优化&#xff08;上&#xff09;&#xff0c;主要涉及以下几个点&#xff1a; 介绍了如何进行儿子节点比对&#xff1b;新老儿子节点可能存在的3种情况及代码实现&#xff1b;新老节点都有儿子时的 diff 方案介绍与处理逻辑…

墙裂推荐,2023年最强、最实用的IDEA插件推荐合集

插件目录Alibaba Java Coding Guidelines(阿里巴巴java开发规范)Alibaba Cloud AI Coding Assistant(阿里云AI代码助理)Code Glance3(代码地图)Codota AI Autocomplete for Java and JavaScriptCSDN Tools(CSDN官方插件)FindBugsGenerateAllSetter Postfix Completion (自动生成…

小程序uni-app的api

小程序uni-app的apiuni api简介uni api使用uni-app自定义组件—传统方式核心步骤uni-app自定义组件—easycom简介核心步骤uni-app组件库uViewUIuview介绍关键步骤uni api简介 uni-api 指的是uni-app 针对一些 微信小程序api所做的封装它解决了两个问题 原生的小程序api不支持…

C/C++const关键字详解(全网最全)

目录 1、const修饰普通变量 2、const修饰指针 &#xff08;1&#xff09;const修饰p: &#xff08;2&#xff09;const修饰*p&#xff1a; &#xff08;3&#xff09;const修饰p和*p 4、const修饰数组 5、const修饰函数形参 &#xff08;1&#xff09;const修饰普通形参…

【数据结构】6.4 图的存储结构

文章目录6.4.1 邻接矩阵&#xff08;数组&#xff09;表示法无向图的邻接矩阵无向图邻接矩阵的特点有向图的邻接矩阵有向图邻接矩阵的特点网&#xff08;有权图&#xff09;的邻接矩阵采用邻接矩阵创建无向网邻接矩阵的优缺点6.4.2 邻接表&#xff08;链式&#xff09;无向图的…

【人工智能原理自学】初识Keras:轻松完成神经网络模型搭建

&#x1f60a;你好&#xff0c;我是小航&#xff0c;一个正在变秃、变强的文艺倾年。 &#x1f514;笔记来自B站UP主Ele实验室的《小白也能听懂的人工智能原理》。 &#x1f514;本文讲解初识Keras&#xff1a;轻松完成神经网络模型搭建&#xff0c;一起卷起来叭&#xff01; 目…

Eureka入门

Eureka入门Eureka入门什么是Eureka构建项目demo服务拆分远程调用创建Pom聚合工程Eureka使用搭建注册中心注册服务远程调用出现的问题Eureka入门 什么是Eureka Eureka是SpringCloud提供的注册中心&#xff0c;用来解决微服务之间远程调用问题&#xff0c;如&#xff1a; 消费…

交通流的微观模型研究(Matlab代码实现)

&#x1f4a5;&#x1f4a5;&#x1f49e;&#x1f49e;欢迎来到本博客❤️❤️&#x1f4a5;&#x1f4a5; &#x1f3c6;博主优势&#xff1a;&#x1f31e;&#x1f31e;&#x1f31e;博客内容尽量做到思维缜密&#xff0c;逻辑清晰&#xff0c;为了方便读者。 ⛳️座右铭&a…

Redis原理篇(四)内存回收

Redis之所以性能强&#xff0c;最主要原因是基于内存存储。但是单节点的Redis其内存大小不宜过大&#xff0c;会影响持久化或主从同步性能。 可以通过配置文件来设置最大内存 # maxmemory <bytes> maxmemory 1gb一、过期策略 可以通过expire命令给Redis的key设置TTL …

【C++算法图解专栏】一篇文章带你掌握高精度加减乘除运算

✍个人博客&#xff1a;https://blog.csdn.net/Newin2020?spm1011.2415.3001.5343 &#x1f4e3;专栏定位&#xff1a;为 0 基础刚入门数据结构与算法的小伙伴提供详细的讲解&#xff0c;也欢迎大佬们一起交流~ &#x1f4da;专栏地址&#xff1a;https://blog.csdn.net/Newin…

Java 异常 笔记

异常体系结构 异常分为Error和Exception。Error通常是灾难性错误&#xff0c;一般发生时&#xff0c;JVM选择终止程序执行&#xff1b;Exception通常可在程序中进行处理&#xff0c;尽量避免 Exception分支中有一个重要子类RuntimeException&#xff0c;运行时异常 ArrayInd…

数据库,计算机网络、操作系统刷题笔记34

数据库&#xff0c;计算机网络、操作系统刷题笔记34 2022找工作是学历、能力和运气的超强结合体&#xff0c;遇到寒冬&#xff0c;大厂不招人&#xff0c;可能很多算法学生都得去找开发&#xff0c;测开 测开的话&#xff0c;你就得学数据库&#xff0c;sql&#xff0c;oracle…

深入理解Promise

Promise的前提概念 Promise是一个构造函数&#xff0c;用来生成Promise实例 Promise构造函数接受一个函数作为参数&#xff0c;该函数有两个参数&#xff0c;分别是resolve和reject resolve&#xff1a;成功时的回调 reject&#xff1a;失败时的回调 Promise分别有三个状态 1…

行人属性识别研究综述(一)

文章目录摘要1、简介2 问题的表述和挑战3 标准3.1 数据集3.2 评价标准4 行人属性识别的常规流程4.1 多任务学习4.2 多标签学习5 深度神经网络&#x1f407;&#x1f407;&#x1f407;&#x1f407;&#x1f407;&#x1f407;&#x1f407; 欢迎阅读 【AI浩】 的博客&#x1f…

C#上位机基础学习_基于S7.Net实现读取S7-1500PLC中的字符串变量

C#上位机基础学习_基于S7.Net实现读取S7-1500PLC中的字符串变量 如下图所示,首先在TIA博途中创建一个项目,添加一个1500PLC,添加一个DB块,在DB块中添加几个字符串变量, 如下图所示,打开Visual Studio 2019,新建一个项目,在Form1中添加一个按钮和一个文本框, 如下图…