GoLong的学习之路,进阶,微服务之序列化协议,Protocol Buffers V3

这章是接上一章,使用RPC包,序列化中没有详细去讲,因为这一块需要看的和学习的地方很多。并且这一块是RPC中可以说是最重要的一块,也是性能的重要影响因子。今天这篇主要会讲其使用方式。

文章目录

  • Protocol Buffers V3 背景以及概念
  • 如何使用?
    • 分配字段编号
      • 指定字段规则
    • 数据类型
      • `标量类型`
      • `复合类型`
        • 枚举
        • 其他消息类型
        • 嵌套类型
        • 更新消息类型
        • 未知字段
        • Any
        • oneof
          • oneof 特性
          • 向后兼容性问题
        • Maps
  • JSON 映射

Protocol Buffers V3 背景以及概念

序列化是系统通信的基础组件,在大数据AI框架云原生分布式系统中广泛使用。

当对象需要跨进程跨语言跨节点传输持久化状态读写复制时,都需要进行序列化,其性能易用性影响运行效率开发效率

但是对于序列化框架而言,业内将其分为两类:静态序列化框架,动态序列化框架。

其中各有优缺点:

  • 静态序列化框架
    • 不支持对象引用和多态、需要提前生成代码等原因,无法作为领域对象直接面向应用进行跨语言开发
    • 常见的有:protobufflatbufferthrift
  • 动态序列化框架
    • 提供了易用性和动态性,但不支持跨语言,且性能存在显著不足,并不能满足高吞吐、低延迟和大规模数据传输场景需求
    • 常见的有:JDK序列化KryoFstHessianPickle

但是前几天阿里推出了Fury,号称比JDK快了170倍,并且兼具静态序列化和动态序列化的优点。这个下一章会讲。目前的gRPC框架是用protobuf搭建的。所以咱们先让自己的项目成功运行起来再说。所以这个后面再说。

Protocol Buffersgoogle开源的一种结构数据序列化机制,可跨语言、跨平台。

相比XMLJSONThrift等其他序列化格式,Protocol Buffers的序列化和反序列化性能是很高的,且Protocol Buffers序列化后是二进制流,因此数据大小和传输速度是很好的。

所以它非常适合在数据存储RPC 数据交换的场景下使用。

以下使用手法是翻译自官网

如何使用?

定义一个 .proto 文件

定义一个搜索请求消息格式,其中每个搜索请求都包含:

  • 一个查询词字符串
  • 你感兴趣的查询结果所在的特定页码数
  • 每一页应展示的结果数
syntax = "proto3";

message SearchRequest {
  string query = 1;
  int32 page_number = 2;
  int32 result_per_page = 3;
}
  • 文件的第一行指定使用 proto3 语法。

如果不这样写,protocol buffer编译器将假定你使用 proto2。这个声明必须是文件的第一个非空非注释行。

  • SearchRequest 消息定义指定了三个字段(名称/值对) ,每个字段表示希望包含在此类消息中的每一段数据。每个字段都有一个名称和一个类型

指定字段类型:

  • 两个整数page_number result_per_page和一个字符串query(上面的例子)
  • 但是也可以为字段指定组合类型,包括枚举和其他消息类型

proto3语法主要包括:

  1. 消息(message)定义
  2. 服务(service)定义
  3. 其他部分语法

一个message内的字段一般包含:

  • 数据类型
  • 字段名
  • 字段编号tag

添加更多消息类型:

可以在一个.proto 文件中定义多个消息类型。定义与 SearchRequest 消息类型对应的应答消息格式SearchResponse,就可以将其添加到同一个.proto文件中。

message SearchRequest {
  string query = 1;
  int32 page_number = 2;
  int32 result_per_page = 3;
}

message SearchResponse {
 ...
}

添加注释:

要给你的.proto文件添加注释,需要使用C/C++风格的///* ... */语法。

保留字段:

  • 如果通过完全删除字段或将其注释掉来更新消息类型,那么未来的用户在对该类型进行自己的更新时可以重用字段号。
  • 如果其他人以后加载旧版本的相同.proto文件,这可能会导致严重的问题,包括数据损坏,隐私漏洞等等。

