序列化和反序列化:将数据变得更加通用化

news2024/12/28 22:48:31

序列化与反序列化简介

序列化和反序列化是计算机领域中常用的概念,用于将对象或数据结构转换为字节序列(序列化)和将字节序列转换回对象或数据结构(反序列化)。

序列化是指将对象或数据结构转换为字节序列的过程。通过序列化,可以将对象保存到文件、数据库或进行网络传输。在序列化过程中,对象的状态和数据会被转换为一系列的字节,以便能够在不同的环境中进行传输或持久存储。

反序列化则是将字节序列转换回对象或数据结构的过程。通过反序列化,可以重新构造出原始的对象或数据结构,使其可以被程序使用。

序列化和反序列化可以实现跨平台的数据交换。例如,在网络通信中,可以将对象序列化后发送给其他计算机,接收端再进行反序列化,从而实现数据的传输和共享。

常见的序列化格式包括 JSON、XML、Protocol Buffers(protobuf)等。不同的序列化格式有各自的特点和适用场景,选择适合的序列化格式可以提高数据传输效率和方便性。

需要注意的是,在进行序列化和反序列化时,应该确保数据的完整性和安全性。对于敏感数据,可以采用加密等方式进行保护,以防止数据泄露或篡改。此外,在不同语言或不同版本间进行序列化和反序列化时,还需要特别注意对象的兼容性和数据格式的一致性。

序列化目的

序列化的主要目的是将对象或数据结构转换为字节序列,以便在不同环境中进行传输或持久存储。具体来说,序列化的目的有以下几个:

  1. 数据交换:序列化可以将数据转换为跨平台兼容的格式,使得数据可以在不同的系统、编程语言之间进行交换和共享。
  2. 持久化存储:将对象序列化后,可以将其保存到磁盘、数据库等介质中,实现数据的持久化存储,防止程序退出或计算机宕机导致数据丢失。
  3. 网络通信:在网络通信中,可以将对象序列化后通过网络发送到其他计算机,接收端再进行反序列化,从而实现数据的传输和共享。
  4. 缓存优化:序列化后的数据可以被缓存,当需要时直接从缓存中读取,避免了频繁的数据库查询,提高了性能。

总的来说,序列化的目的是为了方便数据的传输、保存和共享,同时还可以提高程序的性能和响应速度。

序列化框架选择

在选择序列化和反序列化框架的时候,主要从以下两个方面进行考虑:

(1)结果数据大小; 原则上来说,序列化后的数据越小,传输效率越高;

(2)结构复杂程度;结构复杂度会影响序列化和发序列化的效率,结构越复杂,越耗时。

根据以上两点,对于性能要求不是太高的服务器程序,可以选择Json文本格式的序列化框架;对于性能要求比较高的程序程序,则应该选择传输效率更高的二进制序列化框架,建议使用Protobuf。

Json

Json(JavaScript Object Notation JS对象)是一种轻量级的数据交换格式。它是基于ECMAScript 的一个子集。采用完全独立于编程语言的文本格式来存储和表示数据。

Json 协议是一种文本协议,易于阅读和编写,同时也易于机器解析和生成,并能有效地提升网络传输协效率。

JSON格式具有以下特点:

  • 可读性高:JSON使用简洁明了的文本格式,易于人类阅读和理解。
  • 轻量级:相较于其他数据交换格式如XML,JSON的数据表示更为紧凑,占用更少的存储空间和传输带宽。
  • 平台无关性:JSON格式在不同的编程语言和平台之间具有良好的兼容性,可以方便地进行数据交换和共享。
  • 支持多种数据类型:JSON支持包括字符串、数字、布尔值、数组、对象和null在内的多种数据类型。这使得JSON能够灵活地表示各种数据结构和复杂对象。
  • 易于解析和生成:绝大多数编程语言都提供了JSON的解析和生成库,使得操作JSON数据变得十分方便和高效。
  • 可扩展性:JSON格式支持通过嵌套和组合来表示更复杂的数据结构,可以根据具体需求进行扩展和定制。

在JSON中,数据以键值对的方式表示,键是一个字符串,值可以是字符串、数字、布尔值、数组、对象或null。简单的JSON示例如下:

{
  "name": "张娜",
  "age": 30,
  "isStudent": false,
  "hobbies": ["阅读", "游泳"],
  "address": {
    "street": "安德里北街12号",
    "city": "北京市"
  },
  "score": null
}

