一文通关 Proto3完整语法与工程实践

一、导读与范围

1)本文说明如何使用 Protocol Buffers(Protobuf)proto3 版本来组织数据(.proto 文件语法)并从 .proto 生成各语言的数据访问类。

2)若需 editions 语法,请参见 Protobuf Editions Language Guide ;若需 proto2 ,请参见 Proto2 Language Guide

3)本文是参考指南;若需"从零上手"的分步示例,请参阅你所选语言的官方教程。

二、定义消息类型(Message)

(1)最小示例(搜索请求):

proto 复制代码
syntax = "proto3";

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

(2)syntax/edition 必须是文件中的第一行非空、非注释 语句;若二者都未指定,编译器默认 proto2

(3)一个消息由若干字段(name/value)组成,每个字段有名称类型

三、字段类型(Type)

(1)可用标量类型 (如 int32string)与复合类型 (如其他 messageenum)。

(2)后文"标量值类型"给出跨语言类型映射与编码要点。

四、字段编号(Field Numbers)

(1)范围:1 ~ 536,870,91119,000 ~ 19,999 为实现保留,不可使用

(2)在同一消息内唯一 ;不可使用已保留或扩展(extensions)占用的编号。

(3)一经发布不可更改 :编号决定线格式中的字段标识;"改编号"≈"删字段+新建编号"。

(4)永不复用 :删除字段后,应将其编号与名称 标记为 reserved(见第十节)。

(5)空间优化1~15 的编号更省字节(标签编码更短);16~2047 次之。详见《Protocol Buffer Encoding》。

(6)额外说明:字段编号限制为 29 位 (另外 3 位用于 wire type)。

4.1、复用编号的后果(务必避免)

  • 解码歧义、调试时间浪费、解析/合并错误、PII/SPII 泄露数据损坏
  • 常见诱因:
    1)"重新排号"追求美观;
    2)删除字段但未保留其编号/名称,导致被他人复用。

五、字段基数(Cardinality)与存在性(Presence)

(1)Singular(单值)

  • optional(推荐)

    • 两种状态:已设置 (会序列化)或未设置(读默认值,不序列化);
    • 可检测"是否显式设置";与 proto2/editions 兼容性更好。
  • implicit(不推荐)

    • 若是消息类型 ,行为与 optional 一致;

    • 若是非消息标量

      • 非默认值:会序列化;
      • 默认值(零值) :不序列化,且无法区分"显式设为默认值"与"未提供"。

(2)repeated(可重复) :0 次或多次,保持顺序

(3)map(键值对):见第二十节。

5.1、数值型 repeated 默认打包(packed)

  • proto3 中,数值标量repeated 字段默认 使用 packed 编码(更紧凑)。

5.2、消息字段总是有存在性

  • 消息类型 字段天然具备 presence;给消息字段加 optional 不会改变 存在性或编码。下面两个定义在所有语言的生成代码与二进制/JSON/TextFormat 表现相同
proto 复制代码
syntax="proto3";
package foo.bar;

message Message1 {}

message Message2 {
  Message1 foo = 1;
}

message Message3 {
  optional Message1 bar = 1;
}

六、良构消息与"最后一次获胜"

(1)"良构(well-formed)"指序列化/反序列化的字节 合法;protoc 仅保证 .proto 可被解析。

(2)单值字段 在字节流中可出现多次------解析器接受,但只保留最后一次 出现的值(Last One Wins)。

七、同文件多消息与依赖膨胀

(1)可以在同一 .proto 中定义多个相关消息(如 SearchRequestSearchResponse)。

(2)但过多 类型(message/enum/service)集中在同一文件会引起依赖膨胀 ;建议尽量精简 每个 .proto 的类型数量。

八、注释规范

(1)优先使用 // 放在代码元素前一行;(2)支持 /* ... */ 多行注释;(3)多行推荐 /** ... */ 风格:

proto 复制代码
/**
 * SearchRequest 表示一个搜索查询,并带分页选项。
 */
message SearchRequest {
  string query = 1;           // 查询词
  int32 page_number = 2;      // 页码
  int32 results_per_page = 3; // 每页条数
}

九、删除字段(Deleting Fields)

(1)删除前提:客户端代码不再引用 该字段。

(2)删除后务必:

  • 保留编号reserved <numbers>),防止未来复用;
  • 建议保留名称reserved "<names>"),便于 JSON/TextFormat 解析旧内容。
    (3)也可选择保留但重命名(如加 OBSOLETE_ 前缀)。