分配字段编号

消息定义中的每个字段都有一个 唯一的编号

这些字段编号用来在消息二进制格式中标识字段,在消息类型使用后就不能再更改

注意

  • 范围115中的字段编号需要一个字节进行编码,包括字段编号和字段类型
  • 范围162047的字段编号采用两个字节

应该为经常使用的消息元素保留数字1到15的编号。切记为将来可能添加的经常使用的元素留出一些编号。

可以指定的最小字段数是 1 ,最大的字段数是 2 29 − 1 可以指定的最小字段数是1,最大的字段数是 2^{29}−1 可以指定的最小字段数是1,最大的字段数是2291
即536,870,911。

也不能使用1900019999,它们是预留给Protocol Buffers协议实现的。

如果你在你的.proto文件中使用了预留的编号Protocol Buffers编译器就会报错。

同样,你也不能使用任何之前保留的字段编号。

指定字段规则

消息字段可以是下列字段之一:

  • singular: 格式正确的消息可以有这个字段的零个或一个(但不能多于一个)。这是 proto3语法的默认字段规则。
  • repeated: 该字段可以在格式正确的消息中重复任意次数(包括零次)。重复值的顺序将被保留。

确保这种情况不会发生的一种方法是指定已删除字段的字段编号(和/名称,这也可能导致 JSON 序列化问题)是保留的reserved

如果将来有任何用户尝试使用这些字段标识符,protocol buffer编译器将发出提示。

message Foo {
  reserved 2, 15, 9 to 11;
  reserved "foo", "bar";
}

注意:不能在同一个reserved语句中混合字段名字段编号

当你使用 protocol buffer 编译器来运行.proto文件时,编译器用你选择的语言生成你需要使用文件中描述的消息类型,包括获取和设置字段值,将消息序列化为输出流,以及从输入流解析消息的代码。

  • C++来说,编译器会为每个.proto文件生成一个.h文件和一个.cc文件,.proto文件中的每一个消息有一个对应的类。
  • 对于 Java,编译器生成一个.java 文件,每种消息类型都有一个类,还有一个特殊的 Builder 类用于创建消息类实例。
  • 对于 Kotlin,除了 Java 生成的代码之外,编译器还生成一个每种消息类型的 .kt 文件,包含一个 DSL,可用于简化消息实例的创建。
  • Python 稍有不同ー Python 编译器为.proto文件中的每个消息类型生成一个带静态描述符的模块,然后与 metaclass 一起使用,在运行时创建必要的 Python 数据访问类。
  • 对于 Go,编译器为文件中的每种消息类型生成一个类型(type)到一个.pb.go 文件。
  • 对于 Ruby,编译器生成一个.rb 文件,其中包含一个包含消息类型的 Ruby 模块。
  • 对于 Objective-C,编译器从每个.proto文件生成一个 pbobjc.h 和 pbobjc.m 文件,.proto文件中描述的每种消息类型都有一个类。
  • 对于 C# ,编译器生从每个.proto文件生成一个.cs 文件。.proto文件中描述的每种消息类型都有一个类。
  • 对于 Dart,编译器为文件中的每种消息类型生成一个.pb.dart 文件。

数据类型

数据类型:标量类型复合类型

标量类型

在这里插入图片描述
当解析消息时,如果编码消息不包含特定的 singular 元素,则解析对象中的相应字段将设置为该字段的默认值。

  • 对于字符串,默认值为空字符串。
  • 对于字节,默认值为空字节。
  • 对于布尔值,默认值为 false。
  • 对于数值类型,默认值为零。
  • 对于枚举,默认值是第一个定义的枚举值,该值必须为0。
  • 对于消息字段,未设置该字段。其确切值与语言有关。
  • repeated 字段的默认值为空(通常是适当语言中的空列表)。