Json序列化和反序列化开源库

java处理Json数据有三个比较流行的开源类库:

(1)阿里的FastJson

阿里巴巴的FastJson 是一个高性能的 JSON 库。 FastJson 库采用独创的快速算法,将 JSON 转成 POJO 的速度提升到极致,从性能上说,其反序列化速度超过其他 JSON 开 源库。

FastJson 在复杂类型的 POJO 转换 JSON (序列化)时,可能会 出现一些引用类 型问题而导致 JSON 转换出错,需要进行引用的定制。

(2)谷歌的Gson

Google的 Gson 开源库是一个功能齐全的 JSON 解析库,起源于 Google 公司内部需求而由 Google 自行研发而来,在 2008 年 5 月公开发布第一版之后已被许多公司或用户应用。 Gson 可 以完成复杂类型的 POJO 和 JSON 字符串的相互转换,转换的能力非常强。

(3)开源社区的Jackson

Jackson 是一个简单的、基于java的Json开源库。使用Jackson开源库,可以轻松地将java Pojo对象转换成Json、XML;式串;同样也可以方便地将 JSON 、 XML 字符串转换成 Java POJO 对象。

Jackson 开源库的优点是:所依赖的 Jar 包较少、简单易用、性能也还不错, 另外 Jackson 社区相对比较活跃。

Jackson 开源库的缺点是:对于复杂 POJO 类型、复杂的集合 Map 、 List 的转换结果,不是标准的 JSON 格式,或者会出现一些问题。

在实际生产中,比较主流的策略是Gson+FastJson相互结合的JsonUtil类,在Pojo序列化为JSON字符串的应用场景(序列化场景)使用Gson库;在JSON 字符串反序列化成POJO的应用场景(反序列化)使用FastJson库。JsonUtil类如下:


import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.alibaba.fastjson.TypeReference;
import com.crazymaker.springcloud.common.result.RestOut;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import com.google.gson.JsonElement;

import java.io.UnsupportedEncodingException;
import java.lang.reflect.Type;
import java.util.Map;

public class JsonUtil {

    //谷歌 GsonBuilder 构造器
    static GsonBuilder gb = new GsonBuilder();

    private static final Gson gson;

    static {
        //不需要html escape
        gb.disableHtmlEscaping();
        gson = gb.create();
    }

    //序列化:使用谷歌 Gson 将 POJO 转成字符串
    public static String pojoToJson(Object obj) {
        String json = gson.toJson(obj);
        return json;
    }

    //反序列化:使用阿里fastJson 将字符串转成 POJO对象
    public static <T> T jsonToPojo(String json, Class<T> tClass) {
        T t = JSON.parseObject(json, tClass);
        return t;
    }
  
}

在2022年5月的时候,中共电信天翼云发布了FastJson ≤ 1.2.80 具有严重、高危的反序列化远程代码执行漏洞,在这种情况下,就需要通过快速切换json组件来解决子此问题,最佳策略是策略模式+工具类的结合,实现业务应用兼容主要的json组件,根据具体的场景和各种突发事件能够进行快速、灵活的组件切换。

策略模式(Strategy)

策略模式 属于对象的行为模式,主要是针对一组不同的算法,抽象出一组共同的接口或抽象类,然后根据每一个单独的算法封装具体的实现类,从而使得它们可以相互替换。

策略模式的优势是可以在不影响到客户端的情况下,实现具体算法的切换。

策略模式的类图如下:

在这里插入图片描述

使用策略模式(Strategy)实现不同的Json开源组件之间的切换,其具体类图如下:
在这里插入图片描述

抽象策略接口 JsonStrategy,定义了一组抽象的方法,如toJson()-序列化、fromJson()-反序列化。


public interface JsonStrategy {
   ...
    String toJson(Object object);

    String toJson(Object object, String dateFormatPattern);

    <T> T fromJson(String json, Class<T> valueType);
   ...
}

具体策略类FastJsonStrategy、GsonJSONStrategy、JackSonStrategy,分别使用FastJson、Gson、JackSon三个主流的开源组件,完成Pojo对象的序列化和反序列化。 这样设计遵循了软件设计的开闭原则,在后续若想新增一个新的序列化/反序列化组件,只需要新增一个JsonStrategy实现类即可。

FastJsonStrategy 类:

public class FastJsonStrategy implements JsonStrategy {