十、Reserved:保留编号与名称

(1)保留编号:

proto 复制代码
message Foo {
  reserved 2, 15, 9 to 11; // 含端点
}

(2)保留名称:

proto 复制代码
message Foo {
  reserved 2, 15, 9 to 11;
  reserved "foo", "bar";
}

(3)注意:编号名称 不可在同一个 reserved 语句中混用。

(4)TextProto 的特殊性:部分实现(C++/Go)在解析时可能静默丢弃"保留名称的未知字段";JSON 运行时解析不受影响。

十一、代码生成

(1)C++ :每个 .proto 生成 .h.cc,每个消息一个类。

(2)Java :每个 .proto 生成 .java,每个消息一个类,并有 Builder

(3)Kotlin :在 Java 生成基础上,每个消息生成一个 .kt,提供更友好的 Kotlin API(DSL、可空访问器、copy)。

(4)Python :生成模块,包含静态描述符;运行时通过元类创建访问类。

(5)Go :每个 .proto 生成一个 .pb.go,每个消息对应一个类型。

(6)Ruby :生成 .rb,包含你的消息类型。

(7)Objective-C :每个 .proto 生成 pbobjc.h / pbobjc.m

(8)C# :每个 .proto 生成 .cs,每个消息一个类。

(9)PHP :每个消息生成 .php 文件,另为每个 .proto 生成 .php 元数据文件(用于将有效类型加载进描述符池)。

(10)Dart :每个 .proto 生成 .pb.dart

十二、标量值类型与跨语言映射(含编码提示)

编码提示

  • int32/int64 为变长编码,对负数低效 → 负数多用 sint32/sint64(zigzag)。
  • fixed32/fixed64 恒定 4/8 字节,大数更高效。
  • string 为 UTF-8/7-bit ASCII;bytes 任意字节。
  • 数值型 repeated 在 proto3 默认 packed

类型映射(摘要表)

Proto Type C++ Java/Kotlin1 Python3 Go Ruby C# PHP Dart Rust
double double double float float64 Float double float double f64
float float float float float32 Float float float double f32
int32 int32_t int int int32 Fixnum/Bignum int integer int i32
int64 int64_t long int/long4 int64 Bignum long integer/string6 Int64 i64
uint32 uint32_t int2 int/long4 uint32 Fixnum/Bignum uint integer int u32
uint64 uint64_t long2 int/long4 uint64 Bignum ulong integer/string6 Int64 u64
sint32 int32_t int int int32 Fixnum/Bignum int integer int i32
sint64 int64_t long int/long4 int64 Bignum long integer/string6 Int64 i64
fixed32 uint32_t int2 int/long4 uint32 Fixnum/Bignum uint integer int u32
fixed64 uint64_t long2 int/long4 uint64 Bignum ulong integer/string6 Int64 u64
sfixed32 int32_t int int int32 Fixnum/Bignum int integer int i32
sfixed64 int64_t long int/long4 int64 Bignum long integer/string6 Int64 i64
bool bool boolean bool bool True/False bool boolean bool bool
string std::string String str/unicode5 string String(UTF-8) string string String ProtoString
bytes std::string ByteString bytes \[\]byte ASCII-8BIT ByteString string/List ProtoBytes ProtoBytes

脚注:

1 Kotlin 复用 Java 对应类型(含无符号),保证混合代码兼容。

2 Java 的无符号整型以有符号类型承载,最高位占用符号位。

3 设值会做类型检查。

4 解码时 64 位或无符号 32 位总以 long 表示;若设值给 int 且能容纳,也可为 int

5 Python 解码为 unicode;若给定 ASCII 字符串,也可能是 str(细节可能变动)。

6 64 位机为 integer,32 位机为 string

十三、默认值(Default Field Values)

(1)若字节中不存在某字段:

  • string""bytes → 空;boolfalse;数值 → 0
  • 消息 字段:未设置(具体取值与语言相关);
  • 枚举 :默认是第一个枚举值(必须为 0,见第十四节"枚举默认 0 值");
  • repeated / map:空集合。
    (2)隐式存在性标量 解析后,无法判断 默认值是显式设置还是未提供;设计布尔开关时请用 optional bool 并定义合理默认。
    (3)设为默认值的标量不序列化+0 不写出,-0+0 不同,会写出。