请注意,对于标量消息字段,一旦消息被解析,就无法判断字段是显式设置为默认值(例如,是否一个布尔值是被设置为 false)还是根本没有设置: 在定义消息类型时应该牢记这一点。例如,如果你不希望某个行为在默认情况下也发生,那么就不要设置一个布尔值,该布尔值在设置为 false 时会开启某些行为。还要注意,如果将标量消息字段设置为默认值,则该值将不会在传输过程中序列化。

复合类型

复合类型包括:枚举、嵌套其他message、Any(Map,Oneof)等

枚举

在定义消息类型时,你可能希望其中一个字段只能是预定义的值列表中的一个值。

可以通过在消息定义中添加一个枚举,为每个可能的值添加一个常量来非常简单地完成这项工作。

message SearchRequest {
  string query = 1;
  int32 page_number = 2;
  int32 result_per_page = 3;
  enum Corpus {
    UNIVERSAL = 0;
    WEB = 1;
    IMAGES = 2;
    LOCAL = 3;
    NEWS = 4;
    PRODUCTS = 5;
    VIDEO = 6;
  }
  Corpus corpus = 4;
}

Corpus enum 的第一个常量映射为零: 每个 enum 定义必须包含一个常量,该常量映射为零作为它的第一个元素。

  1. 必须有一个零值,这样我们就可以使用0作为数值默认值。
  2. 零值必须是第一个元素,以便与 proto2语义兼容,其中第一个枚举值总是默认值。

你可以通过将相同的值分配给不同的枚举常量来定义别名。为此,你需要将 allow _ alias 选项设置为 true,否则,当发现别名时,protocol 编译器将生成错误消息。

内部定义

message MyMessage1 {
  enum EnumAllowingAlias {
    option allow_alias = true;
    UNKNOWN = 0;
    STARTED = 1;
    RUNNING = 1;
  }
}
message MyMessage2 {
  enum EnumNotAllowingAlias {
    UNKNOWN = 0;
    STARTED = 1;
    // RUNNING = 1;  // Uncommenting this line will cause a compile error inside Google and a warning message outside.
  }
}

注意:枚举的常数必须在32位整数的范围内。

由于枚举值在传输时使用变长编码,因此负值效率低,因此不推荐使用。

可以在消息定义中定义枚举,如上面的例子所示,也可以在外面定义——这样就可以在.proto文件中的消息定义中重用这些枚举。

外部定义

enum Ezarten {
        option allow_alias = true; //开启枚举值重复开关
        ZARTEN1 = 0;
        ZARTEN2 = 1;
        ZARTEN3 = 2;
        ZARTEN4 = 2; //开启option allow_alias = true后枚举值可以重复
    }

//定义一个message类型
message ZartenOne {
    string name = 1;
    int32 age = 2;
    int32 height = 3;
    Ezarten ezarten = 4;
}

可以使用_MessageType_._EnumType_ 语法,使用在一个消息中声明的enum类型作为不同消息中的字段类型。

使用消息内的枚举
若枚举定义在内部,其他message要使用这个枚举,可以使用 “message名.枚举名”的形式:

//定义一个message类型
message ZartenOne {
    string name = 1;
    int32 age = 2;
    int32 height = 3;
    enum Ezarten {
        ZARTEN1 = 0;
        ZARTEN2 = 1;
        ZARTEN3 = 2;
    }
    Ezarten ezarten = 4;
}

//定义一个message类型
message ZartenTwo {
    string name = 1;
    int32 age = 2;
    int32 height = 3;
    ZartenOne.Ezarten ezarten = 4;
}

当对一个使用了枚举的.proto文件运行 protocol buffer 编译器的时候,对于 Java Kotlin,或 C++ 生成的代码中将有一个对应的enum,或者对于 Python 会生成一个特殊的EnumDescriptor类,它被用于在运行时生成的类中创建一组带有整数值的符号常量。

生成的代码可能会受到特定于语言的枚举数限制(单种语言的数量低于千)