    public FastJsonStrategy() {
    }

   ...

    //序列化
    @Override
    public String toJson(Object object) {
        return JSON.toJSONString(object);
    }

    @Override
    public String toJson(Object object, String dateFormatPattern) {
        return JSON.toJSONStringWithDateFormat(object, dateFormatPattern, SerializerFeature.WriteDateUseDateFormat);
    }

    //反序列化
    @Override
    public <T> T fromJson(String json, Class<T> valueType) {
        return JSON.parseObject(json, valueType);
    }


   ...
}

GsonStrategy 类:


public class GsonStrategy implements JsonStrategy {
    public static Gson gson;
    public static GsonBuilder gsonBuilder;


    public GsonStrategy() {
        gsonBuilder = new GsonBuilder();
        //不需要html escape
        gsonBuilder.disableHtmlEscaping();

        // 解决Gson序列化时出现整型变为浮点型的问题
        gsonBuilder.registerTypeAdapter(new TypeToken<Map<Object, Object>>() { }.getType(),
                        (JsonDeserializer<Map<Object, Object>>) (jsonElement, type, jsonDeserializationContext) -> {
                            Map<Object, Object> map = new LinkedHashMap<>();
                            JsonObject jsonObject = jsonElement.getAsJsonObject();
                            Set<Map.Entry<String, JsonElement>> entrySet = jsonObject.entrySet();
                            for (Map.Entry<String, JsonElement> entry : entrySet) {
                                Object obj = entry.getValue();
                                if (obj instanceof JsonPrimitive) {
                                    map.put(entry.getKey(), ((JsonPrimitive) obj).getAsString());
                                } else {
                                    map.put(entry.getKey(), obj);
                                }
                            }
                            return map;
                        }
                );
        gsonBuilder.registerTypeAdapter(new TypeToken<List<Object>>() { }.getType(),
                        (JsonDeserializer<List<Object>>) (jsonElement, type, jsonDeserializationContext) -> {
                            List<Object> list = new LinkedList<>();
                            JsonArray jsonArray = jsonElement.getAsJsonArray();
                            for (int i = 0; i < jsonArray.size(); i++) {
                                if (jsonArray.get(i).isJsonObject()) {
                                    JsonObject jsonObject = jsonArray.get(i).getAsJsonObject();
                                    Set<Map.Entry<String, JsonElement>> entrySet = jsonObject.entrySet();
                                    list.addAll(entrySet);
                                } else if (jsonArray.get(i).isJsonPrimitive()) {
                                    list.add(jsonArray.get(i));
                                }
                            }
                            return list;
                        }
                );
        gson = gsonBuilder.create();
    }

 ...

  

    @Override
    public String toJson(Object object) {

        return gson.toJson(object);
    }

    @Override
    public String toJson(Object object, String dateFormatPattern) {
        gson = gsonBuilder.setDateFormat(dateFormatPattern).create();
        return gson.toJson(object);
    }

    @Override
    public <T> T fromJson(String json, Class<T> valueType) {
        return gson.fromJson(json, valueType);
    }
...
}

JacksonJsonStrategy类:

public class JacksonJsonStrategy implements JsonStrategy {

    public static ObjectMapper objectMapper;

    public JacksonJsonStrategy() {
        // 禁止时间格式序列化为时间戳
        if (objectMapper == null) {
            objectMapper = new ObjectMapper()
                    .findAndRegisterModules()
                    .disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS);
        }
    }

  ...

    @Override
    public String toJson(Object object) {
        try {
            return objectMapper.writeValueAsString(object);
        } catch (JsonProcessingException e) {
            throw new RuntimeException(e);
        }
    }

    @Override
    public String toJson(Object object, String dateFormatPattern) {
        SimpleDateFormat dateFormat = new SimpleDateFormat(dateFormatPattern);
        try {
            return objectMapper.writer(dateFormat).writeValueAsString(object);
        } catch (JsonProcessingException e) {
            throw new RuntimeException(e);
        }
    }

    @Override
    public <T> T fromJson(String json, Class<T> valueType) {
        try {
            return objectMapper.readValue(json, valueType);
        } catch (IOException e) {
            throw new RuntimeException(e);

        }
    }

   ...
}