十四、枚举(Enum)

(1)第一个值必须为 0 ,且推荐命名为 *_UNSPECIFIED/UNKNOWN(仅表示"未指定"):

proto 复制代码
enum Corpus {
  CORPUS_UNSPECIFIED = 0;
  CORPUS_UNIVERSAL = 1;
  CORPUS_WEB = 2;
  CORPUS_IMAGES = 3;
  CORPUS_LOCAL = 4;
  CORPUS_NEWS = 5;
  CORPUS_PRODUCTS = 6;
  CORPUS_VIDEO = 7;
}

(2)默认值 即第一个值(示例中为 CORPUS_UNSPECIFIED)。

(3)别名(同一数值多个名称)需显式开启:

proto 复制代码
enum EnumAllowingAlias {
  option allow_alias = true;
  EAA_UNSPECIFIED = 0;
  EAA_STARTED = 1;
  EAA_RUNNING = 1; // 别名
  EAA_FINISHED = 2;
}

(4)枚举值应在 32 位整数范围内;不推荐负数 (varint 对负数低效)。

(5)删除枚举值后保留编号与名称 ,可用 max 指到上界:

proto 复制代码
enum Foo {
  reserved 2, 15, 9 to 11, 40 to max;
  reserved "FOO", "BAR";
}

(6)反序列化未知枚举值 会被保留 ;在开放枚举(C++/Go)用底层整数保存,在封闭枚举(Java)用"未识别"分支暴露,并可取到底层整数。

(7)重要:不同语言的"理想行为"与"现状"可能有差异(详见官方 Enum Behavior 说明)

十五、复用其他消息类型 / 跨文件引用

(1)在消息中引用其他消息:

proto 复制代码
message SearchResponse {
  repeated Result results = 1;
}
message Result {
  string url = 1;
  string title = 2;
  repeated string snippets = 3;
}

十六、导入与 import public

(1)导入其他 .proto

proto 复制代码
import "myproject/other_protos.proto";

编译器在 -I/--proto_path 指定的目录中解析导入路径;建议将其指向包含所有 proto 的最高级目录

(2)迁移路径 :当移动 .proto 的位置时,可在旧位置放一个"占位文件",用 import public 转发到新位置,平滑迁移:

proto 复制代码
// old.proto(客户端继续导入它)
import public "new.proto";
import "other.proto";
// client 导入 old.proto 后可用 old/new 的定义,但不能透传 other.proto

(3)注意:Java 中 import public 在迁移整文件java_multiple_files = true 时更稳;Kotlin/TS/JS/GCL 与使用静态反射的 C++ 不支持该功能。

十七、与 proto2 混用

(1)proto3 可以导入并使用 proto2 的消息类型 (反之也可)。

(2)但proto2 的枚举 不可直接在 proto3 语法中作为字段类型(若只在导入的 proto2 消息内部使用则可)。

十八、嵌套类型(Nested Types)

(1)可在消息内部定义消息,并以 Parent.Type 在外部引用;不同父级下同名子类型互不影响

(2)嵌套深度不限制。

十九、模式演进(更新消息类型)

(1)若需扩展消息格式,同时保持旧代码可用,遵循二进制线格式 的演进规则。

(2)Wire-unsafe(不安全)(除非保证所有读写端同时升级):

  • 修改已有字段编号(等价"删+新");
  • 将字段移入已存在oneof
    (3)Wire-safe(安全)
  • 新增字段
  • 删除字段 (并 reserved 编号/名称);
  • 枚举新增值
  • 显式存在性字段/扩展 ↔ 新 oneof 成员(受限场景);
  • 仅含一个字段的 oneof ↔ 显式存在性字段;
  • 字段与"同号同类型的 extension"互换。
    (4)Wire-compatible(条件兼容)
  • int32/uint32/int64/uint64/bool 之间兼容 (可能截断/溢出,需灰度控制写入范围);
  • sint32 ↔ sint64 兼容,但与其他整数类型不兼容(zigzag 差异);
  • string ↔ bytesbytes 必须是有效 UTF-8);
  • message ↔ bytesbytes 为该消息的编码);
  • singular ↔ repeated数值型不安全 ,因 repeated 数值默认 packed 与 singular 不兼容;非数值:单值取最后一个 ,消息会merge);
  • map<K,V> ↔ repeated Entry(语义兼容但 map 可能重排或去重,应用相关)。