反序列化过程中,不可识别的枚举值将保留在消息中,尽管当消息被反序列化时,这种值的表示方式依赖于语言。

  • 开放枚举:在支持值超出指定符号范围(如 C++ 和 Go)开放枚举类型的语言中,未知枚举值仅存储为其底层的整数表示形式。
  • 闭合枚举类型:在具有闭合枚举类型 (如 Java)的语言中,枚举中的一个类型将用于表示一个无法识别的值,并且可以使用特殊的访问器访问底层的整数。

在这两种情况下,如果消息被序列化,那么不可识别的值仍然会与消息一起被序列化。

其他消息类型

你可以使用其他消息类型作为字段类型:

message SearchResponse {
  repeated Result results = 1;
}

message Result {
  string url = 1;
  string title = 2;
  repeated string snippets = 3;
}

如果你希望用作字段类型的消息类型已经在另一个.proto文件中定义了,该怎么办?

你可以通过 import 来使用来自其他.proto文件的定义。要导入另一个.proto 的定义,你需要在文件顶部添加一个import语句

import "myproject/other_protos.proto";

默认情况下,只能从直接导入的.proto文件中使用定义。但是,有时你可能需要将 .proto 文件移动到新的位置。你可以在旧目录放一个占位的.proto文件使用import public 概念将所有导入转发到新位置,而不必直接移动.proto文件并修改所有的地方。

import public依赖项可以被任何导入包含import public语句的proto的代码传递依赖。
语法:

  • import
  • import public
  1. 相同的是,在文件A中两者可以直接引用它们上一级proto文件B的内容。
  2. 不同的是,若文件B内使用了import引用文件C,则文件A不能使用文件C的内容;

若文件B内使用了import public引用文件C,则文件A可以使用文件C的内容。

类似于编程语言中的类是否可以继承的含义。

文件:new.proto

所有的定义都被移到了这里

文件:old.proto
这是所有客户端都要导入的原型

import public "new.proto";
import "other.proto";

文件:client.proto
你可以使用old.protonew.proto,但是不能使用 other.proto

protocol 编译器使用命令行-I/--proto_path参数指定的一组目录中搜索导入的文件。如果没有给该命令行参数,则查看调用编译器的目录。

一般来说,你应该将--proto_path参数设置为项目的根目录并为所有导入使用正确的名称。

嵌套类型

方式1:

message SearchResponse {
  message Result {
    string url = 1;
    string title = 2;
    repeated string snippets = 3;
  }
  repeated Result results = 1;
}

方式2:
要在其父消息类型之外重用此消息类型,通过_Parent_._Type_使用。

message SomeOtherMessage {
  SearchResponse.Result result = 1;
}

方式3:一层又一层嵌入其中

message Outer {                  // Level 0
  message MiddleAA {  // Level 1
    message Inner {   // Level 2
      int64 ival = 1;
      bool  booly = 2;
    }
  }
  message MiddleBB {  // Level 1
    message Inner {   // Level 2
      int32 ival = 1;
      bool  booly = 2;
    }
  }
}
更新消息类型

