热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

序列化和反序列化学习

Java领域的对象如何传输基于socket进行对象传输socket通信的代码的例子user.javapublicclassUser{privateStringname;pu

Java 领域的对象如何传输

基于 socket 进行对象传输

socket 通信的代码的例子

user.java

public class User {private String name;public String getName() {return name;}public void setName(String name) {this.name = name;}
}

SocketServer.java 服务端

public class SocketServer {public static void main(String[] args) throws IOException {ServerSocket serverSocket = null;BufferedReader in = null;try {serverSocket = new ServerSocket(8081);Socket socket = serverSocket.accept();ObjectInputStream objectInputStream = new ObjectInputStream(socket.getInputStream());User user = (User) objectInputStream.readObject();System.out.println(user);} catch (Exception e) {e.printStackTrace();} finally {if (in != null) {try {in.close();} catch (IOException e) {e.printStackTrace();}}if (serverSocket != null) {serverSocket.close();}}}
}

SocketClient.java 客户端

public class SocketClient {public static void main(String[] args) {Socket socket = null;ObjectOutputStream out = null;try {socket = new Socket("127.0.0.1", 8081);User user = new User();out = new ObjectOutputStream(socket.getOutputStream());out.writeObject(user);} catch (IOException e) {e.printStackTrace();} finally {if (out != null) {try {out.close();} catch (IOException e) {e.printStackTrace();}}if (socket != null) {try {socket.close();} catch (IOException e) {e.printStackTrace();}}}}
}

运行结果
这段代码运行以后,能够实现 Java 对象的正常传输吗? 很显然,会报错
在这里插入图片描述
如何解决报错的问题呢?
对 User 这个对象实现一个 Serializable 接口,再次运行就可以看到对象能够正常传输了

public class User implements Serializable {private String name;public String getName() {return name;}public void setName(String name) {this.name = name;}
}

序列化的意义
我们发现对 User 这个类增加一个 Serializable,就可以解决 Java 对象的网络传输问题。

Java 平台允许我们在内存中创建可复用的 Java 对象,但一般情况下,只有当 JVM 处于运行时,这些对象才可能存在,即,这些对象的生命周期不会比 JVM 的生命周期更长。但在现实应用中,就可能要求在 JVM 停止运行之后能够保存(持久化)指定的对象,并在将来重新读取被保存的对象。Java 对象序列化就能够帮助我们实现该功能

简单来说
序列化是把对象的状态信息转化为可存储传输的形式过程,也就是把对象转化为字节序列的过程称为对象的序列化
反序列化是序列化的逆向过程,把字节数组反序列化为对象,把字节序列恢复为对象的过程成为对象的反序列化


序列化的高阶认识

简单认识一下 Java 原生序列化

前面的代码中演示了,如何通过 JDK 提供了 Java 对象的序列化方式实现对象序列化传输,主 要通过输出流 java.io.ObjectOutputStream 和对象输入流 java.io.ObjectInputStream 来实现。


  • java.io.ObjectOutputStream:表示对象输出流 , 它的 writeObject(Object obj)方法可以对参数指定的 obj 对象进行序列化,把得到的字节序列写到一个目标输出流中。
  • java.io.ObjectInputStream:表示对象输入流 ,它的 readObject()方法源输入流中读取字节序列,再把它们反序列化成为一个对象,并将其返回

需要注意的是,被序列化的对象需要实现 java.io.Serializable 接口


serialVersionUID 的作用
身份证的作用,唯一标识一个类

在 IDEA 中通过如下设置可以生成 serializeid
在这里插入图片描述
字面意思上是序列化的版本号,凡是实现 Serializable 接口的类都有一个表示序列化版本标识符的静态变量

演示步骤


  1. 先将 user 对象序列化到文件中
  2. 然后修改 user 对象,增加 serialVersionUID 字段
  3. 然后通过反序列化来把对象提取出来
  4. 演示预期结果:提示无法反序列化

结论
Java 的序列化机制是通过判断类的 serialVersionUID 来验证版本一致性的。在进行反序列化时,JVM 会把传来的字节流中的 serialVersionUID 与本地相应实体类的 serialVersionUID 进行比较,如果相同就认为是一致的,可以进行反序列化,否则就会出现序列化版本不一致的异常,即是 InvalidCastException。

从结果可以看出,文件流中的 class 和 classpath 中的 class,也就是修改过后的 class,不兼容了,处于安全机制考虑,程序抛出了错误,并且拒绝载入。从错误结果来看,如果没有为指定的 class 配置 serialVersionUID,那么 java 编译器会自动给这个 class 进行一个摘要算法, 类似于指纹算法,只要这个文件有任何改动,得到的 UID 就会截然不同的,可以保证在这么多类中,这个编号是唯一的。所以,由于没有显指定 serialVersionUID,编译器又为我们生成了一个 UID,当然和前面保存在文件中的那个不会一样了,于是就出现了 2 个序列化版本号不一致的错误。因此,只要我们自己指定了 serialVersionUID,就可以在序列化后,去添加一个字段,或者方法,而不会影响到后期的还原,还原后的对象照样可以使用。


tips: serialVersionUID 有两种显示的生成方式:

  • 一是默认的 1L,比如:private static final long serialVersionUID = 1L;
  • 二是根据类名、接口名、成员方法及属性等来生成一个 64 位的哈希字段。当实现 java.io.Serializable 接口的类没有显式地定义一个 serialVersionUID 变量时候,Java 序列化机制会根据编译的 Class 自动生成一个 serialVersionUID 作序列化版本比较用,这种情况下,如果 Class 文件(类名,方法明等)没有发生变化(增加空格,换行,增加注释等等),就算再编译多次,serialVersionUID 也不会变化的。


Transient 关键字

Transient 关键字的作用是控制变量的序列化,在变量声明前加上该关键字,可以阻止该变量被序列化到文件中,在被反序列化后,transient 变量的值被设为初始值,如 int 型的是 0,对象型的是 null。

绕开 transient 机制的办法(了解)
略:


Java 序列化的一些简单总结

  1. Java 序列化只是针对对象的状态进行保存,至于对象中的方法,序列化不关心
  2. 当一个父类实现了序列化,那么子类会自动实现序列化,不需要显示实现序列化接口
  3. 当一个对象的实例变量引用了其他对象,序列化这个对象的时候会自动把引用的对象也进
    行序列化(实现深度克隆)
  4. 当某个字段被申明为 transient 后,默认的序列化机制会忽略这个字段
  5. 被申明为 transient 的字段,如果需要序列化,可以添加两个私有方法:writeObject 和
    readObject

分布式架构下常见序列化技术

初步了解了 Java 序列化的知识以后,我们又得回到分布式架构中,了解序列化的发展过程

序列化的发展
随着分布式架构、微服务架构的普及。服务与服务之间的通信成了最基本的需求。这个时候, 我们不仅需要考虑通信的性能,也需要考虑到语言多元化问题 所以,对于序列化来说,如何去提升序列化性能以及解决跨语言问题,就成了一个重点考虑的问题。

由于 Java 本身提供的序列化机制存在两个问题


  1. 序列化的数据比较大,传输效率低
  2. 其他语言无法识别和对接

以至于在后来的很长一段时间,基于 XML 格式编码的对象序列化机制成为了主流,一方面解 决了多语言兼容问题,另一方面比二进制的序列化方式更容易理解。以至于基于 XML 的 SOAP 协议及对应的 WebService 框架在很长一段时间内成为各个主流开发语言的必备的技术。 再到后来,基于 JSON 的简单文本格式编码的 HTTP REST 接口又基本上取代了复杂的 Web Service 接口,成为分布式架构中远程通信的首要选择。但是 JSON 序列化存储占用的空间大、性能低等问题,同时移动客户端应用需要更高效的传输数据来提升用户体验。在这种情况下与语言无关并且高效的二进制编码协议就成为了大家追求的热点技术之一。首先诞生的一个开源的二进制序列化框架-MessagePack。它比 google 的 Protocol Buffers 出现得还要早。


各种序列化技术简介


  • XML 序列化框架介绍
    XML 序列化的好处在于可读性好,方便阅读和调试。但是序列化以后的字节码文件比较大, 而且效率不高,适用于对性能不高,并且 QPS 较低的企业级内部系统之间的数据交换的场景, 同时 XML 又具有语言无关性,所以还可以用于异构系统之间的数据交换和协议。比如我们熟知的 Webservice,就是采用 XML 格式对数据进行序列化的。XML 序列化/反序列化的实现方式有很多,熟知的方式有 XStream 和 Java 自带的 XML 序列化和反序列化两种

  • JSON 序列化框架
    JSON(Javascript Object Notation)是一种轻量级的数据交换格式,相对于 XML 来说,JSON 的字节流更小,而且可读性也非常好。现在 JSON 数据格式在企业运用是最普遍的
    JSON 序列化常用的开源工具有很多

    1. Jackson (https://github.com/FasterXML/jackson)
    2. 阿里开源的 FastJson (https://github.com/alibaba/fastjon)
    3. Google 的 GSON (https://github.com/google/gson)

    这几种 json 序列化工具中,Jackson 与 fastjson 要比 GSON 的性能要好,但是 Jackson、 GSON 的稳定性要比 Fastjson 好。而 fastjson 的优势在于提供的 api 非常容易使用

  • Hessian 序列化框架
    Hessian 是一个支持跨语言传输的二进制序列化协议,相对于 Java 默认的序列化机制来说, Hessian 具有更好的性能和易用性,而且支持多种不同的语言。实际上 Dubbo 采用的就是 Hessian 序列化来实现,只不过 Dubbo 对 Hessian 进行了重构, 性能更高

  • Avro 序列化
    Avro 是一个数据序列化系统,设计用于支持大批量数据交换的应用。它的主要特点有:支持二进制序列化方式,可以便捷,快速地处理大量数据;动态语言友好,Avro 提供的机制使动 态语言可以方便地处理 Avro 数据。

  • kyro 序列化框架
    Kryo 是一种非常成熟的序列化实现,已经在 Hive、Storm中使用得比较广泛,不过它不能跨语言. 目前 dubbo 已经在 2.6 版本支持 kyro 的序列化机制。它的性能要优于之前的 hessian2

  • Protobuf 序列化框架
    Protobuf 是 Google 的一种数据交换格式,它独立于语言、独立于平台。Google 提供了多种语言来实现,比如 Java、C、Go、Python,每一种实现都包含了相应语言的编译器和库文件, Protobuf 是一个纯粹的表示层协议,可以和各种传输层协议一起使用。
    Protobuf 使用比较广泛,主要是空间开销小(比java序列化方式占用空间小10倍)和性能比较好,非常适合用于公司内部对性能要求高的 RPC 调用。 另外由于解析性能比较高,序列化以后数据量相对较少,所以也可以应用在对象的持久化场景中
    但是要使用 Protobuf 会相对来说麻烦些,因为他有自己的语法,有自己的编译器,如果需要用到的话必须要去投入成本在这个技术的学习中。
    protobuf 有个缺点就是要传输的每一个类的结构都要生成对应的 proto 文件,如果某个类发生修改,还得重新生成该类对应的 proto 文件。


Protobuf 序列化的原理

那么接下来着重分析一下 protobuf 的序列化原理,前面说过它的优势是空间开销小,性能也相对较好。它里面用到的一些算法还是值得我们去学习的

protobuf 的基本应用,使用 protobuf 开发的一般步骤是


  1. 配置开发环境,安装 protocol compiler 代码编译器
  2. 编写.proto 文件,定义序列化对象的数据结构
  3. 基于编写的.proto 文件,使用 protocol compiler 编译器生成对应的序列化/反序列化工具类
  4. 基于自动生成的代码,编写自己的序列化应用

Protobuf 案例演示
下载 protobuf 工具 https://github.com/google/protobuf/releases
编写 proto 文件。找到 protoc-3.5.1-win32.zip

syntax="proto2"; //string / bytes / bool / int32(4 个字节) /int64/float/double enum 枚举类 message 自定义类
package com.gupaoedu.serial;
option java_package = "com.gupaoedu.serial";
option java_outer_classname="UserProtos";
message User {
required string name=1; required int32 age=2;
}/*
修饰符
required 表示必填字段
optional 表示可选字段
repeated 可重复,表示集合 1,2,3,4 需要在当前范围内是唯一的
表示顺序
*/

生成实体类

【.\protoc.exe --java_out=./ ./user.proto】

实现序列化

<dependency><groupId>com.google.protobuf</groupId><artifactId>protobuf-java</artifactId><version>3.7.0</version>
</dependency>

UserProtos.User user&#61;UserProtos.User.newBuilder().setName("Mic") .setAge(18).build();
ByteString bytes&#61;user.toByteString();
System.out.println(bytes);
UserProtos.User nUser&#61;UserProtos.User.parseFrom(bytes); System.out.println(nUser);

protobuf 序列化原理

我们可以把序列化以后的数据打印出来看看结果

public static void main(String[] args) {UserProtos.User user &#61; UserProtos.User.newBuilder().setAge(300).setName("Mic").build();byte[] bytes &#61; user.toByteArray();for (byte bt : bytes) {System.out.print(bt &#43; " ");}
}

输出结果


10 3 77 105 99 16 -84 2


我们可以看到&#xff0c;序列化出来的数字基本看不懂&#xff0c;但是序列化以后的数据确实很小。
正常来说&#xff0c;要达到最小的序列化结果&#xff0c;一定会用到压缩的技术&#xff0c;而 protobuf 里面用到了两种压缩算法&#xff0c;一种是 varint&#xff0c;另一种是 zigzag
具体做法是基于一定的规则&#xff0c;对字节做一些处理。这里不细研究了。

总结
Protocol Buffer 的性能好&#xff0c;主要体现在序列化后的数据体积小 & 序列化速度快&#xff0c;最终使得传输效率高&#xff0c;其原因如下:
序列化速度快的原因:
a. 编码 / 解码 方式简单(只需要简单的数学运算 &#61; 位移等等)
b. 采用 Protocol Buffer 自身的框架代码和编译器共同完成序列化后的数据量体积小(即数据压缩效果好)的原因:
a. 采用了独特的编码方式&#xff0c;如 Varint、Zigzag 编码方式等等
b. 采用 T - L - V 的数据存储方式:减少了分隔符的使用 & 数据存储得紧凑


序列化技术的选型

技术层面


  1. 序列化空间开销&#xff0c;也就是序列化产生的结果大小&#xff0c;这个影响到传输的性能
  2. 序列化过程中消耗的时长&#xff0c;序列化消耗时间过长影响到业务的响应时间
  3. 序列化协议是否支持跨平台&#xff0c;跨语言。因为现在的架构更加灵活&#xff0c;如果存在异构系统通信
    需求&#xff0c;那么这个是必须要考虑的
  4. 可扩展性/兼容性&#xff0c;在实际业务开发中&#xff0c;系统往往需要随着需求的快速迭代来实现快速更新&#xff0c;这就要求我们采用的序列化协议基于良好的可扩展性/兼容性&#xff0c;比如在现有的序列化数据结构中新增一个业务字段&#xff0c;不会影响到现有的服务
  5. 技术的流行程度&#xff0c;越流行的技术意味着使用的公司多&#xff0c;那么很多坑都已经淌过并且得到了
    解决&#xff0c;技术解决方案也相对成熟
  6. 学习难度和易用性

选型建议


  1. 对性能要求不高的场景&#xff0c;可以采用基于 XML 的 SOAP 协议
  2. 对性能和间接性有比较高要求的场景&#xff0c;那么 Hessian、Protobuf、Thrift、Avro 都可以。
  3. 基于前后端分离&#xff0c;或者独立的对外的 api 服务&#xff0c;选用 JSON 是比较好的&#xff0c;对于调试、可读
    性都很不错
  4. Avro 设计理念偏于动态类型语言&#xff0c;那么这类的场景使用 Avro 是可以的

推荐阅读
  • Java 中静态和非静态嵌套类的区别 ... [详细]
  • 本文探讨了在使用Apache Flink向Kafka发送数据过程中遇到的事务频繁失败问题,并提供了详细的解决方案,包括必要的配置调整和最佳实践。 ... [详细]
  • 本文探讨了在Qt框架下实现TCP多线程服务器端的方法,解决了一个常见的问题:服务器端仅能与最后一个连接的客户端通信。通过继承QThread类并利用socketDescriptor标识符,实现了多个客户端与服务器端的同时通信。 ... [详细]
  • 本文提供了解决在尝试重置MySQL root用户密码时遇到连接失败问题的方法,包括停止MySQL服务、以安全模式启动MySQL、手动更新用户表中的密码等步骤。 ... [详细]
  • C#爬虫Fiddler插件开发自动生成代码
    哈喽^_^一般我们在编写网页爬虫的时候经常会使用到Fiddler这个工具来分析http包,而且通常并不是分析一个包就够了的,所以为了把更多的时间放在分析http包上,自动化生成 ... [详细]
  • 在现代移动应用开发中,尤其是iOS应用,处理来自服务器的JSON数据是一项基本技能。无论是使用Swift还是PHP,有效地解析和利用JSON数据对于提升用户体验至关重要。本文将探讨如何在Swift中优雅地处理JSON,以及PHP中处理JSON的一些技巧。 ... [详细]
  • 解决JavaWeb项目中因IPv6导致的IP转换错误
    本文探讨了在JavaWeb项目中,当尝试将客户端IP地址从字符串形式转换为整数时遇到的问题,并提供了详细的解决方案。具体问题表现为在本地环境中通过`request.getRemoteHost()`获取到的IP地址为IPv6格式,而非预期的IPv4格式。 ... [详细]
  • 将数组的所有元素递增 1 的 Java 程序 ... [详细]
  • 本文详细介绍了Keycloak框架中UserRepresentation类下的isEnabled()方法的功能与应用,并通过多个实际代码示例说明其在用户管理中的具体实现。 ... [详细]
  • 设计模式系列-原型模式
    一、上篇回顾上篇创建者模式中,我们主要讲述了创建者的几类实现方案,和创建者模式的应用的场景和特点,创建者模式适合创建复杂的对象,并且这些对象的每个组成部分的详细创建步骤可以是动态的变化的,但 ... [详细]
  • 本文介绍如何使用Java实现AC自动机(Aho-Corasick算法),以实现高效的多模式字符串匹配。文章涵盖了Trie树和KMP算法的基础知识,并提供了一个详细的代码示例,包括构建Trie树、设置失败指针以及执行搜索的过程。 ... [详细]
  • PHP网站部署指南:从零开始搭建PHP网站
    本文提供了详细的步骤指导,帮助开发者在不同环境下成功部署PHP网站,包括在IIS和Apache服务器上的具体操作。 ... [详细]
  • 使用EF Core在.Net Core控制台应用中操作SQLite数据库
    本文介绍如何利用Visual Studio 2019和Windows 10环境,通过Entity Framework Core(EF Core)实现对SQLite数据库的读写操作。项目源代码可从百度网盘下载。 ... [详细]
  • 本文将指导你如何通过自定义配置,使 Windows Terminal 中的 PowerShell 7 更加高效且美观。我们将移除默认的广告和提示符,设置快捷键,并添加实用的别名和功能。 ... [详细]
  • 本文详细介绍了如何在Linux系统上安装和配置单节点的Redis服务,包括下载、解压、编译安装以及启动服务的具体步骤。 ... [详细]
author-avatar
绿茶2602921445
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有