二十、未知字段(Unknown Fields)

(1)旧二进制解析新数据时,新字段在旧解析器中成为未知字段

(2)在 proto3 中,未知字段会被保留 (与 proto2 一致),并在再次序列化时写回。

(3)会丢失未知字段的操作

  • 序列化到 JSON
  • 逐字段拷贝(遍历所有字段构造新消息)。
    (4)避免丢失的建议
  • 使用二进制,避免文本格式交换;
  • 使用面向消息 的 API(如 CopyFrom() / MergeFrom()),不要逐字段复制。
    (5)TextFormat 特殊性:序列化会按编号 打印未知字段;但再解析回二进制时,如果仍使用编号表示,可能解析失败

二十一、Any(任意消息容器)

(1)Any 允许在未知类型场景下嵌入任意消息(包含消息字节与类型 URL);需导入:

proto 复制代码
import "google/protobuf/any.proto";

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

(2)默认类型 URL:type.googleapis.com/<package>.<Message>

(3)各语言提供 pack()/unpack()(或等价方法)进行类型安全的打包/解包。

二十二、oneof(互斥字段)

(1)当"同时最多一个 字段被设置"时,使用 oneof 可节省内存并强制互斥:

proto 复制代码
message SampleMessage {
  oneof test_oneof {
    string name = 4;
    SubMessage sub_message = 9;
  }
}

(2)特性与解析规则

  • 设置 oneof 的任意成员会清空其他成员;
  • 解析时同一 oneof 多成员出现,只保留最后出现的那个;
  • 基元值会覆盖 ;消息会merge
  • oneof 的成员设为默认值 也会占用 case序列化
  • maprepeated 不能 直接放入 oneof(可包在消息里)。
    (3)C++ 注意 :避免对已被清理的子消息操作;交换 swap 两个含 oneof 的消息会交换其 oneof case
    (4)向后兼容与标签复用问题
  • 在单值字段与 oneof 间移动,往返序列化可能丢失信息(被清空);
  • 删除后再添加回、拆分/合并 oneof 存在相似风险;
  • 检查 oneof 的值为 None/NOT_SET 时,无法区分"未设置"与"被设置为另一版本中的不同成员"。

二十三、map<K,V>(键值对)

(1)语法:

proto 复制代码
map<key_type, value_type> map_field = N;
  • key_type:整型或 string不能 是浮点、bytesenummessage);
  • value_type:除 map 外任意类型。
    (2)特性
  • map 字段不能repeated
  • 线格式与遍历顺序未定义 (不要依赖顺序);TextFormat 输出按键排序(数值键按数值);
  • 解析/合并遇到重复键 ,以最后一次为准(TextFormat 可能解析失败);
  • 仅提供键 没有值,序列化行为与语言相关(C++/Java/Kotlin/Python 会序列化默认值;其他语言可能不序列化);
  • map foo 同一作用域禁止出现符号名 FooEntry(被实现占用)。
    (3)向后兼容:线格式等价于
proto 复制代码
message MapFieldEntry { key_type key = 1; value_type value = 2; }
repeated MapFieldEntry map_field = N;

支持 map 的实现必须能读写这两种格式。

二十四、包(Packages)与命名

(1)使用 package 防止类型重名:

proto 复制代码
package foo.bar;
message Open { ... }

message Foo {
  foo.bar.Open open = 1;
}

(2)语言层影响(简述):

  • C++ → 命名空间 foo::bar
  • Java/Kotlin → 作为 Java 包,除非显式 java_package
  • Python → 忽略 package(仍建议写,以免描述符冲突);
  • Go → 忽略 package,实际由 go_package 或构建规则决定(开源必须提供 go_package 或 -M);
  • Ruby → 嵌套命名空间(首字母大写,非字母开头加 PB_);
  • PHP/C# → 转 PascalCase 作为命名空间,或受 php_namespace/csharp_namespace 覆盖。
    (3)名称解析 类似 C++:先最内层再向外;以 . 前缀(如 .foo.bar.Baz)从最外层开始。

二十五、服务定义(Services)与 gRPC

(1)在 .proto 中定义 RPC 接口,编译器生成服务接口与桩代码:

proto 复制代码
service SearchService {
  rpc Search(SearchRequest) returns (SearchResponse);
}

(2)gRPC 与 Protobuf 深度集成,可直接用插件自 .proto 生成 RPC 代码。