在不破坏任何现有代码的情况下更新消息类型非常简单:只需记住以下规则:

  • 不要更改任何现有字段的字段编号
  • 如果添加新字段
    • 那么任何使用“旧”消息格式通过代码序列化的消息仍然可以通过新生成的代码进行解析。
      • 你应该记住这些元素的默认值,以便新代码能够正确地与旧代码生成的消息交互。
      • 类似地,新代码创建的消息可以通过旧代码解析: 旧的二进制文件在解析时直接忽略新字段。
  • 字段可以被删除,只要字段编号不再用于你更新的消息类型。
    • 你可能希望改为重命名字段,或者为其添加"OBSOLETE_“前缀,或者声明字段编号为reserved,以便.proto的未来用户不可能不小心重复使用这个编号。
  • int32uint32int64uint64bool都是兼容的——这意味着你可以在不破坏向前或向后兼容性的情况下将一个字段从这些类型中的一个更改为另一个。
  • 如果一个数字被解析到一个并不适当的类型中,你会得到与在 C++ 中将数字转换为该类型相同的效果
    • 例如,如果一个64位的数字被读作 int32,它将被截断为32位
  • sint32sint64相互兼容,但与其他整数类型不兼容
  • stringbytes是兼容的,只要字节是有效的 UTF-8
  • 如果字节包含消息的编码版本,则嵌入的消息与bytes兼容
  • fixed32sfixed32兼容 fixed64sfixed64兼容。
  • 对于stringbytes消息字段optional字段与repeated字段兼容。
    • 给定重复字段的序列化数据作为输入
      • 如果该字段是基本类型字段,期望该字段为可选字段的客户端将接受最后一个输入值
      • 如果该字段是消息类型字段,则合并所有输入元素
    • 注意,这对于数字类型,包括 boolsenums通常是不安全的。
      • 重复的数值类型字段可以按packed的格式序列化,如果是optional字段,则无法正确解析这些字段
  • Enum 在格式方面与 int32uint32int64uint64兼容(请注意,如果不适合,值将被截断)。
    • 但是要注意,当消息被反序列化时,客户端代码可能会区别对待它们:
      • 例如,未被识别的 proto3 enum将保留在消息中,但是当消息被反序列化时,这种类型的表示方式依赖于语言。Int字段总是保留它们的值。
  • 将单个值更改为新的oneof成员是安全的,并且二进制兼容。
    • 如果确保没有代码一次设置多个字段,那么将多个字段移动到新的oneof字段中可能是安全的。
    • 将任何字段移动到现有的字段中都是不安全的。
未知字段

未知字段是格式良好的协议缓冲区序列化数据,表示解析器不识别的字段。
旧二进制解析由新二进制发送的带有新字段的数据时,这些新字段将成为旧二进制中的未知字段

3.5版本中,我们重新引入了未知字段的保存来匹配 proto2行为。在3.5及以后的版本中,解析期间保留未知字段,并将其包含在序列化输出中

Any

Any 消息类型允许你将消息作为嵌入类型使用,而不需要其.proto定义。Any包含一个任意序列化的字节消息,以及一个解析为该消息的类型作为消息的全局唯一标识符的URL

要使用Any类型,需要导入google/protobuf/any.proto

给定消息类型的默认类型 URLtype.googleapis.com/_packagename_._messagename_

import "google/protobuf/any.proto";

message ErrorStatus {
  string message = 1;
  repeated google.protobuf.Any details = 2;
}

不同的语言实现将支持运行库助手以类型安全的方式打包和解包 Any值:

  • 在java中,Any类型会有特殊的pack()和unpack()访问器,

  • 在C++中,Any类型会有特殊的PackFrom()和UnpackTo()方法。

oneof

如果你有一条包含多个字段的消息,并且最多同时设置其中一个字段,那么你可以通过使用oneof来实现并节省内存,优化

oneof字段类似于常规字段,只不过oneof中的所有字段共享内存,而且最多可以同时设置一个字段。

设置其中的任何成员都会自动清除所有其他成员。

根据所选择的语言,可以使用特殊 case()WhichOneof() 方法检查 oneof 中的哪个值被设置

在生成的代码中,其中一个字段具有与常规字段相同的 getter setter

你还可以获得一个特殊的方法来检查其中一个设置了哪个值

message SampleMessage {
  oneof test_oneof {
    string name = 4;
    SubMessage sub_message = 9;
  }
}

然后将其中一个字段添加到该字段的定义中。

你可以添加任何类型的字段,除了map字段repeated字段

oneof有很多特性,具体的个人建议去看文档:

oneof 特性
  • 设置一个字段将自动清除该字段的所有其他成员。
    • 因此,如果你设置了多个 oneof字段,那么只有最后设置的字段仍然具有值。
  • 如果解析器在连接中遇到同一个成员的多个成员,则只有最后看到的成员用于解析消息。
  • oneof 不支持repeated。
  • 反射 api 适用于 oneof 字段。
  • 如果将 oneof 字段设置为默认值(例如将 int32 oneof 字段设置为0) ,则将设置该字段的“ case”,并在连接上序列化该值。
  • 如果使用 C++ ,确保你的代码不会导致内存崩溃。
    • 因为通过调用 set_name()方法已经删除了 sub_message
