数据序列化协议 Protobuf 3 介绍(Go 语言)
Protobuf 3 入门
1. 什么是序列化?
1.1 概念
序列化(Serialization 或 Marshalling) 是指将数据结构或对象的状态转换成可存储或传输的格式。反向操作称为反序列化(Deserialization 或 Unmarshalling),它的作用是将序列化的数据恢复成原始的数据结构或对象。
简单来说,序列化就像“打包”,反序列化就像“解包”。
2.1 为什么需要序列化?
在计算机系统中,数据通常是以内存中的对象(如 struct、class)形式存在的,而内存数据不能直接在不同程序之间传输,必须先转换成可存储或可传输的格式。序列化的主要用途包括:
- 数据存储:将数据保存到文件、数据库等,例如:
- 日志文件
- 配置文件(如 JSON、YAML)
- 持久化存储(如 Redis、MongoDB)
- 数据传输:在不同进程或网络之间传输数据,例如:
- 前端和后端通信(Web API)
- 微服务之间的通信
- 远程调用(RPC,如 gRPC)
- 数据缓存:比如将复杂的对象序列化后存入 Redis,提高访问速度。
- 跨语言兼容:不同编程语言的数据结构不一样,序列化后可以在不同语言之间传输数据。
3.1 序列化的方式
不同的序列化格式适用于不同的应用场景,常见的格式包括:
| 格式 | 特点 | 可读性 | 序列化速度 | 数据大小 | 适用场景 |
|---|---|---|---|---|---|
| JSON | 文本格式,广泛使用 | 可读 | 适中 | 较大 | Web API,前端后端通信 |
| XML | 结构化文本,标签冗余 | 可读 | 慢 | 大 | 早期 Web API,配置文件 |
| YAML | 结构更简洁,适合人阅读 | 可读 | 适中 | 较大 | 配置文件(Kubernetes、Docker) |
| Protobuf | Google 开发的高效二进制格式 | 不可读 | 快 | 小 | 微服务、gRPC、高性能应用 |
| MessagePack | 类似 JSON,但体积更小 | 不可读 | 快 | 小 | 移动端、嵌入式系统 |
| Thrift | Facebook 开发的高效序列化格式 | 不可读 | 快 | 小 | 分布式系统,RPC |
| Avro | 适用于大数据(如 Hadoop) | 不可读 | 适中 | 小 | 大数据处理 |
| BSON | MongoDB 的序列化格式 | 不可读 | 适中 | 适中 | MongoDB 数据存储 |
2. 什么是 Protobuf?
2.1 概念
Protobuf(Protocol Buffers)是 Google 开发的一种高效、跨平台、可扩展的数据序列化协议。它可以将数据转换为紧凑的二进制格式,用于不同系统之间进行高效的数据传输和存储。
简单理解:
- 它类似于 JSON,但比 JSON 体积更小、速度更快。
- 它类似于 XML,但格式更紧凑、解析更高效。
- 它适用于微服务、RPC(远程调用)、数据存储等高性能场景。
2.2 为什么使用 Protobuf?
| 特点 | Protobuf | JSON | XML |
|---|---|---|---|
| 格式 | 二进制 | 文本 | 文本 |
| 体积 | 最小 | 较大 | 最大 |
| 解析速度 | 最快 | 一般 | 最慢 |
| 可读性 | 不可读 | 可读 | 可读 |
| 跨语言支持 | 是 | 是 | 是 |
| 支持 RPC | 是(gRPC) | 否 | 否 |
如果你的项目涉及:
- 高性能数据通信(微服务、RPC、物联网、游戏服务器)
- 跨语言数据传输(Go、Java、Python、C++、Rust 等)
- 大规模数据存储(日志、数据库、缓存)
那么 Protobuf 是比 JSON、XML 更好的选择。
2.3 Protobuf 的使用场景
- 微服务通信(gRPC)
- 适用于 Go、Java、Python、C++ 等语言的微服务之间高效通信。
- 结合
gRPC使用,可以比传统REST API更快。
- 数据存储
- 存储日志、缓存数据(如存入 Redis)时,Protobuf 体积小,能节省存储空间。
- 跨语言数据交换
- 由于 Protobuf 支持多种编程语言,可以在不同语言的系统之间进行高效数据传输。
- 移动端和 IoT(物联网)
- 移动端和 IoT 设备通常带宽和存储受限,Protobuf 适用于传输小体积数据,提高性能。
3. 简单解释 Protobuf 例子
3.1 Protobuf 文件 simple.proto
syntax = "proto3"; // 使用 proto3 语法message SearchRequest { // 定义一个数据结构(类似 JSON 对象)string query = 1; // 搜索关键词(字符串)int32 page_number = 2; // 页码(整数)int32 result_per_page = 3; // 每页返回的结果数(整数)
}
解释
syntax = "proto3";指定使用proto3语法。message SearchRequest定义了一个数据结构(类似 JSON 对象)。- 每个字段的格式:
- 类型(
string、int32) - 字段名称(
query、page_number、result_per_page) - 字段编号(
1、2、3,用于唯一标识字段,不能重复)
- 类型(
3.2 编译 Protobuf 代码
Protobuf 需要编译后才能用于编程语言(Go、Java、Python 等)。 在终端运行:
protoc --go_out=. simple.proto
protoc是 Protobuf 编译器--go_out=.表示生成 Go 代码,并存放在当前目录simple.proto是需要编译的 Protobuf 文件
不同语言对应的参数:
| 语言 | 编译参数 |
|---|---|
| C++ | --cpp_out=. |
| Java | --java_out=. |
| Python | --python_out=. |
| C# | --csharp_out=. |
| Rust | --rust_out=. |
最终会生成 simple.pb.go,这个文件包含 Go 代码,用于操作 SearchRequest 结构。
3.3 生成的 Go 代码
编译后会生成如下 Go 结构:
type SearchRequest struct {Query string `protobuf:"bytes,1,opt,name=query,proto3" json:"query,omitempty"`PageNumber int32 `protobuf:"varint,2,opt,name=page_number,json=pageNumber,proto3" json:"page_number,omitempty"`ResultPerPage int32 `protobuf:"varint,3,opt,name=result_per_page,json=resultPerPage,proto3" json:"result_per_page,omitempty"`
}
解释
SearchRequest是struct,对应.proto文件中的message SearchRequest。Query、PageNumber、ResultPerPage变量对应.proto里的字段。protobuf:"..."里的信息用于 Protobuf 序列化和解析。
3.4 如何使用这个 Go 结构
package mainimport ("fmt""google.golang.org/protobuf/proto"
)func main() {// 创建 SearchRequest 实例request := &SearchRequest{Query: "golang protobuf",PageNumber: 1,ResultPerPage: 10,}// **序列化**data, _ := proto.Marshal(request)// **反序列化**newRequest := &SearchRequest{}proto.Unmarshal(data, newRequest)fmt.Println(newRequest) // 输出: {Query:golang protobuf PageNumber:1 ResultPerPage:10}
}
解释
- 创建
SearchRequest结构,并填充数据。 - 使用
proto.Marshal(request)序列化,转换成二进制格式(适合网络传输)。 - 使用
proto.Unmarshal(data, newRequest)反序列化,把二进制恢复成 Go 结构。
4. Protobuf 的数据类型
4.1 标量数据类型(Scalar Types)
Protobuf 提供了一些常见的基本数据类型,对应不同语言的变量类型。
4.1.1 数值类型
| Protobuf 类型 | 说明 | 适用场景 |
|---|---|---|
int32 | 32 位整数(默认编码) | 适用于较小的整数 |
int64 | 64 位整数(默认编码) | 适用于较大的整数 |
uint32 | 无符号 32 位整数 | 适用于只能为正数的情况 |
uint64 | 无符号 64 位整数 | 适用于大数且不允许负数 |
sint32 | 32 位有符号整数(ZigZag 编码) | 适用于可能包含负数的整数 |
sint64 | 64 位有符号整数(ZigZag 编码) | 适用于包含负数的长整数 |
fixed32 | 32 位整数(固定长度编码) | 适用于数值分布较均匀的场景 |
fixed64 | 64 位整数(固定长度编码) | 适用于较大的定长整数 |
sfixed32 | 32 位有符号整数(固定长度编码) | 适用于负数较多的场景 |
sfixed64 | 64 位有符号整数(固定长度编码) | 适用于较大的负数 |
区别:
int32/int64:默认使用 Varint 编码(数据小的时候占用字节更少)。sint32/sint64:使用 ZigZag 编码,负数编码更高效。fixed32/fixed64:使用固定长度存储,适合数值分布均匀的情况。sfixed32/sfixed64:固定长度的有符号整数。
4.1.2 浮点数类型
| Protobuf 类型 | 说明 | 适用场景 |
|---|---|---|
float | 32 位浮点数 | 适用于存储小数 |
double | 64 位浮点数 | 适用于更高精度的小数 |
注意:
float占 4 个字节,精度有限。double占 8 个字节,适用于更高精度计算。
4.1.3 布尔类型
| Protobuf 类型 | 说明 | 适用场景 |
|---|---|---|
bool | 布尔值 (true/false) | 适用于开关、状态等 |
示例:
message Example {bool is_active = 1; // true or false
}
4.1.4 字符串和字节类型
| Protobuf 类型 | 说明 | 适用场景 |
|---|---|---|
string | UTF-8 编码的字符串 | 存储文本信息 |
bytes | 原始字节数据 | 适用于存储二进制数据(如文件、图片等) |
示例:
message Example {string name = 1;bytes file_data = 2;
}
注意:
string只能存储 文本(UTF-8 编码)。bytes可以存储 任意二进制数据(如图片、视频等)。
4.2 复杂数据类型
4.2.1 数组(Repeated)
使用 repeated 关键字表示 列表/数组:
message Example {repeated string hobbies = 1;repeated int32 scores = 2;
}
repeated string hobbies = 1;→ 表示字符串数组repeated int32 scores = 2;→ 表示整数数组
注意:
- 在 Protobuf 3 中,
repeated类型默认是可选的,不需要额外的optional关键字。
4.2.2 键值对(Map)
Protobuf 3 提供 map<K, V> 类型来存储键值对:
message Example {map<string, int32> scores = 1; // key: string, value: int32
}
map<string, int32>表示 键为字符串,值为整数 的字典。- 生成代码后,会转换成 Go 语言的
map[string]int32。
4.2.3 枚举类型(Enum)
enum Status {UNKNOWN = 0; // 枚举必须从 0 开始ACTIVE = 1;INACTIVE = 2;
}message User {Status status = 1;
}
enum只能用于定义固定的值(类似int)。- 第一个枚举值必须是 0,防止解析错误。
4.2.4 嵌套 Message
message Address {string city = 1;string street = 2;
}message Person {string name = 1;Address address = 2; // 直接嵌套 Address
}
Person结构里包含Address结构,可以用于复杂数据存储。
4.3 Protobuf 类型与不同语言的对应关系
| Protobuf 类型 | Go | Java | Python | C++ |
|---|---|---|---|---|
int32 | int32 | int | int | int32_t |
int64 | int64 | long | int | int64_t |
float | float32 | float | float | float |
double | float64 | double | float | double |
bool | bool | boolean | bool | bool |
string | string | String | str | std::string |
bytes | []byte | byte[] | bytes | std::string |
map<K,V> | map[K]V | Map<K,V> | dict | std::map<K,V> |
repeated | []T | List<T> | list | std::vector<T> |
4.4 Protobuf 3 语法示例
syntax = "proto3";message Person {string name = 1;int32 age = 2;bool is_active = 3;repeated string hobbies = 4;map<string, int32> scores = 5;
}
这个 Person 结构包含:
string name→ 姓名int32 age→ 年龄bool is_active→ 是否激活repeated string hobbies→ 兴趣爱好(数组)map<string, int32> scores→ 课程成绩(键值对)
5. Protobuf 其他字段
5.1 Oneof(互斥字段)
5.1.1 什么是 oneof?
oneof 关键字用于定义一组互斥字段,即同一时间只能有一个字段被设置。它的作用类似于 C 语言的 union,但比 union 更智能,可以判断当前设置的是哪个字段。
5.1.2 为什么要用 oneof?
在 proto3 版本中,所有字段都有默认值,比如:
message Example {int64 id = 1;
}
- 如果
id没有被设置,默认值是0。 - 但如果
id被显式设置为0,你就无法判断这个0是默认值,还是用户真的设置了0。
oneof 解决了这个问题,因为它提供了一个字段状态检查功能,让你可以判断哪个字段被设置了。
5.1.3 oneof 语法
message Response {oneof result {string success_message = 1; // 成功时的消息int32 error_code = 2; // 失败时的错误码}
}
oneof内的字段是互斥的,最多只能设置一个。- 如果
success_message被设置,error_code就不能被设置,反之亦然。 - 如果不设置任何字段,
oneof字段为空。
适用场景
- API 响应(成功返回
success_message,失败返回error_code)。- 状态表示(例如订单可能是“待支付”或“已完成”,但不能同时处于这两个状态)。
5.1.4 oneof 在 Go 语言中的使用
Protobuf 生成的 Go 代码会使用 isXxx() 方法 来判断哪个字段被赋值。
示例:Go 代码
package mainimport ("fmt""google.golang.org/protobuf/proto"
)// 假设 Protobuf 生成的 Go 结构如下:
type Response struct {// 这是 oneof 生成的字段Result isResponse_Result `protobuf_oneof:"result"`
}type isResponse_Result interface {isResponse_Result()
}type Response_SuccessMessage struct {SuccessMessage string
}type Response_ErrorCode struct {ErrorCode int32
}// 实现 isResponse_Result 接口
func (*Response_SuccessMessage) isResponse_Result() {}
func (*Response_ErrorCode) isResponse_Result() {}func main() {// **成功时返回 success_message**resp1 := &Response{Result: &Response_SuccessMessage{SuccessMessage: "Operation successful"}}// **失败时返回 error_code**resp2 := &Response{Result: &Response_ErrorCode{ErrorCode: 404}}// 判断是哪个字段被设置switch v := resp1.Result.(type) {case *Response_SuccessMessage:fmt.Println("Success:", v.SuccessMessage)case *Response_ErrorCode:fmt.Println("Error:", v.ErrorCode)}switch v := resp2.Result.(type) {case *Response_SuccessMessage:fmt.Println("Success:", v.SuccessMessage)case *Response_ErrorCode:fmt.Println("Error:", v.ErrorCode)}
}
输出
Success: Operation successful
Error: 404
oneof生成了isResponse_Result接口,允许我们判断哪个字段被设置。switch v := resp.Result.(type)语法用于检查当前oneof字段的类型。
5.1.5 oneof 的应用场景
- REST API / gRPC 响应(成功返回数据,失败返回错误码)
- 订单状态(
未支付/已支付) - 用户身份验证(
邮箱登录/手机号登录) - 存储不同类型的数据(
文本/图片/视频)
5.2 Reserved(保留字段)
5.2.1 什么是reserved?
在 Protobuf 3 中,reserved 关键字用于保留字段编号或名称,防止将来代码演进时误用已删除的字段。
这可以避免 API 变更时的兼容性问题,确保旧数据不会被错误解析。
5.2.2 为什么需要 reserved?
当你删除或修改字段时,如果不使用 reserved,那么:
- 未来新添加的字段可能意外复用旧的字段编号,导致数据解析出错。
- 旧数据仍然可能使用被删除的字段,导致意外行为。
5.2.3 reserved 语法
你可以用 reserved 关键字保留字段编号或名称,防止后续被重新使用。
保留字段编号
message User {reserved 2, 4 to 6; // 不能再使用编号 2、4、5、6
}
- 以后不能再使用 2、4、5、6 作为字段编号。
- 如果后续尝试用
field = 2;,编译时会报错。
保留字段名称
message User {reserved "old_name", "deprecated_field"; // 不能再使用这些字段名
}
- 以后不能再使用 “old_name” 和 “deprecated_field” 作为字段名。
同时保留编号和名称
message User {reserved 2, 4 to 6;reserved "old_name", "deprecated_field";
}
- 这样可以同时保留编号和字段名称,防止意外复用。
5.2.4 reserved 作用
- 避免旧数据解析错误:如果编号被误用,旧数据可能被错误解析。
- 防止 API 兼容性问题:如果 API 变更,保留字段可以确保旧客户端不会收到无效数据。
- 让代码更可维护:明确告诉后续开发者哪些字段不能使用。
5.3 Any(存储任意数据)
5.3.1 什么是 Any?
Any 是 Protobuf 3 提供的一种特殊类型,允许存储任意类型的 Protobuf 消息,适用于动态数据场景。
它可以在不修改 .proto 结构的情况下,支持不同类型的数据,类似于 JSON 里的 object 或 map<string, any>。
5.3.2 Any 的作用
- 存储动态数据:如果一个字段的类型可能变化(例如可能是
User或Order),可以使用Any而不需要改.proto文件。 - 实现灵活的 API 设计:适用于插件系统、事件系统、日志系统,让不同的子系统传递不同的数据结构。
- 避免频繁修改 Protobuf 定义:当不同的客户端需要传输不同的数据类型时,使用
Any可以减少 API 变更的影响。
5.3.3 Any 的基本用法
(1)导入 Any 类型
Any 需要导入 google/protobuf/any.proto:
import "google/protobuf/any.proto"; // 引入 Any 类型message Response {string message = 1; google.protobuf.Any data = 2; // 存储任意类型
}
message是普通字段,存储文本信息。data是Any类型,可以存储任何 Protobuf 消息。
(2)嵌套不同的消息
假设你有两种不同的消息 User 和 Order:
message User {string name = 1;int32 age = 2;
}message Order {int32 order_id = 1;double price = 2;
}message Response {string message = 1;google.protobuf.Any data = 2; // 可以存储 User 或 Order
}
data字段可以存储User或Order,而不需要修改Response结构。- 这样,
Response可以在不同场景下使用,不受数据类型影响。
5.3.4 Any 在 Go 语言中的使用
(1)安装 Protobuf 依赖
在 Go 代码中,需要 proto 和 anypb(处理 Any 类型):
go get google.golang.org/protobuf/proto
go get google.golang.org/protobuf/types/known/anypb
(2)Go 代码示例
package mainimport ("fmt""google.golang.org/protobuf/proto""google.golang.org/protobuf/types/known/anypb"
)// 定义 User 和 Order 结构
type User struct {Name stringAge int32
}type Order struct {OrderId int32Price float64
}// Response 结构,包含 Any 字段
type Response struct {Message stringData *anypb.Any
}func main() {// 创建 User 结构user := &User{Name: "Alice", Age: 25}// 将 User 结构封装到 Any 里anyData, _ := anypb.New(user)// 创建 Response 并存储 User 数据resp := &Response{Message: "User data",Data: anyData,}// **序列化**data, _ := proto.Marshal(resp)// **反序列化**newResp := &Response{}proto.Unmarshal(data, newResp)// **解析 Any 字段**newUser := &User{}newResp.Data.UnmarshalTo(newUser)fmt.Println("Message:", newResp.Message)fmt.Println("User Name:", newUser.Name, "Age:", newUser.Age)
}
(3)Go 代码解释
- 封装数据:
- 使用
anypb.New(user)把User结构转换成Any类型。
- 使用
- 序列化
Response:- 使用
proto.Marshal(resp)进行序列化,便于存储或传输。
- 使用
- 反序列化
Response:- 使用
proto.Unmarshal(data, newResp)解析Response结构。
- 使用
- 解析
Any数据:newResp.Data.UnmarshalTo(newUser)解析Any字段,恢复User结构。
6. Protobuf 编码原理
Protobuf 使用高效的二进制格式来存储和传输数据,其中最关键的编码方式之一是 Varint(变长整数编码)。它的核心思想是:
- 数值越小,占用字节越少
- 数值越大,占用字节越多
- 高效存储,减少带宽消耗
6.1 什么是 Varint?
Varint(变长整数编码) 是一种特殊的编码方式,它可以使用 1 到 N 个字节 表示整数。
- 小数占用更少字节(如
1只需要 1 个字节)。 - 大数会自动扩展到多个字节(如
300需要 2 个字节)。
6.2 Varint 编码规则
- 每个字节的最高位(MSB,Most Significant Bit)是“是否还有后续字节的标志”
- 最高位为
0:表示这是最后一个字节。 - 最高位为
1:表示后面还有字节。
- 最高位为
- 剩下的 7 位存储数据(低位优先,LSB)。
6.3 具体示例
(1)数字 1 的 Varint 编码
0000 0001 (只有 1 个字节)
- 最高位
0:表示这是最后一个字节。 - 其余 7 位
000 0001(= 1)。
存储方式:
[0000 0001] → 1 字节
(2)数字 300 的 Varint 编码
先看二进制表示:
300 = 100101100(9 位)
需要拆成 7 位 + 剩余部分:
低 7 位: 0101100 → 0x2C(44)
高 2 位: 0000010 → 0x02(2)
- 第一字节:
1010 1100(0xAC)- 最高位
1(表示后面还有字节)。 - 剩余
7位存010 1100(= 44)。
- 最高位
- 第二字节:
0000 0010(0x02)- 最高位
0(表示这是最后一个字节)。 - 剩余
7位存000 0010(= 2)。
- 最高位
最终编码
[1010 1100] [0000 0010] → 2 字节(0xAC 0x02)
6.4 Wire Type(数据类型编码)
Protobuf 数据存储为 键值对(key-value) 形式,每个字段的 key 也需要编码。
字段的 key 由字段编号 + Wire Type 组成。
| Wire Type | 值 | 作用 |
|---|---|---|
Varint | 0 | 变长整数(int32, int64, bool, enum) |
Fixed64 | 1 | 64 位定长(double, fixed64, sfixed64) |
Length-delimited | 2 | 变长数据(string, bytes, message, repeated) |
Start group | 3 | 已废弃(用于嵌套数据) |
End group | 4 | 已废弃 |
Fixed32 | 5 | 32 位定长(float, fixed32, sfixed32) |
存储格式
[字段编号 << 3] | [Wire Type] [数据]
字段编号左移 3 位,低 3 位存 Wire Type。
6.5 例子:Protobuf 编码解析
假设 Person 结构如下:
message Person {int32 id = 1; // 1 字段编号string name = 2; // 2 字段编号
}
数据:
{"id": 150,"name": "Alice"
}
编码过程
-
字段
id = 150- 字段编号 =
1 - Wire Type =
0(Varint) key = (1 << 3) | 0 = 0000 1000 (0x08)150的 Varint 编码:1001 0110 0000 0001(0x96 0x01)
最终存储:
[0x08] [0x96 0x01] (字段编号 1,Varint) - 字段编号 =
-
字段
name = "Alice"- 字段编号 =
2 - Wire Type =
2(Length-delimited,字符串) key = (2 << 3) | 2 = 0001 0001 (0x12)"Alice"= 5 个字节(0x41 0x6C 0x69 0x63 0x65)
最终存储:
[0x12] [0x05] [0x41 0x6C 0x69 0x63 0x65] - 字段编号 =
6.6 解析 Protobuf 二进制数据
假设收到如下二进制数据:
08 96 01 12 05 41 6C 69 63 65
逐字节解析:
08=0000 1000(字段编号1,Wire Type0,Varint)96 01= 150(Varint 解码)12=0001 0010(字段编号2,Wire Type2,字符串)05= 长度541 6C 69 63 65="Alice"
最终解析为:
{"id": 150,"name": "Alice"
}
相关文章:
数据序列化协议 Protobuf 3 介绍(Go 语言)
Protobuf 3 入门 1. 什么是序列化? 1.1 概念 序列化(Serialization 或 Marshalling) 是指将数据结构或对象的状态转换成可存储或传输的格式。反向操作称为反序列化(Deserialization 或 Unmarshalling),它…...
从芯片到光网络:解密平面光波导技术(PLC)核心优势
关键词:PLC、OFDR、光链路检测 平面光波导技术(Planar Lightwave Circuit, PLC)是一种基于平面波导结构的光学器件制造技术。它通过在平面基底上制作光波导,实现光信号的传输、分路、耦合、调制等功能。PLC技术的核心在于利用光波…...
5分钟快速搭建一个 SpringBoot3 + MyBatis-Plus 工程项目
环境 idea 2023.3.5 jdk 17 mysql 8 创建SpringBoot工程 创建SpringBoot工程,这里有两种方式可选,一种是使用idea提供的Spring Initializr自动创建,一种是通过Maven Archetype手动创建 自动创建SpringBoot工程 使用Spring Initializr创建…...
如何判断https使用了哪个版本的TLS?
互联网各领域资料分享专区(不定期更新): Sheet 正文 一、使用浏览器开发者工具(适合普通用户) 1. Google Chrome 打开目标网站(如 https://example.com)。点击地址栏左侧的 锁形图标。选择 「连接是安全的」 → 「证书信息」。在证书详情中,查看 「技术详细信息」 或 「…...
如何在 NocoBase 中实现 CRM 的线索转化
1. 引言 本教程将一步一步地引导您如何在 NocoBase 中实现 CRM 的商机转化(Opportunity Conversion)功能。我们将介绍如何创建所需的 collections(数据表)、配置数据管理页面、设计转化流程以及设置关联管理,从而帮助…...
StarRocks-fe工程在Cursor中不能识别为Java项目
SR简介 StarRocks 是一款高性能分析型数据库,支持实时、多维度、高并发的数据分析。本指南旨在解决在使用 VSCode 或 Cursor 开发 StarRocks 后端项目时遇到的模块识别问题。 问题描述 使用 Cursor 或 VSCode 打开 StarRocks 的后端工程 fe 时,spark-…...
影刀RPA开发拓展--SQL常用语句全攻略
前言 SQL(结构化查询语言)是数据库管理和操作的核心工具,无论是初学者还是经验丰富的数据库管理员,掌握常用的 SQL 语句对于高效管理和查询数据都至关重要。本文将系统性地介绍最常用的 SQL 语句,并为每个语句提供详细…...
05类加载机制篇(D6_方法调用和方法执行)
目录 一、字节码指令集 二、基本数据类型 1. 加载和存储指令 2. const系列 3. push系列 4. ldc系列 5. load系列 load系列A load系列B 6. store系列 store系列A store系列B 7. pop系列 8. 栈顶元素数学操作及移位操作系列 9. 运算指令 10. 类型转换指令 11. 宽…...
视音频数据处理入门:颜色空间(二)---ffmpeg
目录 概述 流程 相关流程 初始化方法 初始化代码 转换方法 转换代码 释放方法 整体代码介绍 代码路径 概述 本篇简单说一下基于FFmpeg的libswscale的颜色空间转换;Libswscale里面实现了各种图像像素格式的转换,例如:YUV与RGB之间的…...
从零开始:H20服务器上DeepSeek R1 671B大模型部署与压力测试全攻略
前言 最近,我有幸在工作中接触到了DeepSeek R1 671B模型,这是目前中文开源领域参数量最大的高质量模型之一。DeepSeek团队在2024年推出的这款模型,以其惊人的6710亿参数量和出色的推理性能,引起了业界广泛关注。 作为一名AI基础…...
【FAQ】HarmonyOS SDK 闭源开放能力 —Map Kit(5)
1.问题描述: 提供两套标准方案,可根据体验需求选择: 1.地图Picker(地点详情) 用户体验:①展示地图 ②标记地点 ③用户选择已安装地图应用 接入文档:https://developer.huawei.com/consumer/cn/doc/harmonyos-guide…...
Leetcode 3469. Find Minimum Cost to Remove Array Elements
Leetcode 3469. Find Minimum Cost to Remove Array Elements 1. 解题思路2. 代码实现 题目链接:3469. Find Minimum Cost to Remove Array Elements 1. 解题思路 这一题我没啥特别好的思路,就只能动态规划了,倒是也能过,不过总…...
Excel的行高、列宽单位不统一?还是LaTeX靠谱
想要生成田字格、米字格、带拼音标准,方便小学生书法和练字。Word,Excel之类所见即所得是最容易相当的方式。但它们处理带田字格之类背景时,如果没有专用模板、奇奇怪怪的插件,使用起来会碰到各种问题。比如,Word里面用…...
(新版本onenet)stm32+esp8266/01s mqtt连接onenet上报温湿度和远程控制(含小程序)
物联网实践教程:微信小程序结合OneNET平台MQTT实现STM32单片机远程智能控制 远程上报和接收数据——汇总 前言 之前在学校获得了一个新玩意:ESP-01sWIFI模块,去搜了一下这个小东西很有玩点,远程控制LED啥的,然后我就想…...
告别GitHub连不上!一分钟快速访问方案
一、当GitHub抽风时,你是否也这样崩溃过? 😡 npm install卡在node-sass半小时不动😭 git clone到90%突然fatal: early EOF🤬 改了半天hosts文件,第二天又失效了... 根本原因:传统代理需要复杂…...
迷你世界脚本对象库接口:ObjectLib
对象库接口:ObjectLib 迷你世界 更新时间: 2023-04-26 20:21:09 具体函数名及描述如下: 序号 函数名 函数描述 1 getAreaData(...) 获取区域数据 2 getPositionData(...) 获取位置数据 3 getLivingData(...) 获取生物数据 4 getItemDat…...
数据库事务、乐观锁及悲观锁
参考:node支付宝支付及同步、异步通知、主动查询支付宝订单状态 以下容结合上述链接查看 1. 什么是数据库事务? 1.1. 连续执行数据库操作 在支付成功后,我们在自定义的paidSuccess里,依次更新了订单状态和用户信息。也就说这里…...
蓝桥王国--dij模板
#include <bits/stdc.h> // 万能头 using namespace std; typedef pair<long long ,int> PII; int n,m; long long d[300011]; struct edge///邻接表 {int v;long long w; }; int vis[300011]; vector<edge> mp[300011];///邻接表 void dij(int s)///dij单源…...
Java基础关键_017_集合(一)
目 录 一、概述 二、Collection 关系结构 1.概览 2.说明 三、Collection 接口 1.通用方法 (1)add(E e) (2)size() (3)addAll(Collection c) (4)contains(Object o) &#…...
Rust编程实战:Rust实现简单的Web服务,单线程性能问题
知识点 tcp 服务多线程处理 实现功能 启动web服务,访问链接获取页面内容。 单线程web服务 TcpListener 使用 TcpListener 开启服务端口 let listener TcpListener::bind("127.0.0.1:7878").unwrap();处理客户端连接: for stream in lis…...
数据库分批入库
今天在工作中,遇到一个问题,就是分批查询的时候,由于批次过大导致出现了一些问题,一下是问题描述和解决方案: 示例: // 假设已有数据列表 dataList 和 PreparedStatement pstmt int batchSize 1000; // …...
多种风格导航菜单 HTML 实现(附源码)
下面我将为您展示 6 种不同风格的导航菜单实现,每种都包含完整 HTML、CSS 和 JavaScript 代码。 1. 简约水平导航栏 <!DOCTYPE html> <html lang"zh-CN"> <head><meta charset"UTF-8"><meta name"viewport&qu…...
SAP学习笔记 - 开发26 - 前端Fiori开发 OData V2 和 V4 的差异 (Deepseek整理)
上一章用到了V2 的概念,其实 Fiori当中还有 V4,咱们这一章来总结一下 V2 和 V4。 SAP学习笔记 - 开发25 - 前端Fiori开发 Remote OData Service(使用远端Odata服务),代理中间件(ui5-middleware-simpleproxy)-CSDN博客…...
Linux离线(zip方式)安装docker
目录 基础信息操作系统信息docker信息 安装实例安装步骤示例 遇到的问题问题1:修改默认工作路径启动失败问题2 找不到对应组 基础信息 操作系统信息 OS版本:CentOS 7 64位 内核版本:3.10.0 相关命令: uname -rcat /etc/os-rele…...
使用LangGraph和LangSmith构建多智能体人工智能系统
现在,通过组合几个较小的子智能体来创建一个强大的人工智能智能体正成为一种趋势。但这也带来了一些挑战,比如减少幻觉、管理对话流程、在测试期间留意智能体的工作方式、允许人工介入以及评估其性能。你需要进行大量的反复试验。 在这篇博客〔原作者&a…...
RSS 2025|从说明书学习复杂机器人操作任务:NUS邵林团队提出全新机器人装配技能学习框架Manual2Skill
视觉语言模型(Vision-Language Models, VLMs),为真实环境中的机器人操作任务提供了极具潜力的解决方案。 尽管 VLMs 取得了显著进展,机器人仍难以胜任复杂的长时程任务(如家具装配),主要受限于人…...
ubuntu系统文件误删(/lib/x86_64-linux-gnu/libc.so.6)修复方案 [成功解决]
报错信息:libc.so.6: cannot open shared object file: No such file or directory: #ls, ln, sudo...命令都不能用 error while loading shared libraries: libc.so.6: cannot open shared object file: No such file or directory重启后报错信息&…...
Spring Boot + MyBatis 集成支付宝支付流程
Spring Boot MyBatis 集成支付宝支付流程 核心流程 商户系统生成订单调用支付宝创建预支付订单用户跳转支付宝完成支付支付宝异步通知支付结果商户处理支付结果更新订单状态支付宝同步跳转回商户页面 代码实现示例(电脑网站支付) 1. 添加依赖 <!…...
Java数组Arrays操作全攻略
Arrays类的概述 Java中的Arrays类位于java.util包中,提供了一系列静态方法用于操作数组(如排序、搜索、填充、比较等)。这些方法适用于基本类型数组和对象数组。 常用成员方法及代码示例 排序(sort) 对数组进行升序…...
门静脉高压——表现
一、门静脉高压表现 00:01 1. 门静脉构成 00:13 组成结构:由肠系膜上静脉和脾静脉汇合构成,是肝脏血液供应的主要来源。淤血后果:门静脉淤血会同时导致脾静脉和肠系膜上静脉淤血,引发后续系列症状。 2. 脾大和脾功能亢进 00:46 …...