(3)也可使用自研 RPC 或第三方实现(详见官方第三方插件列表/维基)。

二十六、JSON 映射(ProtoJSON)

(1)标准二进制线格式是 Protobuf 之间通信的首选

(2)与仅支持 JSON 的系统通信时,可使用规范的 JSON 编码

(3)注意:转 JSON 会丢失未知字段(见第二十节)。

二十七、Options(选项)

(1)选项不改动语义,但会影响生成或运行时行为;完整列表见 /google/protobuf/descriptor.proto

(2)按作用域可分为文件级消息级字段级枚举/枚举值级oneof 级服务/方法级 (但有些层级目前暂无实用选项)。

(3)常用选项(节选):

  • 文件级

    java_package:生成 Java/Kotlin 包名(不生成 Java/Kotlin 时无效)

    proto 复制代码
    option java_package = "com.example.foo";

    java_outer_classname:外层包装类名(java_multiple_files=false 时其他类型作为内部类)

    proto 复制代码
    option java_outer_classname = "Ponycopter";

    java_multiple_filestrue 时顶级类型分别生成 .java 文件(推荐)

    proto 复制代码
    option java_multiple_files = true;

    optimize_forSPEED(默认)/CODE_SIZE/LITE_RUNTIME

    proto 复制代码
    option optimize_for = CODE_SIZE;

    cc_generic_services / java_generic_services / py_generic_services已弃用(默认历史原因为 true,建议禁用,改用 RPC 插件)

    proto 复制代码
    option cc_generic_services = false;
    option java_generic_services = false;
    option py_generic_services = false;

    cc_enable_arenas:启用 C++ arena 分配。

    objc_class_prefix:Objective-C 类前缀(推荐 3--5 个大写字母;2 字母前缀保留给 Apple)。

  • 字段级

    packed:数值型 repeated 在 proto3 默认 packed;与旧解析器兼容可设 false

    proto 复制代码
    repeated int32 samples = 4 [packed = false];

    deprecated:标记字段不建议使用;多数语言仅产生注解/警告;C++ 可触发 clang-tidy 警告

    proto 复制代码
    int32 old_field = 6 [deprecated = true];
  • 枚举值选项(含自定义扩展):

proto 复制代码
import "google/protobuf/descriptor.proto";

extend google.protobuf.EnumValueOptions {
  optional string string_name = 123456789;
}

enum Data {
  DATA_UNSPECIFIED = 0;
  DATA_SEARCH = 1 [deprecated = true];
  DATA_DISPLAY = 2 [(string_name) = "display_value"];
}

(4)自定义选项 :高级特性,依赖 extensions (在 proto3 中 允许用于自定义选项本身)。

(5)选项保留(Option Retention)

  • 默认 runtime(运行时保留,描述符可见);
  • 可设 retention = RETENTION_SOURCE,仅源级 保留,不进入运行时代码(降体积),对 protoc/插件仍可见:
proto 复制代码
extend google.protobuf.FileOptions {
  optional int32 source_retention_option = 1234 [retention = RETENTION_SOURCE];
}
message OptionsMessage {
  int32 source_retention_field = 1 [retention = RETENTION_SOURCE];
}

截至 Protobuf 22.0:C++/Java 支持;Go 自 1.29.0 起支持;Python 实现已完成但尚未入发行版。

(6)选项目标(Targets):限制某选项字段能应用到哪些实体(文件/消息/枚举等):

proto 复制代码
message MyOptions {
  string file_only_option = 1 [targets = TARGET_TYPE_FILE];
  int32  message_and_enum_option = 2 [
    targets = TARGET_TYPE_MESSAGE, targets = TARGET_TYPE_ENUM
  ];
}
extend google.protobuf.FileOptions    { optional MyOptions file_options    = 50000; }
extend google.protobuf.MessageOptions { optional MyOptions message_options = 50000; }
extend google.protobuf.EnumOptions    { optional MyOptions enum_options    = 50000; }

// OK
option (file_options).file_only_option = "abc";

message MyMessage {
  // OK
  option (message_options).message_and_enum_option = 42;
}

enum MyEnum {
  MY_ENUM_UNSPECIFIED = 0;
  // Error:file_only_option 不能用于 enum
  option (enum_options).file_only_option = "xyz";
}