SampleMessage message;
SubMessage* sub_message = message.mutable_sub_message();
message.set_name("name");      // 删除name
sub_message->set_...            // 这里的崩溃
  • 在C++中,如果你使用Swap()两个 oneof 消息,每个消息,两个消息将拥有对方的值
SampleMessage msg1;
msg1.set_name("name");
SampleMessage msg2;
msg2.mutable_sub_message();
msg1.swap(&msg2);
CHECK(msg1.has_sub_message());
CHECK(msg2.has_name());
向后兼容性问题

添加或删除一个字段时要小心。

  • 如果检查one of的值返回None/NOT_SET,这可能意味着 one of 没有被设置,或者它已经被设置为one of的不同版本中的一个字段。
  • 这没有办法区分,因为没有办法知道未知字段是否是 oneof 的成员。

标签重用问题:

  • 将字段移入移出 oneof:在序列化和解析消息之后,你可能会丢失一些信息(某些字段将被清除)。但是,你可以安全地将单个字段移动到新的 oneof 字段中,并且如果已知只设置了一个字段,则可以移动多个字段。
  • 删除一个oneof 字段再添加回来:这可能会在消息被序列化和解析后清除当前设置的 oneof 字段。
  • 拆分或合并oneof:这与移动常规字段有类似的问题。
Maps

如果你想创建一个关联映射作为你数据定义的一部分。

map<key_type, value_type> map_field = N;

  • 其中key_type可以是任何整型或字符串类型(除了浮点类型和字节以外的任何标量类型)
  • value_type可以是除另一个映射以外的任何类型。
map<string, Project> projects = 3;
  • 映射字段不能重复。
  • 映射值的有线格式排序和映射迭代排序是未定义的,因此不能依赖于映射项的特定排序。
  • 当为 .proto 生成文本格式时,映射按键排序。数字键按数字排序。
  • 当从连接解析或合并时,如果有重复的映射键,则使用最后看到的键。当从文本格式解析映射时,如果有重复的键,解析可能会失败
  • 如果为映射字段提供了键但没有值,则该字段序列化时的行为与语言相关。在 C++ Java KotlinPython 中,类型的默认值是序列化的,而在其他语言中,没有任何值是序列化的。

JSON 映射

proto3支持 JSON 的规范编码,使得系统之间更容易共享数据。下表按类型逐一描述了编码。

如果 json 编码的数据中缺少某个值,或者该值为 null,那么在解析为protocol buffer时,该值将被解释为适当的默认值。如果一个字段在 protocol buffer 中具有默认值,为了节省空间,默认情况下 json 编码的数据中将省略该字段。具体实现可以提供在JSON编码中可选的默认值。