环境类JsonContext,此类是模块内部和模块外部之间的纽带,对于模块内部来说,JsonContext类根据配置文件文件中的类型配置,初始化具体的JsonStrategy实现类,并且将其引用保存在内部成员变量中;对于模块外部来说,JsonContext 类为他们提供JsonStrategy引用,供外部Client客户程序使用。

在配置文件system.properties 中配置所使用的json组件,具体配置如下:

json.strategy=Jackson

读取配置文件

@ConfigFileAnno(file = "/system.properties")
public class    SystemConfig extends ConfigProperties
{
	 //依照属性,从配置文件中,装载配置项
    static ConfigProperties singleton
            = new SystemConfig("/system.properties");

    private SystemConfig(String fileName)
    {
        super(fileName);
        super.loadFromFile();
    }
 	/**
     * json的类型: gson/fastjson/Jackson
     *
     * json.strategy=fastjson
     */
    public static final String JSON_STRATEGY
            = singleton.getValue("json.strategy");
}

JsonContext类:

@Slf4j
public abstract class JsonContext {


    private  static volatile JsonStrategy strategy;

    private JsonContext() {
    }

    private static final String CLASS_TYPE_JACKSON = "com.fasterxml.jackson.databind.ObjectMapper";
    private static final String CLASS_TYPE_FASTJSON = "com.alibaba.fastjson.JSON";
    private static final String CLASS_TYPE_GSON = "com.google.gson.Gson";

    /**
     * json的类型: gson/fastjson/Jackson
     */
    private static final String JACKSON = "Jackson";
    private static final String FASTJSON = "fastjson";
    private static final String GSON = "gson";

    private static JsonStrategy loadFromConfig() {

        String jsonType = SystemConfig.JSON_STRATEGY;

        switch (jsonType) {
            case JACKSON:
                if (isClassPresent(CLASS_TYPE_JACKSON)) {
                    log.info("used jackson");
                    return new JacksonJsonStrategy();
                } else {
                    log.error("jackson not found");
                    throw new RuntimeException("未找到jackson的依赖");
                }
            case FASTJSON:
                if (isClassPresent(CLASS_TYPE_FASTJSON)) {
                    log.info("used fastjson");
                    return new FastJsonStrategy();
                } else {
                    log.error("fastjson not found");
                    throw new RuntimeException("未找到fastjson的依赖");
                }
            case GSON:
                if (isClassPresent(CLASS_TYPE_GSON)) {
                    log.info("used gson");
                    return new GsonStrategy();
                } else {
                    log.error("gson not found");
                    throw new RuntimeException("未找到gson的依赖");
                }
            default:
                log.error("未找到jackson、gson或fastjson的依赖");
                throw new RuntimeException("未找到jackson、gson或fastjson的依赖");
        }
    }

    public static JsonStrategy getStrategy() {

        if (strategy == null) {

            synchronized (JsonContext.class) {

                if (strategy == null) {

                    strategy = loadFromConfig();
                }
            }
        }

        return strategy;
    }

    private void setStrategy(JsonStrategy strategy) {
        this.strategy = strategy;
    }


}

外部Client程序只会用到JsonContext类和JsonStrategy引用,不用用到具体的JsonStrategy实现类,实现了client和json开源组件的解耦,达到高内聚低耦合的效果。JsonUtil类需要进行完善一下,具体代码如下:


public class JsonUtil {

    //序列化: pojo =》 json 字符串
    //使用策略模式 将 POJO 转成字符串
    public static String pojoToJson(Object obj) {
        JsonStrategy strategy = JsonContext.getStrategy();

        String json = strategy.toJson(obj);
        return json;
    }

    //反序列化:json 字符串 =》  pojo
    //使用策略模式 将 字符串 转成POJO
    public static <T> T jsonToPojo(String json, Class<T> tClass) {
        JsonStrategy strategy = JsonContext.getStrategy();

        T t = strategy.fromJson(json, tClass);
        return t;
    }

    public static  <K, V> Map<K, V> jsonToMap(String json, Type type) {
        JsonStrategy strategy = JsonContext.getStrategy();

        Map<K, V> t = strategy.toMap(json,type);
        return t;
    }

}

Protobuf

ProtobufProtocol Buffers 的简称,它是一种由Google开发的跨平台、语言无关、可扩展的序列化二进制数据格式。

与 JSON 类似,Protocol Buffers 也用于在不同系统、不同语言之间进行数据交换和存储。它通过定义消息结构(.proto 文件)来描述数据的类型和格式,并使用专门的编译器(例如protoc)来生成针对特定编程语言的序列化和反序列化代码。

