protocal buffers 官方文档学习
protocal buffers 官方文档学习
文章目录
- protocal buffers 官方文档学习
- 1.What are protocol buffers?
- 2.选择你喜欢的语言
- 3.怎么开始,步骤
- 3.1下载和安装protocal buffer 编译器
- 3.2阅读入门指南
- 3.2.1 开发者指南
- 3.2.1.1 怎么开始?
- 3.2.1.2为什么不使用xml?
- 3.2.1.3介绍一下protobuf3
- 3.2.2 proto3语言
- 3.2.2.1 定义一个消息类型
- 3.2.2.2 字段值类型
- 3.2.2.3枚举
- 3.2.2.4 使用消息类型
- 3.2.2.5嵌套类型
- 3.2.2.6 更新一个消息时怎么办
- 3.2.2.7 未知的字段
- 3.2.2.8 any 类型
- 3.2.2.9Oneof
- 3.2.2.11定义服务
- 3.2.2.12可选项
- 3.2.3编码风格指南
- 3.2.3.1标准文件格式
- 3.2.3.2 消息和字段名称
- 3.2.3.4 重复字段
- 3.2.3.5 枚举
- 3.2.3.4 重复字段
- 3.2.3.5 枚举
前言:最近在做一个项目,负责通讯这块。采用技术是netty+protobuf 在安卓端和c#端进行通信。项目快差不多了,做一下沉淀,翻译一下protbuf文档,和自己的项目工程实践。工程实践代码可以看我的另一篇博文,是接着写的。
1.What are protocol buffers?
protocal buffer是由谷歌公司研发的,是一种与语言,平台无关,可扩展的为结构化数据序列化的机制。
相对比xml,它更小,更快,更简单。
一旦你定义好了数据结构化,那么就可以使用提供的编译器生成生成的代码,使用protocal buffer 支持的数据流和语言读取和写入结构化数据。
2.选择你喜欢的语言
protocal buffer 当前支持可以生成代码的语言有java,python,c,c++,在最近的proto3版本中,你也可以使用Dart,GO和c#语言。
3.怎么开始,步骤
3.1下载和安装protocal buffer 编译器
安装教程自行百度搜索
3.2阅读入门指南
3.2.1 开发者指南
3.2.1.1 怎么开始?
通过在.proto文件中定义协议缓冲区消息类型,可以指定要序列化的信息的结构。每个协议缓冲区消息都是一个小的信息逻辑记录,包含一系列名称-值对。下面是一个非常基本的.proto文件示例,该文件定义了一条包含有关某人信息的消息
message Person {required string name = 1;required int32 id = 2;optional string email = 3;enum PhoneType {MOBILE = 0;HOME = 1;WORK = 2;}message PhoneNumber {required string number = 1;optional PhoneType type = 2 [default = HOME];}repeated PhoneNumber phone = 4;
}
如这个proto文件所示,消息格式很简单——每个消息类型都有一个或多个唯一编号的字段,每个字段都有一个名称和一个值类型,其中值类型可以是数字(整数或浮点)、布尔值、字符串、原始字节,甚至其他协议buffer消息类型,允许您按层次结构构建数据。可以指定optional字段、required字段和repeated字段。
optional字段:这个字段可赋值,可不赋值。
required字段:指定这个字段必须进行赋值等操作。
repeated字段:相当于一个集合。
一旦定义了消息,就可以在.proto文件上运行编译器来生成数据访问类。这个类里面会有方法为每个字段(如name)提供简单的访问器比如get/set方法,以及将整个结构序列化/解析为原始字节/从原始字节序列化/解析整个结构的方法。
可以在不破坏向后兼容性的情况下向消息格式添加新字段,可以扩展你的协议,而不必担心破坏现有的代码。
你可以在这里 API Reference section找到所生成的类里面的一些方法的使用与解释,如果想要了解更多关于protocal buffer 消息和编解码的内容可以看着里Protocol Buffer Encoding。
3.2.1.2为什么不使用xml?
protocal buffer的优势:
数据更简单,小3到10倍,快20到100倍,不那么模棱两可,生成更易于编程使用的数据访问类.
当然,protocal buffer 并不是在所有的地方都要好于xml,首先,protobuf 不是用标记(例如HTML)对基于文本的文档建模的好方法,因为您不能轻易地将结构与文本交错,另外,xml是人类可读和可编辑的,protobuf不是。protobuf只有在定义了消息,才是有意义的。
3.2.1.3介绍一下protobuf3
现在发布了一个最新的语言版本proto3,并且proto2中也增加了一些新功能。proto3简化了protocal buffer语言,并简化了使用和在更广泛的语言范围内使用,当前版本使您可以在java、C++、Python、Java Lite、Ruby、JavaScript、ObjuleC和C*中生成协议缓冲代码.
注意,当前两个版本的API并不完全的兼容,为了避免对现有的用户造成麻烦,将会持续对先前的版本支持。
3.2.2 proto3语言
3.2.2.1 定义一个消息类型
首先来看一个简单的例子,定义一个查询请求的消息,在这个消息类型中有一个 query, page_number,result_per_page的属性,下面的 .proto文件里面就定义你的消息类型
syntax = "proto3";message SearchRequest {string query = 1;int32 page_number = 2;int32 result_per_page = 3;
}
第一行表示你用的版本是proto3,如果不写明,那么默认你采用proto2,在文件中,这个必须是非空,并且没有注释的一行。
在这个SearchRequest消息定义了三个字段,每一个字段都有一个名字和类型。
指定字段的类型
在上面的例子中,我们用了2个整型,和一个string类型,你还可以定义其他的类型,还有枚举类型和特殊的消息类型。
分配字段编号
如你所见,每一个字段都有一个独一无二的编号,这些字段的值用于消息二进制格式标识你的字段,一旦消息类型被定义使用,就不应该修改这些字段。需要注意的是,端编号从1号15号采用1字节编码,,16 到 2047采用2字节编码。所以你应该为那些经常出现的字段保留1-15,。 记住,要预留出一些空间 给一些未来可能添加的频繁使用的元素。
指定字段规则
singular:符合语法规则的消息可以包含0个或者一个字段值(但不能超过一个),这是proto3默认的字段规则。可以不用在字段类型前指明。
repeated: 被此规则指明的字段,可以出现多次赋值,或者0次,也就是像一个集合类型的规则,并且会保留重复字段的顺序。
添加更多的消息类型
多个消息类型可以被定义在一个.proto文件中,例如为上面的proto文件增加一个搜索回复的消息类型
message SearchRequest {string query = 1;int32 page_number = 2;int32 result_per_page = 3;
}message SearchResponse {...
}
添加注释的规则
为你的.proto文件添加注释。如果在.proto文件中添加了注释,在那么生成的代码中,会自动生成注释。
/* SearchRequest represents a search query, with pagination options to* indicate which results to include in the response. */message SearchRequest {string query = 1;int32 page_number = 2; // Which page number do we want?int32 result_per_page = 3; // Number of results to return per page.
}
保留字段(不常用)
如果通过完全删除字段或将其注释掉来更新消息类型,将来的用户可以在对该类型进行自己的更新时重用字段号。如果他们以后加载相同.proto的旧版本,这可能会导致严重的问题,包括数据损坏、隐私错误等。确保不会发生这种情况的一种方法是指定保留已删除字段的字段编号(和/或名称,这也可能导致JSON序列化问题)。如果将来有任何用户试图使用这些字段标识符,协议缓冲区编译器将抛出异常,不能编译。
message Foo {reserved 2, 15, 9 to 11;reserved "foo", "bar";
}
注意,你不能将字段名字和字段编号混合使用在同一个保留声明中。
通过.proto文件生成的是什么
当你用编译器来运行.proto文件时,编译器会生成你所选择的语言类型的代码,这些代码里包含了你定义的消息类型和每一个字段的get/set方法,以及将消息序列化到输出流,和从输入流分析消息。
上图介绍的就是选择不同的语言生成的文件,以及一些简单构造等。
如果想看更多的所选语言的API,请看 API reference。
3.2.2.2 字段值类型
在.proto文件中定义的字段值类型可以有一下类型,并且在生成的代码中会自动对应所选语言的值类型。
[外链图片转存失败(img-JmTgsR6X-1567085491498)(F:\笔记\Linux_zfc\image\1566826033811.png)]
当一个消息被解析时,如果编码的(被解析的)消息中没有包含原本消息中定义的singular 字段,那么这个字段就会被默认赋值。
如果是字段类型是
string类型,默认值为空
字节 类型,默认值为空字节
布尔类型, 默认值为false
numeric类型,默认值为0
枚举类型,默认值是第一个被定义的枚举值,它一定是0,也就是枚举类型的第一个值为0.
对于有重复规则的字段来说,默认值为空。
请注意,对于标量消息字段,一旦解析了消息,就无法判断字段是否显式设置为默认值(例如布尔值是否设置为false),或者根本没有设置:在定义消息类型时,您应该记住这一点。例如,如果您不希望某些行为在默认情况下也发生,则不要使用布尔值在设置为false时打开某些行为。另外请注意,如果将标量消息字段设置为其默认值,则不会在通信中上序列化该值。
3.2.2.3枚举
在定义消息类型是,如果你希望一个字段有一个预定义的值列表。例如假设您希望为每个搜索请求添加一个语料库字段,其中语料库可以是通用的、Web、图像、本地、新闻、产品或视频。您可以通过为消息定义添加一个枚举来实现这一点,每个可能的值都有一个常量。
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;
}
如你所见,枚举的第一个元素映射到常量0。在定义枚举时,第一个元素必须映射到常量0
可以通过为不同的枚举常量指定相同的值来定义别名。为此,您需要将allow_alias选项设置为true,否则当找到别名时,协议编译器将生成一条错误消息。
enum EnumAllowingAlias {option allow_alias = true;UNKNOWN = 0;STARTED = 1;RUNNING = 1;
}
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文件中的任何的message中重用。**还可以使用一条消息中声明的枚举类型作为另一条消息中字段的类型,使用语法 MessageType.EnumType
在反序列化过程中,消息中将保留无法识别的枚举值,尽管反序列化消息时如何表示枚举值取决于语言。在支持具有指定符号范围之外的开放枚举类型的语言(如C++和GO)中,未知的枚举值仅作为其基础的整数表示形式存储。在具有封闭枚举类型的语言(如Java)中,枚举中的一个实例用于表示未识别的值,而基础的整数可以用特殊的访问器访问。在这两种情况下,如果消息被序列化,则无法识别的值仍将用消息序列化。
对于更多的关于如何使用枚举类型的信息可以在generated code guide中找到你所选择的语言的教程
3.2.2.4 使用消息类型
你可以在一个消息中使用 另一种消息做为一个字段的类型。例如,你想在SearchResponse
消息中添加包括Result
类型,那么你可以在.proto文件中定义一个Result
消息,在SearchResponse消息中指定一个字段的类型为Result` 消息类型、
message SearchResponse {repeated Result results = 1;
}message Result {string url = 1;string title = 2;repeated string snippets = 3;
}
导入定义好的ptoto
在上面的例子中,Result消息是定义在与SearchResponse` 同一个proto文件中,那么如果你想让一个字段的类型为其他的已经定义好的.proto文件中的消息类型,那怎么办呢?这时,你可以通过import关键字导入这个proto文件。
import "myproject/other_protos.proto";
默认情况下,你只能使用直接导入的proto文件中的定义。然而,有时候这样可能会需要移动proto文件到一个新的位置。为了不直接移动proto文件位置和更新所有的调用的地方,你可以可以在旧位置放置一个虚拟的.proto文件,使用import public概念将所有导入转发到新位置。
// new.proto
//所有的定义移动到此文件中
// old.proto
// This is the proto that all clients are importing.
import public "new.proto";
import "other.proto";
// client.proto
import "old.proto";
// You use definitions from old.proto and new.proto, but not other.proto
//你可以使用来自old.proto和new.proto文件中的定义,但不能使用other.proto文件中的定义
3.2.2.5嵌套类型
你可以定义和使用消息类型 在其他的消息类型内部,就像下面的例子中,在SearchResponse消息类型中定义了Result消息类型。
message SearchResponse {message Result {string url = 1;string title = 2;repeated string snippets = 3;}repeated Result results = 1;
}
如果你想要在父消息类型之外重用这个消息,那么很使用的时候,要指明父消息
message SomeOtherMessage {SearchResponse.Result result = 1;
}
你可以随心所欲地把消息内嵌的很深,就像下面这样
message Outer { // Level 0message MiddleAA { // Level 1message Inner { // Level 2int64 ival = 1;bool booly = 2;}}message MiddleBB { // Level 1message Inner { // Level 2int32 ival = 1;bool booly = 2;}}
}
3.2.2.6 更新一个消息时怎么办
如果存在的一个消息类型不在满足你的需求,例如,你想要增加这个消息格式中增加一个字段,但是你仍然希望使用旧格式创建的代码。不要担心,在不破坏任何现有代码的情况下更新消息类型是很简单的,请记住以下规则:
1.不要改变已经存在的字段的字段编号
2.如果添加新字段,则使用“旧”消息格式的代码序列化的任何消息仍然可以由新生成的代码解析。您应该记住这些元素的默认值,以便新代码可以与旧代码生成的消息正确交互。同样,由新代码创建的消息可以由旧代码解析:旧二进制文件在解析时只忽略新字段。
3.只要在更新的消息类型中不再使用字段编号,就可以删除字段。您可能需要重命名字段,或者添加前缀“obsolete_u”,或者保留字段编号,这样.proto的未来用户就不能意外地重用该编号。
4.int32、uint32、int64、uint64和bool都是兼容的——这意味着您可以在不中断向前或向后兼容性的情况下将字段从这些类型中的一种更改为另一种。sint32和sint64彼此兼容,但与其他整数类型不兼容。只要字节是有效的UTF-8,字符串和字节就可以兼容。如果字节包含消息的编码版本,则嵌入消息与字节兼容。fixed32与sfixed32兼容,fixed64与sfixed64兼容。
5.枚举类型在格式方面与Int32、UInt32、Int64和UInt64兼容,注意,如果值不对应,则会被截断。但是请注意,当消息被反序列化时,客户端代码可能会对它们进行不同的处理:例如,消息中将保留无法识别的proto3枚举类型,但是当消息被反序列化时,如何表示这些枚举类型取决于语言。int字段总是保留其值。这里做一个强调,就是对于枚举类型,我们可以为其赋值为Int32、UInt32、Int64,UInt64类型的值,然后传输到客户端,客户端经过反序列化,如果值对应的话,可以得到整型。(但表示也取决于语言)。比如服务端和客户端 都采用java语言。通常为枚举赋值,我们是 .setEnum(Message.EnumOneValue),但是如果我们想赋值整型,我们可以这样写 .setEnumValue(int a) ,然后客户单收到时,可以getEnumValue(),这是java语言生成的代码中的方法。其他语言比如c#,会在案例中用到。
6.将单个值更改为新的oneof的成员是安全的,并且与二进制兼容。如果您确定一次没有设置多个代码,那么将多个字段移动到新的字段中可能是安全的。将任何字段移动到现有的某个字段中是不安全的。
3.2.2.7 未知的字段
未知字段表示作为语法分析无法识别这个字段。例如,当用一个旧的二进制文件解析有新的二进制文件发来的数据,这些新的字段就会成为旧的二进制文件的未知字段。
最初,Proto3消息在解析过程中总是丢弃未知字段,但在3.5版中,我们重新引入了保留未知字段以匹配Proto2行为。在3.5及更高版本中,解析期间会保留未知字段,并包含在序列化输出中。
3.2.2.8 any 类型
Any消息类型允许您将消息作为嵌入类型,而不需要它们 .proto定义。Any包含任意序列化的消息(字节),以及一个URL,该URL充当该消息的全局唯一标识符并解析为该消息的类型。要使用Any类型,你需要导入google/protobuf/any.proto
3.2.2.9Oneof
如果有一条包含许多字段的消息,并且最多同时设置一个字段,您可以使用其中oneof功能来强制执行此行为并节省内存。
Oneof 字段类似于常规字段,除了Oneof共享内存的所有字段之外,最多可以同时设置一个字段。设置Oneof 的任何一个成员都会自动清除所有其他成员。可以使用case()或WhichOneof()方法检查Oneof 中的哪个值被设置(如果有的话),具体取决于您选择的语言。
使用oneof
在 .proto中定义一个oneof 关键字后跟着oneof 名称,在本例中为test_oneof:
message SampleMessage {oneof test_oneof {string name = 4;SubMessage sub_message = 9;}
}
然后将您的oneof字段添加到oneof定义中。您可以添加任何类型的字段,但不能使用重复字段。
在生成的代码中,oneof字段具有与常规字段相同的setter和getter方法。您还可以获得一种特殊的方法来检查中的哪个值(如果有的话)被设置。你可以在相关的API(API reference)参考中找到更多关于你选择的语言的API
oneof功能*
设置oneof字段将自动清除oneof字段的所有其他成员。因此,如果您设置了几个oneof字段,则只有最后一个字段才有值。
如果解析器遇到同一oneof的多个成员,则在解析的消息中只使用最后一个成员。
oneof不能重复。
3.2.2.11定义服务
如果要将消息类型用于RPC (远程过程调用)系统,可以在.proto文件中定义RPC服务接口和协议缓冲编译器将使用您选择的语言生成服务接口代码和存根。因此,如果您想用一种接受您的搜索请求并返回搜索响应的方法来定义RPC服务,可以在.proto文件中定义它。 如下:
service SearchService {rpc Search (SearchRequest) returns (SearchResponse);
}
与protobuf一起使用的最直接的RPC系统是gRPC : Google开发的语言和平台无关的开源RPC系统。gRPC与protobuf配合得特别好,并允许您直接从使用特殊协议缓冲编译插件的.proto文件中生成相关的RPC代码。
3.2.2.12可选项
proto文件中可以声明许多选项。选项不会改变声明的整体含义,但可能会影响在特定上下文中处理声明的方式
有些选项是文件级选项,这意味着它们应该写在顶级范围内,而不是任何消息、枚举或服务定义内。有些选项是消息级选项,这意味着它们应该写在消息定义中。有些选项是字段级选项,这意味着它们应该写在字段定义中。选项也可以写在枚举类型、枚举值、服务类型和服务方法上。
以下是一些最常用的选项:
java_package : 用于生成的Java类的包。如果.proto文件中没有给出明确的Java _ package选项,那么默认情况下将使用proto package (在.proto文件中使用“package”关键字指定)。然而,proto包通常不能成为好的Java包,因为proto包不应该以反向域名开始。如果不生成Java代码,则此选项无效。
用法(写在所有消息类型外): option java_package = “com.example.foo”;
java_outer_classname (file option): 要生成的最外面的Java类的类名(因此也是文件名)。如果.proto文件中未指定显式 java_outer_classname,则类名将通过转换来构造。原型文件名转换为ccamel-case(因此foo_bar.proto变成了FooBar.java)。如果不生成Java代码,则此选项无效。
用法(写在所有消息类型外) :option java_outer_classname = “Ponycopter”;
optimize_for (file option): 可以设置为SPEED、CODE_SIZE或LITE_RUNTIME。这会以以下方式影响c++和Java代码生成器(以及可能的第三方生成器)
SPEED (default): 协议缓冲区编译器将生成用于序列化、解析和对消息类型执行其他常见操作的代码。这个代码是高度优化的。
CODE_SIZE: 协议缓冲编译器将生成最少的类,并将依赖共享的、基于反射的代码来实现序列化、解析和各种其他操作。因此,生成的代码将比速度小得多,但是操作将会更慢。类仍将实现与它们在速度模式下完全相同的公共API。这种模式在包含大量.proto文件的应用程序中非常有用,并且不需要所有这些文件都非常快。
LITE_RUNTIME: 协议缓冲编译器将生成仅依赖于“lite”运行时库( ibprotobuf-lite而不是libprotobuf )的类。lite运行时比整个库小得多(大约小一个数量级),但省略了某些功能,如描述符和反射。这对在手机等受限平台上运行的应用程序特别有用。编译器仍然会生成所有方法的快速实现,就像在速度模式下一样。生成的类将只实现每种语言的MessageLite接口,它只提供完整消息接口方法的子集。
用法: option optimize_for = speed;
3.2.3编码风格指南
3.2.3.1标准文件格式
保持行长度为80个字符。
使用2个空格的缩进。
3.2.3.2 消息和字段名称
使用CamelCase (首字母大写)作为消息名,例如SongServerRequest。字段名称使用下划线分隔名称,例如song_name
message SongServerRequest {required string song_name = 1;
}
如果字段名包含数字,则数字应出现在字母后,而不是下划线后。例如,使用song_name 1而不是song_name_1
3.2.3.4 重复字段
对重复字段使用复数名称。
repeated string keys = 1;...repeated MyMessage accounts = 17;
3.2.3.5 枚举
枚举类型名使用CamelCase (带首字母大写),值名使用CAPITALS_WITH_UNDERSCORES:
enum Foo {FIRST_VALUE = 0;SECOND_VALUE = 1;
}
每个枚举值应以分号结束,而不是逗号.
ame
message SongServerRequest {required string song_name = 1;
}
如果字段名包含数字,则数字应出现在字母后,而不是下划线后。例如,使用song_name 1而不是song_name_1
3.2.3.4 重复字段
对重复字段使用复数名称。
repeated string keys = 1;...repeated MyMessage accounts = 17;
3.2.3.5 枚举
枚举类型名使用CamelCase (带首字母大写),值名使用CAPITALS_WITH_UNDERSCORES:
enum Foo {FIRST_VALUE = 0;SECOND_VALUE = 1;
}
每个枚举值应以分号结束,而不是逗号.
protocal buffers 官方文档学习相关推荐
- ZooKeeper官方文档学习笔记03-程序员指南03
我的每一篇这种正经文章,都是我努力克制玩心的成果,我可太难了,和自己做斗争. ZooKeeper官方文档学习笔记04-程序员指南03 绑定 Java绑定 客户端配置参数 C绑定 陷阱: 常见问题及故障 ...
- ZooKeeper官方文档学习笔记01-zookeeper概述
纠结了很久,我决定用官方文档学习 ZooKeeper概述 学习文档 学习计划 ZooKeeper:分布式应用程序的分布式协调服务 设计目标 数据模型和分层名称空间 节点和短命节点 有条件的更新和监视 ...
- Spring Framework 官方文档学习(四)之Validation、Data Binding、Type Conversion(二)
接前一篇 Spring Framework 官方文档学习(四)之Validation.Data Binding.Type Conversion(一) 本篇主要内容:Spring Type Conver ...
- Spring Boot 官方文档学习(一)入门及使用
Spring Boot 官方文档学习(一)入门及使用 个人说明:本文内容都是从为知笔记上复制过来的,样式难免走样,以后再修改吧.另外,本文可以看作官方文档的选择性的翻译(大部分),以及个人使用经验及问 ...
- R语言reshape2包-官方文档学习
R语言reshape2包-官方文档学习 简介 核心函数 长数据与宽数据 宽数据 长数据 melt函数 meltarray meltdataframe meltdefault meltlist cast ...
- Spring Data Commons 官方文档学习
Spring Data Commons 官方文档学习 -by LarryZeal Version 1.12.6.Release, 2017-07-27 为知笔记版本在这里,带格式. Table o ...
- Spring Framework 官方文档学习(四)之Validation、Data Binding、Type Conversion
本篇太乱,请移步: Spring Framework 官方文档学习(四)之Validation.Data Binding.Type Conversion(一) 写了删删了写,反复几次,对自己的描述很不 ...
- jsTree 组件官方文档学习
jsTree 组件官方文档学习 什么是 jsTree 根据jsTree官网的解释:jsTree 是一个jquery 插件, 提供交互式树.它是完全免费的,开源的,并根据MIT许可进行分发.jsTree ...
- HarmonyOS(一) 快速开始学习鸿蒙开发,官方文档学习路线解析
系列文章目录 HarmonyOS(一):快速开始学习鸿蒙开发,官方文档学习路线解析 HarmonyOS(二):应用开发环境搭建准备 HarmonyOS(三):创建你的第一个HelloWorld应用 文 ...
最新文章
- 如何静态添加toolbar到datagrid
- 学用MVC4做网站二:2.2添加用户组
- 永恒python怎么强化_永恒python加6_pythontip 挑战python (6-10)
- hadoop配置文件的加载机制
- 测试结果表明开车打手机比酒后开车更危险
- java url utf 8_java中文乱码解决之道(八)—–解决URL中文乱码问题
- 【vue】vue +element 搭建项目,要求既支持pc端又支持移动端
- Leetcode1293.网格中的最短路径
- vector::erase()的那些事儿
- Django深入模板引擎
- linux livecd 挂载硬盘,网上的Ubuntu LiveCD硬盘安装方法
- Java单例模式实现方式
- 【整理】system\app中的APK一览
- 软银没有中国,孙正义失去一切
- CRT和LCD显示器的区别
- 【2022年度总结2023新年Flag】--2022:高考失利,我奋力奔跑的大一上;2023,朝着成为更优秀的自己迈进ing
- 一只老鸟嵌入式工程师的血泪史!
- 使用设计模式出任CEO迎娶白富美(4)--走马上任,华丽转身
- 《先知·爱》 《先知·婚姻》
- 京东平台研发朱志国:领域驱动设计(DDD)理论启示