【翻譯】Gob 的資料

來源:互聯網
上載者:User
這是一個建立於 的文章,其中的資訊可能已經有所發展或是發生改變。

原文在此:http://blog.golang.org/2011/03/gobs-of-data.html,來自 Golang 官方部落格。

Gob 是 Golang 的包中帶的一個資料結構序列化的編/解碼工具。在實際應用中,已經有不少的編解碼工具/包/庫了,為什麼 Golang 還要新開發一個 Gob?又是一個重複的輪子?Gob 做了哪些工作?Gob 的優勢是什嗎?本文做了一個較為全面的解釋。

—————-翻譯分割線—————-

Gob 的資料

為了讓某個資料結構能夠在網路上傳輸或能夠儲存至檔案,它必須被編碼然後再解碼。當然,已經有許多可用的編碼方式了:JSON,XML,Google 的 protocol buffers,等等。而現在,又多了一種,由 Go 的 gob 包提供的方式。

為什麼定義新的編碼?這要做許多繁重的工作。為什麼不使用某個現成的格式?呃,無論如何,我們這樣做了!Go 已經有剛才提到的所有編碼方式的包(protocol buffer 包在另外一個程式碼程式庫中,但它是下載得最多的包之一)。並且在許多情況下,包括同其他語言編寫的工具和系統通訊,這些都是正確的選擇。

但是在特定的 Go 環境中,例如在兩個 Go 編寫的服務之間通訊,這需要某些東西使得其更加容易使用,並且可能更加有效率。

Gobs 協同 Go 語言的工作方式,對於那些外部定義的、同語言無關的編碼方式來說無法做到。同時,從現有的系統中也吸取了很多教訓。

目標

gob 包有在設計時有許多目標。

首先,也是最顯然的,它被設計成為非常容易使用的。一方面,由於 Go 有反射(reflection),就沒有必要弄一個單獨的介面定義語言或“協議編譯器”。資料結構本身提供了編碼和解碼所需要的全部資訊。 另一方面,這種方法也意味著 gob 永遠無法良好的同其他語言協同工作,但這沒問題:gob 是厚顏無恥的以 Go 為中心(譯註:呃,以XXX為中心,堅決貫徹XXX的領導……)。

效率也是非常重要的。基於文本形式的,如 XML 和 JSON ,應用於高效通訊網路會太慢了。二進位編碼是必須的(譯註:二進位神馬的,是必須的!迴音:必須的……)!

Gob 流必須可以自解釋。每個 gob 流,從開始讀取,整個流將由包含足夠的資訊,以便在終端對其內容毫不知情的前提下對整個流加以解析。這一特性意味,即便你忘記了儲存在檔案中的 gob 流表示什麼,也總是可以對其解碼。

同樣,這裡有一些從 Google protocol buffers 獲得的經驗。

Protocol buffer 的硬傷

Protocol buffers 對 gob 的設計產生了主要的影響,但是有三個特性被謹慎的避開了。(暫且不說 protocol buffer 不是自解釋的:如果你不知道 protocol buffer 編碼時的資料的定義,你就無法解析它。)

首先,protocol buffer 僅工作於 Go 的 struct 資料類型。不能在最頂級編碼一個整數或者數組,只可以將其至於 struct 中作為一個欄位。 至少在 Go 中,這個限制似乎沒有什麼意義。如果你希望傳輸的僅僅是一個數組或者整數,為什麼你要先將其放到 struct 中?

其次,可能 protocol buffer 的定義指定欄位 T.x 和 T.y 需要解析,無論是在編碼還是解碼類型 T 的值。雖然,這樣的必須欄位看起來是個好主意,但是由於轉碼器中必須含有用於編碼和解碼的特定的資料結構,用於報告必須欄位是否丟失,實現的開銷是大的。這同樣也產生了問題。過一段時間後,某人可能希望修改資料定義,移除了必須的欄位,但這導致現有接收資料的用戶端崩潰。最好是在編碼時就根本沒有這些欄位。(Protocol buffer 也有可選欄位。但是,如果我們沒有必須欄位,所有的欄位就是可選的。等一下還會針對可選欄位進行一些討論。)