与JSON 、 XML 相比, Protobuf 算是后起之秀,只是 Protobuf 更加适合于高性能、快速响 应的数据传输应用场景。 Protobuf 数据包是一种二进制的格式,相对于文本格式的数据交换 JSON 、 XML )来说,速度要快很多 。由于 Protobuf 优异的性能,使得它更加适用于分布式应用场景下的数据通信或者异构环境下的数据交换。

Protocol Buffers 具有以下特点:

  • 高效性:Protocol Buffers 采用二进制编码,相比于文本格式如 JSON,它能够更高效地进行数据压缩和传输,占用更少的存储空间和带宽。
  • 可扩展性:Protocol Buffers 提供了向后兼容和字段标签等特性,可以方便地进行数据模型的演进和版本管理。
  • 跨平台、跨语言:Protocol Buffers 的定义文件可以在不同的编程语言中使用,生成的序列化和反序列化代码也支持跨平台操作。
  • 性能优化:Protocol Buffers 生成的代码通常比手动编写的序列化和反序列化代码更高效,能够提供更快的数据操作速度。
  • 代码生成:通过使用特定的编译器(如protoc),根据 .proto 文件可以生成用于不同编程语言的数据结构和序列化代码。

在使用 Protocol Buffers 时,需要定义消息结构的 .proto 文件,并使用对应的编译器生成目标语言的代码。然后,就可以使用生成的代码进行消息的序列化和反序列化操作,实现跨平台、跨语言的数据交换。

proto文件生成

Protobuf使用 proto 文件来预先定义的消息格式。数据包是按照 proto 文件所定义的消息格式完成二进制码流的编码和解码。

proto 文件就是一个消息的协议文件,这个协议文件的后缀文件名为“.proto ”。其目录结构如下:
在这里插入图片描述

Msg.proto文件具体内容如下:

// [开始声明]
syntax = "proto3";
 //定义protobuf的包名称空间
package com.th.protocol;
// [结束声明]

// [开始 java 选项配置]
//作用:在生成 proto ”文件中消息的POJO 类和 Builder (构造者)的 Java 代码时,将生成的 Java 代码放入该选项所指定的 package类路径中。
option java_package = "com.th.protocol";
//作用:在生成 proto件所对应 Java代码时,生产的 Java 外部类使用配置的名称。
option java_outer_classname = "MsgProtos";
// [结束 java 选项配置]

// [开始 消息定义]
//message 关键字来定义消息的结构体。
//每一个消息结构体可以有多个字段。定义一个字段的格式为“类型名称= 编号”。
message Msg {
  uint32 id = 1;  // Unique ID number for this person.
  string content = 2;
}
message Msg2 {
  uint32 id = 1;  // Unique ID number for this person.
  string content = 2;
}
message Msg3 {
  uint32 id = 1;  // Unique ID number for this person.
  string content = 2;
}
// [结束 消息定义]

在每一个“.proto ”文件中,可以声明多个 message 。大部分情况下会把存在依赖关 系或者包含关系的 message 消息结构体写入一个 .proto 文件。将那些没有关系、相互独立的 message 消息结构体,分别写入不同的文件,这样便于管理。

完成 “.proto ”文件定义后,下一步就是生成消息的 POJO 类和 Builder (构造者)类。有两种方式生成 Java 类:

(1)通过控制台命令的方式;

(2)使用 Maven 插件的方式(推荐使用)。

使用maven插件

首先从“https://github.com/protocolbuffers/protobuf/releases ”下载 Protobuf 的安装包,可以选择不同的版本,这里下载的是 3.6.1 的 Java 版本。

使用protobuf maven plugin 插件,可以非常方便地生成消息的 POJO 类和 Builder 类的 Java 代码。在 Maven 的 pom 文件中增加此 plugin 插件的配置项,具体如下:

 <plugin>
                <groupId>org.xolstice.maven.plugins</groupId>
                <artifactId>protobuf-maven-plugin</artifactId>
                <version>0.6.5</version>
                <extensions>true</extensions>
                <configuration>
                    <!--proto文件路径-->
                    <protoSourceRoot>${project.basedir}/protobuf</protoSourceRoot>
                    <!--目标路径-->
                    <outputDirectory>${project.build.sourceDirectory}</outputDirectory>
                    <!--设置是否在生成java文件之前清空outputDirectory的文件-->
                    <clearOutputDirectory>false</clearOutputDirectory>
                    <!--临时目录-->
                    <temporaryProtoFileDirectory>${project.build.directory}/protoc-temp</temporaryProtoFileDirectory>
                    <!--protoc 可执行文件路径-->
                    <protocExecutable>${project.basedir}/protobuf/protoc3.6.1.exe</protocExecutable>
                </configuration>
                <executions>
                    <execution>
                        <goals>
                            <goal>compile</goal>
                            <goal>test-compile</goal>
                        </goals>
                    </execution>
                </executions>
            </plugin>

