Protobuf語言指南——.proto檔案語法詳解
Protobuf語言指南
l 定義一個訊息(message)型別
l 標量值型別
l Optional 的欄位及預設值
l 列舉
l 使用其他訊息型別
l 巢狀型別
l 更新一個訊息型別
l 擴充套件
l 包(package)
l 定義服務(service)
l 選項(option)
l 生成訪問類
本指南描述了怎樣使用protocolbuffer語言來構造你的protocol buffer資料,包括.proto檔案語法以及怎樣生成.proto檔案的資料訪問類。
l 定義一個訊息型別
先來看一個非常簡單的例子。假設你想定義一個“搜尋請求”的訊息格式,每一個請求含有一個查詢字串、你感興趣的查詢結果所在的頁數,以及每一頁多少條查詢結果。可以採用如下的方式來定義訊息型別的.proto檔案了:
message SearchRequest { required string query = 1; optional int32 page_number = 2; optional int32 result_per_page = 3; } |
SearchRequest訊息格式有3個欄位,在訊息中承載的資料分別對應於每一個欄位。其中每個欄位都有一個名字和一種型別。
Ø 指定欄位型別
在上面的例子中,所有欄位都是標量型別:兩個整型(page_number和result_per_page),一個string型別(query)。當然,你也可以為欄位指定其他的合成型別,包括列舉(
Ø 分配標識號
正如上述檔案格式,在訊息定義中,每個欄位都有唯一的一個識別符號。這些識別符號是用來在訊息的二進位制格式中識別各個欄位的,一旦開始使用就不能夠再改 變。注:[1,15]之內的標識號在編碼的時候會佔用一個位元組。[16,2047]之內的標識號則佔用2個位元組。所以應該為那些頻繁出現的訊息元素保留 [1,15]之內的標識號。切記:要為將來有可能新增的、頻繁出現的標識號預留一些標識號。
最小的標識號可以從1開始,最大到229 - 1, or 536,870,911。不可以使用其中的[19000-19999]的標識號, Protobuf協議實現中對這些進行了預留。如果非要在.proto檔案中使用這些預留標識號,編譯時就會報警。
Ø 指定欄位規則
所指定的訊息欄位修飾符必須是如下之一:
² required:一個格式良好的訊息一定要含有1個這種欄位。表示該值是必須要設定的;
² optional:訊息格式中該欄位可以有0個或1個值(不超過1個)。
² repeated:在一個格式良好的訊息中,這種欄位可以重複任意多次(包括0次)。重複的值的順序會被保留。表示該值可以重複,相當於java中的List。
由於一些歷史原因,基本數值型別的repeated的欄位並沒有被儘可能地高效編碼。在新的程式碼中,使用者應該使用特殊選項[packed=true]來保證更高效的編碼。如:
repeated int32 samples = 4 [packed=true]; |
required是永久性的:在將一個欄位標識為required的時候,應該特別小心。如果在某些情況下不想寫入或者傳送一個required的 欄位,將原始該欄位修飾符更改為optional可能會遇到問題——舊版本的使用者會認為不含該欄位的訊息是不完整的,從而可能會無目的的拒絕解析。在這 種情況下,你應該考慮編寫特別針對於應用程式的、自定義的訊息校驗函式。Google的一些工程師得出了一個結論:使用required弊多於利;他們更 願意使用optional和repeated而不是required。當然,這個觀點並不具有普遍性。
Ø 新增更多訊息型別
在一個.proto檔案中可以定義多個訊息型別。在定義多個相關的訊息的時候,這一點特別有用——例如,如果想定義與SearchResponse訊息型別對應的回覆訊息格式的話,你可以將它新增到相同的.proto檔案中,如:
message SearchRequest { required string query = 1; optional int32 page_number = 2; optional int32 result_per_page = 3; } message SearchResponse { … } |
Ø 添加註釋
向.proto檔案添加註釋,可以使用C/C++/java風格的雙斜槓(//) 語法格式,如:
message SearchRequest { required string query = 1; optional int32 page_number = 2;// 最終返回的頁數 optional int32 result_per_page = 3;// 每頁返回的結果數 } |
Ø 從.proto檔案生成了什麼?
當用protocolbuffer編譯器來執行.proto檔案時,編譯器將生成所選擇語言的程式碼,這些程式碼可以操作在.proto檔案中定義的訊息型別,包括獲取、設定欄位值,將訊息序列化到一個輸出流中,以及從一個輸入流中解析訊息。
² 對C++來說,編譯器會為每個.proto檔案生成一個.h檔案和一個.cc檔案,.proto檔案中的每一個訊息有一個對應的類。
² 對Java來說,編譯器為每一個訊息型別生成了一個.java檔案,以及一個特殊的Builder類(該類是用來建立訊息類介面的)。
² 對Python來說,有點不太一樣——Python編譯器為.proto檔案中的每個訊息型別生成一個含有靜態描述符的模組,,該模組與一個元類(metaclass)在執行時(runtime)被用來建立所需的Python資料訪問類。
l 標量數值型別
一個標量訊息欄位可以含有一個如下的型別——該表格展示了定義於.proto檔案中的型別,以及與之對應的、在自動生成的訪問類中定義的型別:
.proto型別 |
Java 型別 |
C++型別 |
備註 |
double |
double |
double |
|
float |
float |
float |
|
int32 |
int |
int32 |
使用可變長編碼方式。編碼負數時不夠高效——如果你的欄位可能含有負數,那麼請使用sint32。 |
int64 |
long |
int64 |
使用可變長編碼方式。編碼負數時不夠高效——如果你的欄位可能含有負數,那麼請使用sint64。 |
uint32 |
int[1] |
uint32 |
Uses variable-length encoding. |
uint64 |
long[1] | uint64 | Uses variable-length encoding. |
sint32 |
int |
int32 |
使用可變長編碼方式。有符號的整型值。編碼時比通常的int32高效。 |
sint64 |
long |
int64 |
使用可變長編碼方式。有符號的整型值。編碼時比通常的int64高效。 |
fixed32 |
int[1] |
uint32 |
總是4個位元組。如果數值總是比總是比228大的話,這個型別會比uint32高效。 |
fixed64 |
long[1] |
uint64 |
總是8個位元組。如果數值總是比總是比256大的話,這個型別會比uint64高效。 |
sfixed32 |
int |
int32 |
總是4個位元組。 |
sfixed64 |
long |
int64 |
總是8個位元組。 |
bool |
boolean |
bool |
|
string |
String |
string |
一個字串必須是UTF-8編碼或者7-bit ASCII編碼的文字。 |
bytes |
ByteString |
string |
可能包含任意順序的位元組資料。 |
l Optional的欄位和預設值
如上所述,訊息描述中的一個元素可以被標記為“可選的”(optional)。一個格式良好的訊息可以包含0個或一個optional的元素。當解 析訊息時,如果它不包含optional的元素值,那麼解析出來的物件中的對應欄位就被置為預設值。預設值可以在訊息描述檔案中指定。例如,要為 SearchRequest訊息的result_per_page欄位指定預設值10,在定義訊息格式時如下所示:
optional int32 result_per_page = 3 [default = 10]; |
如果沒有為optional的元素指定預設值,就會使用與特定型別相關的預設值:對string來說,預設值是空字串。對bool來說,預設值是false。對數值型別來說,預設值是0。對列舉來說,預設值是列舉型別定義中的第一個值。
l 列舉
當需要定義一個訊息型別的時候,可能想為一個欄位指定某“預定義值序列”中的一個值。例如,假設要為每一個SearchRequest訊息新增一個 corpus欄位,而corpus的值可能是UNIVERSAL,WEB,IMAGES,LOCAL,NEWS,PRODUCTS或VIDEO中的一個。 其實可以很容易地實現這一點:通過向訊息定義中新增一個列舉(enum)就可以了。一個enum型別的欄位只能用指定的常量集中的一個值作為其值(如果嘗 試指定不同的值,解析器就會把它當作一個未知的欄位來對待)。在下面的例子中,在訊息格式中添加了一個叫做Corpus的列舉型別——它含有所有可能的值 ——以及一個型別為Corpus的欄位:
message SearchRequest { required string query = 1; optional int32 page_number = 2; optional int32 result_per_page = 3 [default = 10]; enum Corpus { UNIVERSAL = 0; WEB = 1; IMAGES = 2; LOCAL = 3; NEWS = 4; PRODUCTS = 5; VIDEO = 6; } optional Corpus corpus = 4 [default = UNIVERSAL]; } |
列舉常量必須在32位整型值的範圍內。因為enum值是使用可變編碼方式的,對負數不夠高效,因此不推薦在enum中使用負數。如上例所示,可以在 一個訊息定義的內部或外部定義列舉——這些列舉可以在.proto檔案中的任何訊息定義裡重用。當然也可以在一個訊息中宣告一個列舉型別,而在另一個不同 的訊息中使用它——採用MessageType.EnumType的語法格式。
當對一個使用了列舉的.proto檔案執行protocol buffer編譯器的時候,生成的程式碼中將有一個對應的enum(對Java或C++來說),或者一個特殊的EnumDescriptor類(對 Python來說),它被用來在執行時生成的類中建立一系列的整型值符號常量(symbolic constants)。
關於如何在你的應用程式的訊息中使用列舉的更多資訊,請檢視所選擇的語言http://code.google.com/intl/zh-CN/apis/protocolbuffers/docs/reference/overview.html。
l 使用其他訊息型別
你可以將其他訊息型別用作欄位型別。例如,假設在每一個SearchResponse訊息中包含Result訊息,此時可以在相同的.proto檔案中定義一個Result訊息型別,然後在SearchResponse訊息中指定一個Result型別的欄位,如:
message SearchResponse { repeated Result result = 1; } message Result { required string url = 1; optional string title = 2; repeated string snippets = 3; } |
Ø 匯入定義
在上面的例子中,Result訊息型別與SearchResponse是定義在同一檔案中的。如果想要使用的訊息型別已經在其他.proto檔案中已經定義過了呢?
你可以通過匯入(importing)其他.proto檔案中的定義來使用它們。要匯入其他.proto檔案的定義,你需要在你的檔案中新增一個匯入宣告,如:
import "myproject/other_protos.proto"; |
protocol編譯器就會在一系列目錄中查詢需要被匯入的檔案,這些目錄通過protocol編譯器的命令列引數-I/–import_path指定。如果不提供引數,編譯器就在其呼叫目錄下查詢。
l 巢狀型別
你可以在其他訊息型別中定義、使用訊息型別,在下面的例子中,Result訊息就定義在SearchResponse訊息內,如:
message SearchResponse { message Result { required string url = 1; optional string title = 2; repeated string snippets = 3; } repeated Result result = 1; } |
如果你想在它的父訊息型別的外部重用這個訊息型別,你需要以Parent.Type的形式使用它,如:
message SomeOtherMessage { optional SearchResponse.Result result = 1; } |
當然,你也可以將訊息巢狀任意多層,如:
message Outer { // Level 0 message MiddleAA { // Level 1 message Inner { // Level 2 required int64 ival = 1; optional bool booly = 2; } } message MiddleBB { // Level 1 message Inner { // Level 2 required int32 ival = 1; optional bool booly = 2; } } } |
Ø 組
注:該特性已被棄用,在建立新的訊息型別的時候,不應該再使用它——可以使用巢狀訊息型別來代替它。
“組”是指在訊息定義中巢狀資訊的另一種方法。比如,在SearchResponse中包含若干Result的另一種方法是 :
message SearchResponse { repeated group Result = 1 { required string url = 2; optional string title = 3; repeated string snippets = 4; } } |
一個“組”只是簡單地將一個巢狀訊息型別和一個欄位捆綁到一個單獨的宣告中。在程式碼中,可以把它看成是含有一個Result型別、名叫result的欄位的訊息(後面的名字被轉換成了小寫,所以它不會與前面的衝突)。
因此,除了資料傳輸格式不同之外,這個例子與上面的SearchResponse例子是完全等價的。
l 更新一個訊息型別
如果一個已有的訊息格式已無法滿足新的需求——如,要在訊息中新增一個額外的欄位——但是同時舊版本寫的程式碼仍然可用。不用擔心!更新訊息而不破壞已有程式碼是非常簡單的。在更新時只要記住以下的規則即可。
² 不要更改任何已有的欄位的數值標識。
² 所新增的任何欄位都必須是optional或repeated的。這就意味著任何使用“舊”的訊息格式的程式碼序列化的訊息可以被新的程式碼所解析,因為它們 不會丟掉任何required的元素。應該為這些元素設定合理的預設值,這樣新的程式碼就能夠正確地與老程式碼生成的訊息互動了。類似地,新的程式碼建立的訊息 也能被老的程式碼解析:老的二進位制程式在解析的時候只是簡單地將新欄位忽略。然而,未知的欄位是沒有被拋棄的。此後,如果訊息被序列化,未知的欄位會隨之一 起被序列化——所以,如果訊息傳到了新程式碼那裡,則新的欄位仍然可用。注意:對Python來說,對未知欄位的保留策略是無效的。
² 非required的欄位可以移除——只要它們的標識號在新的訊息型別中不再使用(更好的做法可能是重新命名那個欄位,例如在欄位前新增“OBSOLETE_”字首,那樣的話,使用的.proto檔案的使用者將來就不會無意中重新使用了那些不該使用的標識號)。
² 一個非required的欄位可以轉換為一個擴充套件,反之亦然——只要它的型別和標識號保持不變。
² int32, uint32, int64, uint64,和bool是全部相容的,這意味著可以將這些型別中的一個轉換為另外一個,而不會破壞向前、 向後的相容性。如果解析出來的數字與對應的型別不相符,那麼結果就像在C++中對它進行了強制型別轉換一樣(例如,如果把一個64位數字當作int32來 讀取,那麼它就會被截斷為32位的數字)。
² sint32和sint64是互相相容的,但是它們與其他整數型別不相容。
² string和bytes是相容的——只要bytes是有效的UTF-8編碼。
² 巢狀訊息與bytes是相容的——只要bytes包含該訊息的一個編碼過的版本。
² fixed32與sfixed32是相容的,fixed64與sfixed64是相容的。
l 擴充套件
通過擴充套件,可以將一個範圍內的欄位標識號宣告為可被第三方擴充套件所用。然後,其他人就可以在他們自己的.proto檔案中為該訊息型別宣告新的欄位,而不必去編輯原始檔案了。看個具體例子:
message Foo { // … extensions 100 to 199; } |
這個例子表明:在訊息Foo中,範圍[100,199]之內的欄位標識號被保留為擴充套件用。現在,其他人就可以在他們自己的.proto檔案中新增新欄位到Foo裡了,但是新增的欄位標識號要在指定的範圍內——例如:
extend Foo { optional int32 bar = 126; } |
這個例子表明:訊息Foo現在有一個名為bar的optional int32欄位。
當用戶的Foo訊息被編碼的時候,資料的傳輸格式與使用者在Foo裡定義新欄位的效果是完全一樣的。
然而,要在程式程式碼中訪問擴充套件欄位的方法與訪問普通的欄位稍有不同——生成的資料訪問程式碼為擴充套件準備了特殊的訪問函式來訪問它。例如,下面是如何在C++中設定bar的值:
Foo foo; |
類似地,Foo類也定義了模板函式 HasExtension(),ClearExtension(),GetExtension(),MutableExtension(),以及 AddExtension()。這些函式的語義都與對應的普通欄位的訪問函式相符。要檢視更多使用擴充套件的資訊,請參考相應語言的程式碼生成指南。注:擴充套件可 以是任何欄位型別,包括訊息型別。
l 巢狀的擴充套件
可以在另一個型別的範圍內宣告擴充套件,如:
message Baz { extend Foo { optional int32 bar = 126; } … } |
在此例中,訪問此擴充套件的C++程式碼如下:
Foo foo; foo.SetExtension(Baz::bar, 15); |
一個通常的設計模式就是:在擴充套件的欄位型別的範圍內定義該擴充套件——例如,下面是一個Foo的擴充套件(該擴充套件是Baz型別的),其中,擴充套件被定義為了Baz的一部分:
message Baz { extend Foo { optional Baz foo_ext = 127; } … } |
然而,並沒有強制要求一個訊息型別的擴充套件一定要定義在那個訊息中。也可以這樣做:
message Baz { … } extend Foo { optional Baz foo_baz_ext = 127; } |
事實上,這種語法格式更能防止引起混淆。正如上面所提到的,巢狀的語法通常被錯誤地認為有子類化的關係——尤其是對那些還不熟悉擴充套件的使用者來說。
Ø 選擇可擴充套件的標符號
在同一個訊息型別中一定要確保兩個使用者不會擴充套件新增相同的標識號,否則可能會導致資料的不一致。可以通過為新專案定義一個可擴充套件標識號規則來防止該情況的發生。
如果標識號需要很大的數量時,可以將該可擴充套件標符號的範圍擴大至max,其中max是229 - 1, 或536,870,911。如下所示:
message Foo { extensions 1000 to max; } |
通常情況下在選擇標符號時,標識號產生的規則中應該避開[19000-19999]之間的數字,因為這些已經被Protocol Buffers實現中預留了。
l 包(Package)
當然可以為.proto檔案新增一個可選的package宣告符,用來防止不同的訊息型別有命名衝突。如:
package foo.bar; message Open { ... } |
在其他的訊息格式定義中可以使用包名+訊息名的方式來定義域的型別,如:
message Foo { ... required foo.bar.Open open = 1; ... } |
包的宣告符會根據使用語言的不同影響生成的程式碼。對於C++,產生的類會被包裝在C++的名稱空間中,如上例中的Open會被封裝在 foo::bar空間中;對於Java,包宣告符會變為java的一個包,除非在.proto檔案中提供了一個明確有java_package;對於 Python,這個包宣告符是被忽略的,因為Python模組是按照其在檔案系統中的位置進行組織的。
Ø 包及名稱的解析
Protocol buffer語言中型別名稱的解析與C++是一致的:首先從最內部開始查詢,依次向外進行,每個包會被看作是其父類包的內部類。當然對於 (foo.bar.Baz)這樣以“.”分隔的意味著是從最外圍開始的。ProtocolBuffer編譯器會解析.proto檔案中定義的所有型別名。 對於不同語言的程式碼生成器會知道如何來指向每個具體的型別,即使它們使用了不同的規則。
l 定義服務(Service)
如果想要將訊息型別用在RPC(遠端方法呼叫)系統中,可以在.proto檔案中定義一個RPC服務介面,protocol buffer編譯器將會根據所選擇的不同語言生成服務介面程式碼及存根。如,想要定義一個RPC服務並具有一個方法,該方法能夠接收 SearchRequest並返回一個SearchResponse,此時可以在.proto檔案中進行如下定義:
service SearchService { rpc Search (SearchRequest) returns (SearchResponse); } |
protocol編譯器將產生一個抽象介面SearchService以及一個相應的存根實現。存根將所有的呼叫指向RpcChannel,它是一 個抽象介面,必須在RPC系統中對該介面進行實現。如,可以實現RpcChannel以完成序列化訊息並通過HTTP方式來發送到一個伺服器。換句話說, 產生的存根提供了一個型別安全的介面用來完成基於protocolbuffer的RPC呼叫,而不是將你限定在一個特定的RPC的實現中。C++中的程式碼 如下所示:
using google::protobuf;
protobuf::RpcChannel* channel;
void DoSearch() {
// The protocol compiler generates the SearchService class based on the
service = new SearchService::Stub(channel);
// Execute the RPC.
void Done() { |
所有service類都必須實現Service介面,它提供了一種用來呼叫具體方法的方式,即在編譯期不需要知道方法名及它的輸入、輸出型別。在伺服器端,通過服務註冊它可以被用來實現一個RPC Server。
using google::protobuf; |
l 選項(Options)
在定義.proto檔案時能夠標註一系列的options。Options並不改變整個檔案宣告的含義,但卻能夠影響特定環境下處理方式。完整的可用選項可以在google/protobuf/descriptor.proto
找到。
一些選項是檔案級別的,意味著它可以作用於最外範圍,不包含在任何訊息內部、enum或服務定義中。一些選項是訊息級別的,意味著它可以用在訊息定 義的內部。當然有些選項可以作用在域、enum型別、enum值、服務型別及服務方法中。到目前為止,並沒有一種有效的選項能作用於所有的型別。
相關推薦
Protobuf語言指南——.proto檔案語法詳解
Protobuf語言指南 l 定義一個訊息(message)型別 l 標量值型別 l Optional 的欄位及預設值 l 列舉 l 使用其他訊息型別 l 巢狀型別 l 更新一個訊息型別 l 擴充套件 l 包(package) l
Android.mk檔案語法詳解
原文地址為:Android.mk 檔案語法詳解 轉:http://blog.sina.com.cn/s/blog_602f8770010148ce.html ===============================================================
Android.mk 檔案語法詳解
===================================================================================== 0. Android.mk簡介: Android.mk檔案用來告知NDK Build 系統關於Sou
安卓Android.mk 檔案語法詳解
0. Android.mk簡介: Android.mk檔案用來告知NDK Build 系統關於Source的資訊。 Android.mk將是GNU Makefile的一部分,且將被Build System解析一次或多次。 所以,請儘量少的在Android.mk中宣告
NDK 編譯和使用靜態庫、動態庫; Android.mk 檔案語法詳解; Android.mk高階寫法
===================================================================================== 0. Android.mk簡介: Android.mk檔案用來告知NDK Build 系統關於Source的資訊。 Andro
Android.mk入門到精通(001)——Android.mk 檔案語法詳解:神文
https://www.cnblogs.com/wainiwann/p/3837936.html 0. Android.mk簡介: Android.mk檔案用來告知NDK Build 系統關於Source的資訊。 Android.mk將是GNU Makefile的一部分,
android init.rc檔案語法詳解
初始化語言包含了四種類型的宣告: Actions(行動)、 Commands(命令)、Services(服務)和Options(選項)。 基本語法規定 1 所有型別的語句都是基於行的,一個語句包含若干個tokens,token之間通過空格字元分隔. 如果一個token中需
C語言檔案操作詳解
* 檔案狀態檢查 A. 檔案結束 (1) 函式原型 int feof(FILE *fp) (2) 功能說明 該函式用來判斷檔案是否結束。 (3) 引數說明 fp:檔案指標。 (4) 返回值 0:假值,表示檔案未結束。 1:真值,表示檔案結束。
C語言指標、連結串列與檔案操作詳解
用兩個函式 Load_LinkList() 和 Save_LinkList() 讓連結串列與檔案操作結合,除了列印函式,其他都是在記憶體中操作連結串列,這樣寫更有條理,在建立連結串列時沒有采用書上的用一箇中間變數引導,並插入到結點前面,而是直接在連結串列尾的next申請記憶
C語言,檔案操作詳解
在 C 語言中,檔案操作的函式大多包含在 標頭檔案中,使用時記得 #include。 一、開啟和關閉檔案 1. 開啟檔案 FILE * fopen ( const char * filename, const char * mode ); 開啟一個檔案,成功則返回
008-Hadoop Hive sql語法詳解3-DML 操作:元數據存儲
pan 查詢 寫入 所有 not insert語句 int 寫入文件 文件系統 一、概述 hive不支持用insert語句一條一條的進行插入操作,也不支持update操作。數據是以load的方式加載到建立好的表中。數據一旦導入就不可以修改。 DML包括:INSERT插入
Oracle create tablespace 創建表空間語法詳解
系統回滾段 語法 判斷 臨時 extent 數值 off offline 文件的 CREATE [UNDO] TABLESPACE tablespace_name [DATAFILE datefile_spec1 [,datefile_spec2] ..
mysql-5.7.9 shutdown 語法詳解
resp 登錄 ive conn denied 權限不足 這樣的 fec comm mysql-5.7.9 終於提供shutdown 語法啦: 之前如果想關閉一個mysql數據庫可以通過kill 命令、mysqladmin shutdown 、service mysql
[持續交付實踐] pipeline:pipeline 使用之語法詳解
安裝工具 詳細 href 3.0 def 實現 能夠 action roo 一、引言 jenkins pipeline語法的發展如此之快用日新月異來形容也不為過,而目前國內對jenkins pipeline關註的人還非常少,相關的文章更是稀少,唯一看到w3c有篇相關的估計是
Nginx Rewrite語法詳解
服務器端 .html use rgs args port 資源 如果 urn 重寫中用到的指令 if (條件) {} 設定條件,再進行重寫 set #設置變量 return #返回狀態碼 break #跳出rewrite rewrite #重寫 If 語法格式 If
017-Hadoop Hive sql語法詳解7-去重排序、數據傾斜
col 去重排序 sel cluster 可能 更多 分發 指定 clust 一、數據去重排序 1.1、去重 distinct與group by 盡量避免使用distinct進行排重,特別是大表操作,用group by代替 -- 不建議 selec
016-Hadoop Hive sql語法詳解6-job輸入輸出優化、數據剪裁、減少job數、動態分區
分享 hive table 取數 nbsp put union 正在 style 一、job輸入輸出優化 善用muti-insert、union all,不同表的union all相當於multiple inputs,同一個表的union all,相當map一次輸出多條
(4)Smali系列學習之Smali語法詳解內部類
這一 數字 學習 get 私有方法 如果 單獨 hello 我們 在這一節,我們來介紹一下內部類。對於Java文件中的每一個內部類,都會產生一個單獨的smali文件,比如ActivityThread$1.smali。這些文件的命名規範是如果是匿名內部類,則命名規則是外部類+
Shodan的http.favicon.hash語法詳解
簡單 tar 地址 字段 就是 mpi 舉例 數值 sts 在Shodan搜索中有一個關於網站icon圖標的搜索語法,http.favicon.hash,我們可以使用這個語法來搜索出使用了同一icon圖標的網站,不知道怎麽用的朋友請參考我上一篇文章。 通過上一篇文章
Android.mk 文件語法詳解
too itl 其他 國內 fine 鏈接 sina 流程詳解 img Android.mk 文件語法詳解 轉:http://blog.sina.com.cn/s/blog_602f8770010148ce.html =========================