第三個 protocol buffer 的硬傷是預設值。當 protocol buffer 在某個“預設”欄位上設定了預設值,而解碼後的結構就像那個欄位被設定了某個值一樣。這個想法在有 getter 和 setter 控制欄位的訪問的時候非常棒,但是當容器是一個原始結構的時候就很難控制其保持清晰了。必須的欄位也存在同樣的麻煩:在哪定義預設值,它們的類型是什麼(是UTF-8文本?無符號位元組串?在浮點型中有幾位?)儘管有許多看起來很簡單,protocol buffer 的設計和實現還是有許多伴隨的問題。我們決定讓這些都遠離 gob,並且回到我們的 Go 旅程中,一個很有效率的預設規則:除非你設定了一些內容,否則就是那個類型的“零值”,而這個不需要被傳輸。

所以 gob 最終看起來是個更加通用、簡單的 protocol buffer。它又是如何工作的呢?

編碼後的 gob 資料不是 int8 或者 uint16 的串。作為代替,其看起來更象是 Go 的常量,不論是有符號的還是無符號的整數值是虛擬、無大小定義的數字。當你編碼一個 int8 的時候,其值被轉換為一個無大小定義的變長整數。當你對 int64 編碼時,其值也是轉換為一個無大小定義的變長整數。(有符號和無符號是相同處理區別對待的,但是無大小定義也適用於無符號值。)如果都是值 7,線上傳輸的位是一致的。當接收者解碼其值,它將其放入接收者變數中,可能是任意的一個整數類型。因此,編碼方發送了一個來自 int8 的 7,而接收方可能將其儲存在 int64 中。這沒有問題:這個值永遠匹配於一個整數。(如果不匹配,會產生錯誤。)在變數的大小上解偶,為編碼提供了一些靈活性:我們可以隨著軟體演化擴充整數類型,但是仍然可以解碼舊的資料。

這種靈活性對於指標同樣有效。在傳輸前,所有指標都進行整理。int8、*int8、**int8、****int8等等的值,被傳輸為可能被儲存於任何大小的 int,或者 *int,或者 ******int等等的整數值。這同樣是一種靈活性。

同樣的原因,在解碼一個 struct,當其欄位由編碼方發送,儲存於目標方的時候,也體現出這種靈活性。給出這樣一個值

type T struct { X, Y, Z int } // 只有匯出欄位(exported fields)被編碼和解碼。var t = T{X: 7, Y: 0, Z: 8}

編碼後僅發送 7 和 8。由於為零,Y 不會被發送;沒有必要發送一個零值。

接收方可能用下面的結構解碼:

type U struct { X, Y *int8 } // 注意:int8 的指標var u U

而獲得的 u 的值只有 X (值為 7 的 int8 變數的地址);Z 欄位被忽略了——你應將其放到哪裡呢?當解碼一個 struct 的時候,欄位會匹配其名字和類型,只有雙方都有的欄位會生效。這個簡單的辦法巧妙處理了“可選欄位”問題:類型 T 添加了欄位,到期的接收者仍然能處理它們知道的那部分。因此 gob 在可選欄位上提供了重要的特性——無須任何額外的機制或標識。

從整數串可以構造其他類型:位元組數組、字串、數組、記憶體片段、Map,甚至浮點數組。IEEE 754 浮點位定義描述了浮點值儲存為整數,在你知道其類型的時候,這會工作得很好,我們總是知道類型的吧。另外,這裡的整數使用位元組翻轉的順序發送,因為一般的浮點數字,就像是小整數數組,在低位上有許多個零是不用傳遞的。

gob 還有一個非常棒的特性是 Go 使得通過 GobEncoder 和 GobDecoder 介面使得自訂類型的編碼成為可能,從某個意義上說類似於 JSON 包的 Marshaler 和 Unmarshaler,以及 fmt 包的 String 化介面。這個技巧使一些特殊功能成為可能,強制使用常量,或者傳輸資料的時候隱藏資訊。閱讀文檔瞭解更多細節。