二十八、代码生成命令(protoc

(1)基本形式:

bash 复制代码
protoc --proto_path=IMPORT_PATH \
  --cpp_out=DST_DIR --java_out=DST_DIR --kotlin_out=DST_DIR \
  --python_out=DST_DIR --go_out=DST_DIR --ruby_out=DST_DIR \
  --objc_out=DST_DIR --csharp_out=DST_DIR --php_out=DST_DIR \
  path/to/file.proto

(2)IMPORT_PATH:解析 import 的查找路径,可多次指定;-I=... 是简写。

(3)全局规范名唯一 :相对各自 proto_path 的文件名必须全局唯一。不要在不同 -I 目录下放置同名 data.proto 并期望 import "data.proto" 能区分;应统一以更高层 -I 指向公共根,使全局名(如 lib1/data.protolib2/data.proto)唯一。

(4)发布库时,建议在 proto 路径中包含唯一库名 ,避免与他人冲突。

(5)输出归档 :若 DST_DIR.zip.jar 结尾,输出将打包到单一归档;.jar 还会生成 manifest。已存在 会被覆盖

(6)必须提供一个或多个 .proto 输入;这些文件必须位于 IMPORT_PATH 下,以便编译器确定其规范名

二十九、.proto 文件放置建议

(1)不要.proto 与其他语言源码混在同一目录;建议在项目根包下创建语言无关proto/ 子包。

(2)例外 :仅在明确只用于 Java(如测试)的场景下可以共置。

(3)对于多语言项目,统一在项目根protos/ 下管理所有 .proto,并统一 --proto_path

三十、支持平台(概览入口)

(1)关于 操作系统、编译器、构建系统与 C++ 版本 :参见 Foundational C++ Support Policy

(2)关于 PHP 支持版本 :参见 Supported PHP versions

三十一、实践清单与常见陷阱

(一)发布前清单

1)新增字段编号与历史/保留不冲突;

2)删除字段已 reserved 编号与名称

3)枚举新增值可能导致下游"穷尽 switch"编译告警,需先处理;

4)若做兼容(Compatible)变更,先升级读端 ,再放开写入扩大值域;

5)双向解析与未知字段透传的集成测试已覆盖。

(二)常见陷阱

1)"重新排号 "追求美观 → 等价"删+新",严禁

2)删除字段不 reserved复用编号 引发数据损坏/隐私泄露

3)把隐式标量 当"业务开关",false 不可与"未提供"区分 → 用 optional bool

4)随意在单值与 oneof 间迁移 → 往返序列化丢值

5)指望 map 有序 → 顺序未定义;需要顺序改用 repeated + 显式排序键;

6)想透传未知字段却转 JSON → 未知字段丢失;请用二进制与消息级拷贝 API。

三十二、完整小结

1)编号不可变、不可复用 ;删除后 reserved 编号与名称。

2)optional 优先 ,避免隐式标量的"默认值不可区分"。

3)常用字段放 1--15 ;数值 repeated 在 proto3 默认 packed

4)枚举首值为 0 ,命名 *_UNSPECIFIED/UNKNOWN

5)oneof 互斥共享存储,注意迁移风险与 C++ 指针生命周期。

6)未知字段仅在二进制 保留;JSON/Text 会丢失。

7)模块化 .proto、合理 packageimport public,控制"每文件类型数量"。

8)演进尽量选择 Wire-safeCompatible 需强约束"写入时机与范围"。

相关推荐
我是一颗柠檬3 小时前
【MySQL全面教学】MySQL面试高频考点汇总Day15(2026年)
数据库·后端·mysql·面试
橙淮4 小时前
并发编程(六)
java·jvm
拽着尾巴的鱼儿4 小时前
springboot openfeign 自定义feign 接口重试机制
java·spring boot·后端
白露与泡影4 小时前
2026大厂Java面试题大全!牛客网最新版
java·开发语言
凯瑟琳.奥古斯特4 小时前
高阶子查询题目精炼
开发语言·数据库·python·职场和发展·数据库开发
lolo大魔王4 小时前
Linux 文件系统超全面详解(原理、结构、挂载、分区、inode、日志、管理命令)
linux·运维·服务器
身如柳絮随风扬4 小时前
数据库读写分离:从原理到实战,构建高并发系统
数据库·mysql
EntyIU5 小时前
JVM内存与GC笔记
java·jvm·笔记
XS0301065 小时前
并发编程 六
java·后端