教面試官ReentrantLock源碼
2695
2025-03-31
Google Protocol Buffer(protobuf)是一種高效且格式可擴展的編碼結構化數據的方法。和JSON不同,protobuf支持混合二進制數據,它還有先進的和可擴展的模式支持。protobuf已在大多數軟件平臺上實現,包括適用于Android的精簡Java版。
http://developers.google.com/protocol-buffers/上有protobuf文檔,下載鏈接以及安裝說明。需要注意的是,Android平臺為構建精簡版的protobuf,所以不能使用中央Maven倉庫里的版本。在Java源碼目錄內執行mvn package -p lite可以生成精簡版。檢查是否有更多安裝細節。
JSON允許對JSONObject對象進行任意數據的讀寫操作,但protobuf要求使用模式來定義要存儲的數據。模式會定義一些消息,每個消息包含一些名-值對字段。字段可能是內置的原始數據類型,枚舉或者其他消息。可以指定一個字段是必須的還是可選的,以及其他一些參數。一旦定義好模式,就可以使用protobuf工具生成Java代碼。生成的Java類現在可以很方便地用來讀寫protobuf數據。
下面的代碼使用protobuf模式定義了Task信息:
package com.aptl.code.task;
option optimize_for = LITE_RUNTIME;
option java_package = "com.aptl.protobuf";
option java_outer_classname = "TaskProtos";
message Task {
enum Status {
CREATED = 0;
ONGOING = 1;
CANCELLED = 2;
COMPLETED = 3;
}
message Owner {
required string name = 1;
optional string email = 2;
optional string phone = 3;
}
message Comment {
required string author = 1;
required uint32 timestamp = 2;
required string content = 3;
}
required string name = 1;
required uint64 created = 2;
required int32 priority = 3;
required Status status = 4;
optional Owner owner = 5;
repeated Comment comments = 6;
}
這里將給出以上消息定義的關鍵性說明。
1. message是消息定義的關鍵字,等同于C++中的struct/class,或是Java中的class。
2. Task 為消息的名字,等同于結構體名或類名。
3. required前綴表示該字段為必要字段,既在序列化和反序列化之前該字段必須已經被賦值。與此同時,在Protocol Buffer中還存在另外兩個類似的關鍵字,optional和repeated,帶有這兩種限定符的消息字段則沒有required字段這樣的限制。相比于optional,repeated主要用于表示數組字段。具體的使用方式在后面的用例中均會一一列出。
4. int64和string分別表示長整型和字符串型的消息字段,在Protocol Buffer中存在一張類型對照表,既Protocol Buffer中的數據類型與其他編程語言(C++/Java)中所用類型的對照。該對照表中還將給出在不同的數據場景下,哪種類型更為高效。該對照表將在后面給出。
5. name,created ,priority ,status,owner 和comments分別表示消息字段名,等同于Java中的域變量名,或是C++中的成員變量名。
6. 標簽數字1和2則表示不同的字段在序列化后的二進制數據中的布局位置。在該例中,created字段編碼后的數據一定位于name之后。需要注意的是該值在同一message中不能重復。另外,對于Protocol Buffer而言,標簽值為1到15的字段在編碼時可以得到優化,既標簽值和類型信息僅占有一個byte,標簽范圍是16到2047的將占有兩個bytes,而Protocol Buffer可以支持的字段數量則為2的29次方減一。有鑒于此,我們在設計消息結構時,可以盡可能考慮讓repeated類型的字段標簽位于1到15之間,這樣便可以有效的節省編碼后的字節數量。
Protocol Buffer允許我們在.proto文件中定義一些常用的選項,這樣可以指示Protocol Buffer編譯器幫助我們生成更為匹配的目標語言代碼。Protocol Buffer內置的選項被分為以下三個級別:
1. 文件級別,這樣的選項將影響當前文件中定義的所有消息和枚舉。
2. 消息級別,這樣的選項僅影響某個消息及其包含的所有字段。
3. 字段級別,這樣的選項僅僅響應與其相關的字段。
下面將給出一些常用的Protocol Buffer選項。
1. option java_package = "com.aptl.protobuf";
java_package是文件級別的選項,通過指定該選項可以讓生成Java代碼的包名為該選項值,如上例中的Java代碼包名為com.aptl.protobuf。與此同時,生成的Java文件也將會自動存放到指定輸出目錄下的com/aptl/protobuf子目錄中。如果沒有指定該選項,Java的包名則為package關鍵字指定的名稱。該選項對于生成C++代碼毫無影響。
2. option java_outer_classname = "TaskProtos";
java_outer_classname是文件級別的選項,主要功能是顯示的指定生成Java代碼的外部類名稱。如果沒有指定該選項,Java代碼的外部類名稱為當前文件的文件名部分,同時還要將文件名轉換為駝峰格式,如:my_project.proto,那么該文件的默認外部類名稱將為MyProject。該選項對于生成C++代碼毫無影響。
注:主要是因為Java中要求同一個.java文件中只能包含一個Java外部類或外部接口,而C++則不存在此限制。因此在.proto文件中定義的消息均為指定外部類的內部類,這樣才能將這些消息生成到同一個Java文件中。在實際的使用中,為了避免總是輸入該外部類限定符,可以將該外部類靜態引入到當前Java文件中,如:import static com.aptl.protobuf.TaskProtos.*。
3. option optimize_for = LITE_RUNTIME;
optimize_for是文件級別的選項,Protocol Buffer定義三種優化級別SPEED/CODE_SIZE/LITE_RUNTIME。缺省情況下是SPEED。
SPEED: 表示生成的代碼運行效率高,但是由此生成的代碼編譯后會占用更多的空間。
CODE_SIZE: 和SPEED恰恰相反,代碼運行效率較低,但是由此生成的代碼編譯后會占用更少的空間,通常用于資源有限的平臺,如Mobile。
LITE_RUNTIME: 生成的代碼執行效率高,同時生成代碼編譯后的所占用的空間也是非常少。這是以犧牲Protocol Buffer提供的反射功能為代價的。因此我們在C++中鏈接Protocol Buffer庫時僅需鏈接libprotobuf-lite,而非libprotobuf。在Java中僅需包含protobuf-java-2.4.1-lite.jar,而非protobuf-java-2.4.1.jar。
注:對于LITE_MESSAGE選項而言,其生成的代碼均將繼承自MessageLite,而非Message。
4. [pack?= true]: 因為歷史原因,對于數值型的repeated字段,如int32、int64等,在編碼時并沒有得到很好的優化,然而在新近版本的Protocol Buffer中,可通過添加[pack=true]的字段選項,以通知Protocol Buffer在為該類型的消息對象編碼時更加高效。如:
repeated int32 samples = 4 [packed=true]。
注:該選項僅適用于2.3.0以上的Protocol Buffer。
5. [default?= default_value]: optional類型的字段,如果在序列化時沒有被設置,或者是老版本的消息中根本不存在該字段,那么在反序列化該類型的消息是,optional的字段將被賦予類型相關的缺省值,如bool被設置為false,int32被設置為0。Protocol Buffer也支持自定義的缺省值,如:
optional int32 result_per_page = 3 [default = 10]。
從InputStream反序列化protobuf對象非常容易,如下例所示。生成的Java代碼提供一些用于合并字節數組,byteBuffer和InputStream對象的函數。
public static TaskProtos.Task readBrotoBufFromStream(InputStream inputStream)
throws IOException {
TaskProtos.Task task = TaskProtos.Task.newBuilder()
.mergeFrom(inputStream).build();
Log.d("ProtobufDemo", "Read Task from stream: "
+ task.getName() + ", "
+ new Date(task.getCreated()) + ", "
+ (task.hasOwner() ?
task.getOwner().getName() : "no owner") + ", "
+ task.getStatus().name() + ", "
+ task.getPriority()
+ task.getCommentsCount() + " comments.");
return task;
}
本例顯示了如何檢索protobuf對象的值。注意:protobuf對象是不可變的。修改它們唯一的方法是從現有對象創建一個新的構建器,設置新的值,并生成一個取代原有對象的Task。這使得protobuf有點不好用,但它強制開發者在持久化復雜對象時使用更好的設計。
下面的方法顯示了如何構建一個新的protobuf對象。首先為構造的對象創建一個新的Builder,然后設置所需要的值并調用Builder.build()方法來創建不可變的protobuf對象。
public static TaskProtos.Task buildTask(String name, Date created,
String ownerName, String ownerEmail,
String ownerPhone,
TaskProtos.Task.Status status,
int priority,
List
TaskProtos.Task.Builder builder = TaskProtos.Task.newBuilder();
builder.setName(name);
builder.setCreated(created.getTime());
builder.setPriority(priority);
builder.setStatus(status);
if(ownerName != null) {
TaskProtos.Task.Owner.Builder ownerBuilder
= TaskProtos.Task.Owner.newBuilder();
ownerBuilder.setName(ownerName);
if(ownerEmail != null) {
ownerBuilder.setEmail(ownerEmail);
}
if(ownerPhone != null) {
ownerBuilder.setPhone(ownerPhone);
}
builder.setOwner(ownerBuilder);
}
if (comments != null) {
builder.addAllComments(comments);
}
return builder.build();
}
API提供了一系列方法用來把protobuf對象寫到文件或者網絡流中。下面的代碼演示了如何把Task對象序列化到OutputStream中。
public static void writeTaskToStream(TaskProtos.Task task,
OutputStream outputStream)
throws IOException {
task.writeTo(outputStream);
}
protobuf主要的優點是它比JSON消耗的內存少,而且讀寫速度更快。protobuf對象還是不可變的,如果要確保對象的值在整個生命周期中保持不變,該特性會非常有用。
Protocol Buffers 2.6.1 full source:?protobuf-2.6.1.tar.gz?(MD5: f3916ce13b7fcb3072a1fa8cf02b2423)
Protocol Compiler 2.6.1 binary for windows:?protoc-2.6.1-win32.zip?(MD5: b057f86ef83835010bb227eb2d82de04)
C++ Java
版權聲明:本文內容由網絡用戶投稿,版權歸原作者所有,本站不擁有其著作權,亦不承擔相應法律責任。如果您發現本站中有涉嫌抄襲或描述失實的內容,請聯系我們jiasou666@gmail.com 處理,核實后本網站將在24小時內刪除侵權內容。
版權聲明:本文內容由網絡用戶投稿,版權歸原作者所有,本站不擁有其著作權,亦不承擔相應法律責任。如果您發現本站中有涉嫌抄襲或描述失實的內容,請聯系我們jiasou666@gmail.com 處理,核實后本網站將在24小時內刪除侵權內容。