protobuf-maven-plugin 插件的配置项,具体介绍如下:

  • protoSourceRoot proto” 消息结构体所在文件的路径;
  • outputDirectory :生成的 POJO 类和 Builder 类的目标路径;
  • protocExecutable :protobuf 的 Java 代码生成工具的 protoc3.6.1.exe 可执行文件的 路径。

配置好之后,执行插件的compile 命令, Java 代码就利索生成了。

在这里插入图片描述

演示示例

添加maven依赖:

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

使用Builder 构造POJO消息对象

   public static MsgProtos.Msg buildMsg()
    {
        MsgProtos.Msg.Builder personBuilder = MsgProtos.Msg.newBuilder();
        personBuilder.setId(1000);
        personBuilder.setContent("protoBuf适用于分布式系统");
        MsgProtos.Msg message = personBuilder.build();
        return message;
    }
   

获得消息POJO 的实例之后,可以通过多种方法将 POJO 对象序列化成二进制字节,或者反序列化。

方式一:调用 Protobuf POJO 对象的 toByteArray() 方法将 POJO 对象序列化成字节数组,具体的代码如下:

  //第1种方式:序列化 serialization & 反序列化 Deserialization
    @Test
    public void serAndDesr1() throws IOException
    {
        MsgProtos.Msg message = buildMsg(1,"protoBuf适用于高并发、高性能的分布式系统");
        //将Protobuf对象,序列化成二进制字节数组
        byte[] data = message.toByteArray();

        //可以用于网络传输,保存到内存或外存
        ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
        outputStream.write(data);
        data = outputStream.toByteArray();

        //二进制字节数组,反序列化成Protobuf 对象
        MsgProtos.Msg inMsg = MsgProtos.Msg.parseFrom(data);
        Logger.info("devId:=" + inMsg.getId());
        Logger.info("content:=" + inMsg.getContent());
    }

这种方式类似于普通Java 对象的序列化,适用于很多将 Protobuf 的 POJO 序列化到内存或 者外存(如物理硬盘)的应用场景。

方式二:通过调用Protobuf 生成的 POJO 对象的 writeTo (OutputStream )方法将 POJO 对象 的二进制字节写出到输出流。通过调用 Protobuf 生成的 POJO 对象的 parseFrom( InputStream) 方法, Protobuf 从输入流中读取二进制码然后反序列化,得到 POJO 新的实例。具体的代码如下:

//第2种方式:序列化 serialization & 反序列化 Deserialization
    @Test
    public void serAndDesr2() throws IOException
    {
        MsgProtos.Msg message = buildMsg();
        //序列化到二进制流
        ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
        message.writeTo(outputStream);
        ByteArrayInputStream inputStream = new ByteArrayInputStream(outputStream.toByteArray());
        //从二进流,反序列化成Protobuf 对象
        MsgProtos.Msg inMsg = MsgProtos.Msg.parseFrom(inputStream);
        Logger.info("devId:=" + inMsg.getId());
        Logger.info("content:=" + inMsg.getContent());
    }

在阻塞式的二进制码流传输应用场景中,这种序列化和反序列化的方式是没有问题的。 例如,可以将二进制码流写入阻塞式的 Java OIO 套接字或者输出到文件。但是,这种方式在 异步操作的 NIO 应用场景中,存在粘包半包的问题。