在这里插入图片描述
在这里插入图片描述
一个proto3协议 JSON 实现可能提供以下选项:

  • 提供默认值的字段在proto3 JSON 输出中,值为默认值的字段被省略。可以提供一个选项,用默认值覆盖此行为和输出字段。
  • 忽略位置字段在缺省情况下,Proto3 JSON 解析器应该拒绝未知字段,但在解析过程中可能会提供一个忽略未知字段的选项。
  • 使用 proto 字段名而不是小驼峰名称默认情况下,proto3 JSON 打印机应该将字段名转换为 lowerCamelCase,并使用它作为 JSON 名称
    • 可以提供一个选项,用原型字段名作为 JSON 名。需要协议3 JSON 解析器同时接受转换后的 lowerCamelCase `名称和原始字段名称。
  • 以整数而不是字符串形式展示枚举值在 JSON 输出中,默认情况下使用枚举值的名称。可以提供一个选项来代替使用枚举值的数值。

剩下的可以看:官方文档

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

网络安全项目实战(四)--报文检测

8. TCP/UDP 段 目标 了解 TCP 段头的组织结构了解 UDP 段头的组织结构掌握 TCP/UDP 段的解析方式 8.1. UDP 段格式 下图是UDP的段格式&#xff08;该图出自[TCPIP]&#xff09;。 8.2. UDP头部 //UDP头部&#xff0c;总长度8字节// /usr/include/linux/udp.h struct udphdr …

Arris VAP2500 list_mac_address未授权RCE漏洞复现

0x01 产品简介 Arris VAP2500是美国Arris集团公司的一款无线接入器产品。 0x02 漏洞概述 Arris VAP2500 list_mac_address接口处命令执行漏洞,未授权的攻击者可通过该漏洞在服务器端任意执行代码,写入后门,获取服务器权限,进而控制整个web服务器。 0x03 复现环境 FOFA…

SpringSecurity6从入门到上天系列第八篇:SpringSecurity当中的默认登录页面是如何产生的?

&#x1f609;&#x1f609; 欢迎加入我们的学习交流群呀&#xff01; ✅✅1&#xff1a;这是孙哥suns给大家的福利&#xff01; ✨✨2&#xff1a;我们免费分享Netty、Dubbo、k8s、Mybatis、Spring等等很多应用和源码级别的高质量视频和笔记资料&#xff0c;你想学的我们这里都…

Axure中动态面板使用及轮播图多种登录方式左侧导航栏之案列

&#x1f3ac; 艳艳耶✌️&#xff1a;个人主页 &#x1f525; 个人专栏 &#xff1a;《产品经理如何画泳道图&流程图》 ⛺️ 越努力 &#xff0c;越幸运 目录 一、轮播图简介 1、什么是轮播图 2、轮播图有什么作用 3、轮播图有什么特点 4、轮播图适应范围 5、…

pytorch文本分类(二):引入pytorch处理文本数据

pytorch文本数据处理 目录 pytorch文本数据处理1. Pytorch背景2. 数据分割3. 数据加载Dataset代码分析字典的用途代码修改的目的 Dataloader 4. 练习 原学习任务链接 相关数据链接&#xff1a;https://pan.baidu.com/s/1iwE3LdRv3uAkGGI2fF9BjA?pwdro0v 提取码&#xff1a;ro…

日志框架Log4j、JUL、JCL、Slf4j、Logback、Log4j2

为什么程序需要记录日志 我们不可能实时的24小时对系统进行人工监控&#xff0c;那么如果程序出现异常错误时要如何排查呢&#xff1f;并且系统在运行时做了哪些事情我们又从何得知呢&#xff1f;这个时候日志这个概念就出现了&#xff0c;日志的出现对系统监控和异常分析起着…

wordpress 修改社交图标

要去掉标记的图标&#xff0c;死活找不到在那里配置。后来找到了&#xff0c;下图&#xff08;wordpress 小白&#xff0c;特此记录&#xff09;

java --- lambda表达式

1、先爽用一下lambda lambda表达式很大一个作业就是简化匿名内部类 public class case1 {public static void main(String[] args) {Integer[] arr {2,5,1,0,9,7,4};Integer[] arr2 {2,5,1,0,9,7,4};//使用匿名内部类作为比较器Arrays.sort(arr, new Comparator<Integer…

Java 实现汉字转拼音带单调

代码 import net.sourceforge.pinyin4j.PinyinHelper; import net.sourceforge.pinyin4j.format.HanyuPinyinOutputFormat; import net.sourceforge.pinyin4j.format.HanyuPinyinToneType; import net.sourceforge.pinyin4j.format.HanyuPinyinVCharType; import net.sourcefo…

【VRTK】【VR开发】【Unity】15-远程抓取

课程配套学习资源下载 https://download.csdn.net/download/weixin_41697242/88485426?spm=1001.2014.3001.5503 【背景】 之前的篇章介绍了如何实现直接抓取,本篇介绍另一种抓取方式-远程抓取。 【远程抓取的先决条件】 要让远程抓取起作用,需要先设置oculus提供的手部…

定时限电流继电器 JL-8D/2X112A5 AC220V 0.1~9.9S 0.1~9.9A 导轨

系列型号 JL-8D/3X1定时限电流继电器&#xff1b;JL-8D/3X111A2定时限电流继电器&#xff1b; JL-8D/3X121A2定时限电流继电器&#xff1b;JL-8D/3X211A2定时限电流继电器&#xff1b; JL-8D/3X221A2定时限电流继电器&#xff1b;JL-8D/3X2定时限电流继电器&#xff1b; JL…

天翼云盘秒变硬盘分享

https://cloud.189.cn/web/share?codeAvUnqaj6NNza&#xff08;访问码&#xff1a;wf4r&#xff09;y 以下介绍为作者开发的单机版软件&#xff0c;可用于Windows环境中将天翼云盘挂载为本地硬盘&#xff0c;确实可以达到本地硬盘的使用感知&#xff0c;对于多终端数据副本一…

Tomcat的结构和部署

目录 什么是tomcat&#xff1f; Tomcat 核心的组件三个 什么是 servlet&#xff1f; 什么是 JSP? Tomcat 功能组件结构&#xff1a; Container 结构分析&#xff1a; Tomcat 请求过程&#xff1a; 具体部署Tomcat 将安装包移动到opt目录下 配置systemd控制 验证访…

2 - Electron 核心概念

Electron 核心概念 主进程 通过Node.js、Electron提供的API与系统底层打交道启动项目时运行的 main.js 脚本就是我们说的主进程。在主进程运行的脚本可以以创建 Web 页面的形式展示 GUI。主进程只有一个 渲染进程 每个 Electron 的页面都在运行着自己的进程&#xff0c;这样…

每天五分钟计算机视觉:GoogLeNet的核心模型结构——Inception

本文重点 当构建卷积神经网络的时候,我们需要判断我们的过滤器的大小,这往往也作为一个超参数需要我们进行选择。过滤器的大小究竟是 11,33 还是 55,或者要不要添加池化层,这些都需要我们进行选择。而本文介绍的Inception网络的作用就是代替你来决定,把它变成参数的一部…

软件设计师——软件工程(二)

&#x1f4d1;前言 本文主要是【软件工程】——软件设计师——软件工程的文章&#xff0c;如果有什么需要改进的地方还请大佬指出⛺️ &#x1f3ac;作者简介&#xff1a;大家好&#xff0c;我是听风与他&#x1f947; ☁️博客首页&#xff1a;CSDN主页听风与他 &#x1f304…

ZLMediaKit 编译以及测试(Centos 7.9 环境)

文章目录 一、前言二、编译器1、获取代码2、编译器2.1 编译器版本要求2.2 安装编译器 3、安装cmake4、依赖库4.1 依赖库列表4.2 安装依赖库4.2.1 安装libssl-dev和libsdl-dev4.2.2 安装 ffmpeg-devel依赖和ffmpeg依赖 三、构建和编译项目&#xff08;启用WebRTC功能&#xff09…

pytorch:to()、device()、cuda()将Tensor或模型移动到指定的设备上

将Tensor或模型移动到指定的设备上&#xff1a;tensor.to(‘cuda:0’) 最开始读取数据时的tensor变量copy一份到device所指定的GPU上去&#xff0c;之后的运算都在GPU上进行在做高维特征运算的时候&#xff0c;采用GPU无疑是比用CPU效率更高&#xff0c;如果两个数据中一个加了…

智慧城市/一网统管建设:人员危险行为检测算法,为城市安全保驾护航

随着人们压力的不断增加&#xff0c;经常会看见在日常生活中由于小摩擦造成的大事故。如何在事故发生时进行及时告警&#xff0c;又如何在事故发生后进行证据搜索与事件溯源&#xff1f;旭帆科技智能视频监控人员危险行为/事件检测算法可以给出答案。 全程监控&#xff0c;有源…

智能优化算法应用:基于探路者算法3D无线传感器网络(WSN)覆盖优化 - 附代码

智能优化算法应用&#xff1a;基于探路者算法3D无线传感器网络(WSN)覆盖优化 - 附代码 文章目录 智能优化算法应用&#xff1a;基于探路者算法3D无线传感器网络(WSN)覆盖优化 - 附代码1.无线传感网络节点模型2.覆盖数学模型及分析3.探路者算法4.实验参数设定5.算法结果6.参考文…