数据序列化协议 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),它…...
FineReport 操作注意
1.父单元格重复的时候,如何取消合并 效果如下: 只需要在单元格中,将数据设置为【列表】即可。 2.待定...
3D手眼标定转换详细实施步骤及原理概述
3D手眼标定转换详细实施步骤及原理概述 一、手眼标定的核心目标二、3D手眼标定的原理概述一、基本概念与坐标系定义**二、数学建模与方程推导****1. 坐标变换的齐次矩阵表示****2. 手眼标定方程推导** **三、方程求解方法****1. 分离旋转与平移****2. 旋转矩阵求解****3. 平移向…...
Verilog:SCCB控制器
目录 一、SCCB协议 (1)SCCB时序 (2)与I2C的区别 二、Verilog 实现 (1)设计要求 (2)设计要点 (3)模块完整代码 三、功能验证 (1)写…...
维度建模基础篇:从理论到核心组件解析
维度建模基础篇:从理论到核心组件解析 引言 在数据仓库与商业智能(BI)领域,维度建模(Dimensional Modeling)作为一种经典的数据组织方法论,自Kimball提出以来,已成为构建高效分析型系统的核心范式1,2,3。其以业务需求为导向,通过事实表与维度表的组合,实现对复杂…...
与中国联通技术共建:通过obdiag分析OceanBase DDL中的报错场景
中国联通软件研究院(简称联通软研院)在全面评估与广泛调研后,在 2021年底决定采用OceanBase 作为基础,自研分布式数据库产品CUDB(即China Unicom Database,中国联通数据库)。目前,该…...
大数据与网络安全讲座
🍅 点击文末小卡片 ,免费获取网络安全全套资料,资料在手,涨薪更快 大数据的价值为大家公认。业界通常以4个“V”来概括大数据的基本特征——Volume(数据体量巨大)、Variety(数据类型繁多)、Value(价值密度低)、Velocity(处理速度快…...
AtCoder Beginner Contest 395 E
点我写题 题意:给个有向图,从1出发,每次可以走一条有向边,花费为1,也可以选择把全部有向边翻转,花费x,问到n的最小花费 思路:最短路dp,定义dis[i][0/1]表示走到i为止&…...
Linux进程管理6 - CFS调度
0、CFS调度器 CFS调度器使用完全公平调度算法。 完全公平调度算法引入虚拟运行时间的概念:虚拟运行时间 = 实际运行时间 * nice_0_weight / 进程的权重。完全公平调度算法使用红黑树把进程按虚拟运行时间从小到大排序,每次调度选择虚拟运行时间最小的进程。时间片 操作系统进…...
张驰咨询:用六西格玛重构动力电池行业的BOM成本逻辑
在动力电池行业,BOM(物料清单)成本每降低1%,都可能改写企业的利润曲线。某头部企业的三元锂电池BOM成本曾较行业标杆高出11%,单电芯利润率被压缩至3%的生死线。然而,通过张驰咨询的六西格玛方法论ÿ…...
pyside6学习专栏(九):在PySide6中使用PySide6.QtCharts绘制6种不同的图表的示例代码
PySide6的QtCharts类支持绘制各种型状的图表,如面积区域图、饼状图、折线图、直方图、线条曲线图、离散点图等,下面的代码是采用示例数据绘制这6种图表的示例代码,并可实现动画显示效果,实际使用时参照代码中示例数据的格式将实际数据替换即可…...
SpringBoot获取YAML配置文件中的属性值(二):使用Environment环境组件读取值
Spring Boot 使用 Properties 和 YAML 配置文件文件,系列文章: 《Spring使用@Value注解与@PropertySource注解加载配置文件》 《SpringBoot获取YAML配置文件中的属性值(一):使用@Value注解、@ConfigurationProperties注解》 《SpringBoot获取YAML配置文件中的属性值(二)…...
14天 -- Redis 的持久化机制有哪些?Redis 主从复制的实现原理是什么? Redis 数据过期后的删除策略是什么?
Redis 的持久化机制有哪些? Redis 是一种高性能的键值存储系统,主要用于缓存、消息队列等场景。为了防止数据丢失,Redis 提供了多种持久化机制,主要包括以下两种: 1. RDB(Redis Database Backupÿ…...
《深度学习实战》第10集:联邦学习与隐私保护
第10集:联邦学习与隐私保护 2025年3月4日更新了代码,补充了实例程序运行截图 和 如何提高模型准确率的方法 系统梳理 集集精彩 代码验证 保证实战 随着数据隐私问题日益受到关注,联邦学习(Federated Learning) 作为一…...
如何解决跨域请求的问题(CORS)?
文章目录 1. 引言2. 理解 CORS2.1 CORS 基本概念2.2 同源策略与跨域分类 3. CORS 的核心机制3.1 预检请求(Preflight Request)3.2 简单请求 4. 服务器端配置 CORS4.1 关键响应头4.2 Node.js (Express) 示例4.3 其他后端语言配置 5. 前端处理 CORS 请求5.…...
【数据结构】二叉树总结篇
遍历 递归 递归三部曲: 1.参数和返回值 2.终止条件 3.单层逻辑(遍历顺序) var preorderTraversal function(root) { // 第一种let res[];const dfsfunction(root){if(rootnull)return ;//先序遍历所以从父节点开始res.push(root.val);//递归…...
软考-数据库开发工程师-3.1-数据结构-线性结构
第3章内容比较多,内容考试分数占比较大,6分左右 线性表 1、线性表的定义 一个线性表是n个元素的有限序列(n≥0),通常表示为(a1,a2, a3,…an). 2、线性表的顺序存储(顺序表) 是指用一组地址连续的存储单元依次存储线性表中的数据元…...
【五.LangChain技术与应用】【2.LangChain虚拟环境搭建(下):环境优化与调试】
一、Docker化部署:别让你的环境成为薛定谔的猫 经历过"在我机器上能跑"惨案的老铁都懂,传统虚拟环境就像个黑盒子。去年我帮客户部署LangChain应用,因为glibc版本差了0.1,整个服务直接崩成烟花。从那天起,我所有项目都强制上Docker! Dockerfile生存指南: #…...
deepseek+mermaid【自动生成流程图】
成果: 第一步打开deepseek官网(或百度版(更快一点)): 百度AI搜索 - 办公学习一站解决 第二步,生成对应的Mermaid流程图: 丢给deepseek代码,或题目要求 生成mermaid代码 第三步将代码复制到me…...
Java实现大数据量导出报表
一、实现方式 在Java中,导出数据到Excel有多种方式,每种方式都有其优缺点,适用于不同的场景。以下是常见的几种方式及其特点: 1.1 Apache POI Apache POI 是 Java 中最流行的库,支持读写 Excel 文件(包括…...
在 Element Plus 的 <el-select> 组件中,如果需要将 <el-option> 的默认值设置为 null。 用于枚举传值
文章目录 引言轻松实现 `<el-option>` 的默认值为 `null`I 实现方式监听清空事件 【推荐】使用 v-model 绑定 null添加一个值为 null 的选项处理 null 值的显示引言 背景:接口签名规则要求空串参与,空对象不参与签名计算 // 空字符串“” 参与签名组串,null不参与签…...
Spring Boot 接口 JSON 序列化优化:忽略 Null 值的九种解决方案详解
一、针对特定接口null的处理: 方法一:使用 JsonInclude 注解 1.1 类级别:在接口返回的 DTO 类或字段 上添加 JsonInclude 注解,强制忽略 null 值: 类级别:所有字段为 null 时不返回 JsonInclude(Js…...
解码未来!安徽艾德未来智能科技有限公司荣获“GAS消费电子科创奖-产品创新奖”!
在2025年“GAS消费电子科创奖”评选中,安徽艾德未来智能科技有限公司提交的“讯飞AI会议耳机iFLYBUDS Pro 2”,在技术创新性、设计创新性、工艺创新性、智能化创新性及原创性五大维度均获得评委的高度认可,荣获“产品创新奖”。 这一殊荣不仅…...
Velox 之 Expression
Round 函数 velox/functions/prestosql/Arithmetic.h template <typename T> struct RoundFunction {template <typename TInput>FOLLY_ALWAYS_INLINE voidcall(TInput& result, const TInput& a, const int32_t b = 0) {result = round(a, b);} };/// R…...
【零基础到精通Java合集】第二十四集:ZGC收集器详解
课程标题:ZGC收集器——突破停顿时间极限的下一代垃圾回收器(15分钟) 目标:掌握ZGC的核心技术原理、适用场景与调优策略,理解其如何实现亚毫秒级停顿 0-1分钟:课程引入与ZGC设计目标 以“高速公路无障碍通行”类比ZGC核心思想:通过染色指针与读屏障技术,实现垃圾回收…...
力扣hot100刷题——栈
文章目录 69.有效的括号题目描述思路:栈code 70.最小栈题目描述思路:双栈法code优化:单栈法code 71.字符串解码题目描述思路:栈code 73.每日温度题目描述思路:单调栈code 74.柱状图中最大的矩形题目描述思路࿱…...
TMS320F28P550SJ9学习笔记2:Sysconfig 配置与点亮LED
今日学习使用Sysconfig 对引脚进行配置,并点亮开发板上的LED4 与LED5 我的单片机开发板平台是 LAUNCHXL_F28P55x 我是在上文描述的驱动库C2000ware官方例程example的工程基础之上进行添加功能的 该例程路径如下:D:\C2000Ware_5_04_00_00\driverlib\f28p…...
STM32MP1xx的启动流程
https://wiki.st.com/stm32mpu/wiki/Boot_chain_overview 根据提供的知识库内容,以下是STM32 MPU启动链的详细解析: 1. 通用启动流程 STM32 MPU启动分为多阶段,逐步初始化外设和内存,并建立信任链: 1.1 ROM代码&…...
开源之夏经验分享|Koupleless 社区黄兴抗:在开源中培养工程思维
开源之夏经验分享|Koupleless 社区黄兴抗:在开源中培养工程思维 文|黄兴抗 电子信息工程专业 Koupleless 社区贡献者 就读于南昌师范学院,电子信息工程专业的大三学生。 本文 2634 字,预计阅读 7 分钟 今天 SOFAStack 邀…...
健康养生:开启活力人生的钥匙
在快节奏的现代生活中,健康养生已成为我们追求美好生活的关键。它不仅关乎身体的强健,更与心灵的宁静息息相关。 合理饮食是健康养生的基石。多吃蔬菜、水果,它们富含维生素与矿物质,为身体提供充足养分。全谷物食品也是不错的选…...