方式三:通过调用Protobuf 生成的 POJO 对象的 writeDelimitedTo( OutputStream )方法在序列化的字节码之前添加了字节数组的长度。

 //第3种方式:序列化 serialization & 反序列化 Deserialization
    //带字节长度:[字节长度][字节数据],解决粘包问题
    @Test
    public void serAndDesr3() throws IOException
    {
        MsgProtos.Msg message = buildMsg();
        //序列化到二进制流
        ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
        message.writeDelimitedTo(outputStream);
        ByteArrayInputStream inputStream = new ByteArrayInputStream(outputStream.toByteArray());
        //从二进流,反序列化成Protobuf 对象
        MsgProtos.Msg inMsg = MsgProtos.Msg.parseDelimitedFrom(inputStream);
        Logger.info("devId:=" + inMsg.getId());
        Logger.info("content:=" + inMsg.getContent());


    }

这种方式可以用于异步操作的NIO 应用场景中,解决了粘包半包的问题。

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

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

相关文章

Git使用方法与IDEA集成Git

1.Git介绍 1.1版本控制(理解) 无论是代码编写&#xff0c;还是文档编写&#xff0c;我们都会遇到对文档内容反复修改的情况。 1.2开发中存在的问题(理解) 程序员小明负责的模块就要完成了&#xff0c;就在即将提交发布之前的一瞬间&#xff0c;电脑突然蓝屏&#xff0c;硬盘…

使用RNN联合注意力机制实现机器翻译

https://zhuanlan.zhihu.com/p/28834212 具体来自这一篇文章的指导 一、相关使用的查漏补缺&#xff1a; 1.其中的两种神奇的处理字符的操作&#xff1a; 2.关于nn.GRU()的参数解释和用法&#xff1a; http://t.csdn.cn/30PZL 这篇文章讲得很清楚&#xff0c;需要用来预测…

windows下mysql的高可用方案

PS:理论上linux下也可以使用这种方案 环境准备&#xff1a; 首先准备两台电脑&#xff0c;全部安装MySQL&#xff0c;然后分别查看一下ip地址&#xff0c;我的两个ip分别是&#xff1a; 192.168.25.134&#xff08;简称134&#xff09; 192.168.25.135&#xff08;简称135&a…

亚马逊日本站养号测评需要哪些资源和注意点,如何确保账号的稳定性和纯净环境?

日本亚马Amazon.co.jp逊简称日亚&#xff0c;在日本国内亚马逊是アマゾン&#xff0c;日本亚马逊是美国亚马逊在日本成立的分公司&#xff0c;于2000年开通。 目前亚马逊日本站的情况是&#xff0c;流量大&#xff0c;产品少。有很多美国的卖家之间把亚马逊北美站的热卖产品加…

malloc是如何实现内存分配的?【深入理解】

文章目录 前言一、malloc实现原理概括&#xff1f;二、brk() 函数与mmap()函数三、mmap实现原理普通读写与mmap对比mmap内存映射实现过程mmap 的适用场景 前言 在C和C中&#xff0c;malloc函数是用于动态分配内存的常用函数。本文将深入探究malloc函数的内存分配实现机制&…

Linux 定时任务Crontab详解及常见问题解决

Linux 定时任务Crondtab简单了解 crond 是linux下用来周期性的执行某种任务或等待处理某些事件的一个守护进程&#xff0c;与windows下的计划任务类似&#xff0c;当安装完成操作系统后&#xff0c;默认会安装此服务 工具&#xff0c;并且会自动启动crond进程&#xff0c;cron…

Python 变量作用域

视频版教程 Python3零基础7天入门实战视频教程 在程序中定义一个变量时&#xff0c;这个变量是有作用范围的&#xff0c;变量的作用范围被称为它的作用域。根据定义变量的位置&#xff0c;变量分为两种。 局部变量。在函数中定义的变量&#xff0c;包括参数&#xff0c;都被称…

Django:四、Djiango如何连接使用MySQL数据库

一、安装数据库第三方插件 安装下载mysql第三方插件 pip install mysqlclient 二、创建MySQL数据库 ORM可以帮助我们做两件事&#xff1a; 创建、修改、删除数据库中的表&#xff08;不用写SQL语句&#xff09;&#xff0c;但无法创建数据库操作表中的数据&#xff08;不用…

长胜证券:煤价突破900元大关 GLP-1减重药进入集中获批期

上星期五&#xff0c;两市股指早盘震动上扬&#xff0c;午后回落走低。到收盘&#xff0c;沪指跌0.28%报3117.74点&#xff0c;深成指跌0.52%报10144.59点&#xff0c;创业板指涨跌0.45%报2002.73点&#xff0c;科创50指数涨0.71%&#xff1b;两市合计成交7217亿元&#xff0c;…