類型的傳輸

在第一次傳輸給定類型的時候,gob 包中包含了這個類型的描述。實際上,是這樣的,編碼器編碼的是gob標準格式,而內部的 struct 則帶有類型描述並給其標識一個唯一編號。(基本類型、類型描述結構的層級,在軟體啟動時已經定義好了。)在類型被描述後,它可以通過編號來引用。

因此,當我們發送類型 T 時,gob 編碼器發送 T 的描述,並對其編號,例如 127。包括第一個資料包在內的所有的資料,都使用這個編號,所以 T 值的資料流看起來是這樣:

("define type id" 127, definition of type T)(127, T value)(127, T value), ...

類型編號使得描述遞迴類型,以及發送這些類型的資料成為可能。因此,gob 可以對樹狀類型做編碼:

type Node struct {Value intLeft, Right *Node}

(這是一個讓讀者實踐零預設值規則是如何工作的練習,儘管 gob 不會處理指標。)

帶有了類型資訊,gob 流就完全自說明了。除了那些初始類型,它們已經在開始的時候就定義好了。

編譯機

在第一次傳輸給定類型的時候,gob 包會構造一個針對這個類型的小翻譯機。在這個類型上使用了反射來構造這個翻譯機,但是一旦翻譯機構建完成,它就不再依賴反射。這個翻譯機使用了 unsafe 和其他一些巧妙的機制來高速的將資料轉化為編碼後的位元組流。也可以使用反射來避免 unsafe,但是會明顯變慢。(受到 gob 實現的影響,Go 的 protocol buffer 使用了類似的機制提高速度。)而後的同樣類型的值使用已經編譯好的翻譯機,這樣就可以總是有一致的編碼。

解碼類似,但是略微複雜。當你解碼一個資料,gob 包用一個位元組片儲存編碼後的類型的值用於來解碼,再加上得到解碼的 Go 的值。gob 包構造一個翻譯機用於這個過程:gob 類型線上傳輸用於 Go 類型的解碼。一旦解碼翻譯機構造,一個沒有反射的使用 unsafe 方法的引擎能提供最快的速度。

使用

在帽子裡還有很多秘密,但是結果是得到一個用於資料轉送的高效的,容易使用的編碼系統。這裡有一個完整的例子示範了不同類型的編碼和解碼。留意發送和接收資料是多麼簡單;你所需要做的一切,就是將值和變數置入 gob 包,然後它會完成所有的工作。

package mainimport (    "bytes"    "fmt"    "gob"    "log")type P struct {    X, Y, Z int    Name string}type Q struct {    X, Y *int32    Name string}func main() {    // 初始化編碼器和解碼器。通常 enc 和 dec 會綁定到網路連接,而編碼器和解碼器會運行在不同的進程中。    var network bytes.Buffer //代替網路連接    enc := gob.NewEncoder(&network) // 將會寫入網路    dec := gob.NewDecoder(&network) // 將會從網路中讀取    // 編碼(發送)    err := enc.Encode(P{3, 4, 5, "Pythagoras"})    if err != nil {        log.Fatal("encode error:", err)    }    // 解碼(接收)    var q Q    err = dec.Decode(&q)    if err != nil {        log.Fatal("decode error:", err)    }    fmt.Printf("%q: {%d,%d}\n", q.Name, *q.X, *q.Y)}

你可以複製代碼到 Go Playground 中,編譯並執行這個例子。
rpc 包在 gob 的基礎上將網路上的方法調用的編碼/解碼自動化。這是另一篇隨筆的主題了。

細節

gob 包文檔,尤其是 doc.go 檔案解釋了本文所說的許多細節,並且包含了完整的可啟動並執行例子,用來示範如何對資料進行編碼。如果你對 gob 的實現感興趣,這是個不錯的起點。

– Rob Pike, 三月 2011

相關文章

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.