Linux系统调试篇——错误码介绍

文章目录 错误码错误码案例goto语句 错误码 在处理一些程序出错语句中&#xff0c;不管是用户空间还是内核空间&#xff0c;通常都会返回一个错误码。例如return -ERROR。 这些错误码是Linux内核定义的&#xff0c;它几乎包括了我们能想到的所有错误类型。 错误码的定义位于&…

GeoServer(配合Tomcat)安装与配置

GeoServer是什么&#xff1f; GeoServer是用于共享地理空间数据的开源服务器。专为互操作性而设计&#xff0c;它使用开放标准发布来自任何主要空间数据源的数据。GeoServer实现了行业标准的OGC协议&#xff0c;例如Web功能服务 (WFS)&#xff0c;Web地图服务 (WMS) 和Web覆盖…

HTML5数据推送SSE原理及应用开发

JavaScript表达行为&#xff0c;CSS表达外观&#xff0c;注意HTML既表达结构&#xff08;逻辑结构&#xff09;&#xff0c;又表达内容&#xff08;数据本身&#xff09;通常需要更新数据时&#xff0c;并不需要更新结构&#xff0c;正是这种不改变组织结构仅改变数据的诉求&am…

长胜证券:开盘竞价买卖技巧?

开盘竞价是股票生意进程中的一个重要环节&#xff0c;对于出资者来说&#xff0c;怎么在这个短暂的时间内下单买入或卖出股票&#xff0c;成为了检测出资者生意技巧的重要挑战。 一、认识开盘竞价 开盘竞价是指在股票商场开盘前&#xff0c;一切买进卖出单据的价格在必定的时间…

20个最佳实践提升Terraform工作流程|Part 1

Terraform 是管理基础设施及代码&#xff08;IaC&#xff09;最常用的工具之一&#xff0c;它能使我们安全且可预测地对基础设施应用更改。刚开始上手 Terraform 可能会感觉有些不容易&#xff0c;但很快就能对该工具有基本的了解&#xff0c;随之可以开始运行命令、创建和重构…

达观RPA实战-自定义控件创建excel表头

一、应用背景 工作中我们经常会对excel文件进行操作,比如获取表格数据后,需要在空白excel文件中先写入表头,在逐行写入数据。如果每次都需要在流程中进行表头的写入,流程会看起来很臃肿。此时,我们可以充分利用达观RPA中自定义控件来创建一个。后续涉及到写excel表数据,…

Sqilte3初步教程

文章目录 安装创建数据库创建和删除表插入行数据 安装 Windows下安装&#xff0c;首先到下载页面&#xff0c;下载Windows安装软件&#xff0c;一般是 sqlite-dll-win32-*.zip sqlite-tools-win32-*.zip下载之后将 其内容解压到同一个文件夹下&#xff0c;我把它们都放在了D…

儿童用白炽灯和护眼灯哪个好一点?适合儿童使用的台灯推荐

现今的近视已然成为普遍现象&#xff0c;而且有往低年龄段发展的趋势。对孩子来说&#xff0c;日常孩子在家里抹黑看书&#xff0c;晚上看手机不开灯等习惯&#xff0c;都会导致眼睛受损&#xff0c;继而引发近视。所以给孩子挑选一款合适的台灯还是很重要的&#xff01;那么儿…

LeetCode 1159.市场分析2

数据准备 Create table If Not Exists Users (user_id int, join_date date, favorite_brand varchar(10)); Create table If Not Exists Orders (order_id int, order_date date, item_id int, buyer_id int, seller_id int); Create table If Not Exists Items (item_id int…

面试官问你前端性能优化时,他想问什么?

一直以来&#xff0c;前端性能优化都是面试过程中考察的热点题目。 相关的技术博客也层不出穷&#xff0c;我们总是能找到很多这样的文章&#xff0c; 从一个应用的各个层面开始分析&#xff0c;优化的种种手段&#xff0c;取得的种种效果。 往往篇幅越长&#xff0c;讲得越…

jQuery 框架学习笔记(基础)

What jQuery 是一种快速、简洁跨游览器的 JavaScript 函数库&#xff0c;其宗旨是“Write less, Do more”&#xff0c;它封装JavaScript常用的功能代码&#xff0c;提供一种简便的JavaScript设计模式&#xff0c;优化HTML文档操作、事件处理、动画设计和Ajax交互。 注意&…