亚洲在线久爱草,狠狠天天香蕉网,天天搞日日干久草,伊人亚洲日本欧美

為了賬號安全,請及時綁定郵箱和手機立即綁定
已解決430363個問題,去搜搜看,總會有你想問的

結構到磁盤的高效 Go 序列化

結構到磁盤的高效 Go 序列化

Go
慕碼人2483693 2022-01-17 10:41:55
我的任務是將 C++ 代碼替換為 Go,而且我對 Go API 還是很陌生。我正在使用 gob 將數百個鍵/值條目編碼到磁盤頁面,但是 gob 編碼有太多不需要的膨脹。package mainimport (    "bytes"    "encoding/gob"    "fmt")type Entry struct {    Key string    Val string}func main() {    var buf bytes.Buffer    enc := gob.NewEncoder(&buf)    e := Entry { "k1", "v1" }    enc.Encode(e)    fmt.Println(buf.Bytes())}這會產生很多我不需要的膨脹:[35 255 129 3 1 1 5 69 110 116 114 121 1 255 130 0 1 2 1 3 75 101 121 1 12 0 1 3 86 97 108 1 12 0 0 0 11 255 130 1 2 107 49 1 2 118 49 0] 我想序列化每個字符串的 len 后跟原始字節,例如:[0 0 0 2 107 49 0 0 0 2 118 49]我保存了數百萬個條目,因此編碼中的額外膨脹將文件大小增加了大約 x10。如何在不手動編碼的情況下將其序列化為后者?
查看完整描述

3 回答

?
郎朗坤

TA貢獻1921條經驗 獲得超9個贊

如果您壓縮一個名為a.txt包含文本"hello"(5 個字符)的文件,則結果 zip 大約為 115 個字節。這是否意味著 zip 格式無法有效壓縮文本文件?當然不是。有一個開銷。如果文件包含"hello"一百次(500 字節),壓縮它會導致文件為120 字節!1x"hello"=> 115 字節,100x"hello"=> 120 字節!我們添加了 495 個字節,但壓縮后的大小只增加了 5 個字節。


encoding/gob包裹也發生了類似的事情:


該實現為流中的每種數據類型編譯自定義編解碼器,并且在使用單個編碼器傳輸值流時最有效,從而分攤編譯成本。


當您“首先”序列化類型的值時,還必須包含/傳輸類型的定義,因此解碼器可以正確解釋和解碼流:


一連串的gobs是自我描述的。流中的每個數據項之前都有其類型的規范,用一小組預定義類型表示。


讓我們回到你的例子:


var buf bytes.Buffer

enc := gob.NewEncoder(&buf)

e := Entry{"k1", "v1"}

enc.Encode(e)

fmt.Println(buf.Len())

它打?。?/p>


48

現在讓我們再編碼幾個相同的類型:


enc.Encode(e)

fmt.Println(buf.Len())

enc.Encode(e)

fmt.Println(buf.Len())

現在輸出是:


60

72

在Go Playground上嘗試一下。


分析結果:


相同Entry類型的附加值僅花費12 個字節,而第一個是48字節,因為還包括類型定義(大約 26 個字節),但這是一次性開銷。


所以基本上你傳輸 2 strings:"k1"并且"v1"是 4 個字節,并且strings 的長度也必須包括在內,使用4字節(int在 32 位架構上的大?。┙o你 12 個字節,這是“最小值”。(是的,您可以使用較小的類型來表示長度,但這有其局限性。對于小數字,可變長度編碼將是更好的選擇,請參閱encoding/binary包。)


總而言之,encoding/gob可以很好地滿足您的需求。不要被最初的印象所迷惑。


如果這 12 個字節對Entry您來說“太多”,您始終可以將流包裝到 acompress/flate或compress/gzipwriter 中以進一步減小大?。ㄒ該Q取較慢的編碼/解碼和進程的稍高內存要求)。

示范:

讓我們測試以下 5 個解決方案:

  • 使用“裸”輸出(無壓縮)

  • 用于compress/flate壓縮輸出encoding/gob

  • 用于compress/zlib壓縮輸出encoding/gob

  • 用于compress/gzip壓縮輸出encoding/gob

  • 用于github.com/dsnet/compress/bzip2壓縮輸出encoding/gob

我們將編寫一千個條目,更改每個條目的鍵和值,如"k000""v000"、"k001""v001"。這意味著 an 的未壓縮大小Entry為 4 字節 + 4 字節 + 4 字節 + 4 字節 = 16 字節(2x4 字節文本,2x4 字節長度)。

代碼如下所示:

for _, name := range []string{"Naked", "flate", "zlib", "gzip", "bzip2"} {

    buf := &bytes.Buffer{}


    var out io.Writer

    switch name {

    case "Naked":

        out = buf

    case "flate":

        out, _ = flate.NewWriter(buf, flate.DefaultCompression)

    case "zlib":

        out, _ = zlib.NewWriterLevel(buf, zlib.DefaultCompression)

    case "gzip":

        out = gzip.NewWriter(buf)

    case "bzip2":

        out, _ = bzip2.NewWriter(buf, nil)

    }


    enc := gob.NewEncoder(out)

    e := Entry{}

    for i := 0; i < 1000; i++ {

        e.Key = fmt.Sprintf("k%3d", i)

        e.Val = fmt.Sprintf("v%3d", i)

        enc.Encode(e)

    }


    if c, ok := out.(io.Closer); ok {

        c.Close()

    }

    fmt.Printf("[%5s] Length: %5d, average: %5.2f / Entry\n",

        name, buf.Len(), float64(buf.Len())/1000)

}

輸出:


[Naked] Length: 16036, average: 16.04 / Entry

[flate] Length:  4120, average:  4.12 / Entry

[ zlib] Length:  4126, average:  4.13 / Entry

[ gzip] Length:  4138, average:  4.14 / Entry

[bzip2] Length:  2042, average:  2.04 / Entry

在Go Playground上嘗試一下。


正如您所看到的:“裸”輸出16.04 bytes/Entry僅略高于計算的大小(由于上面討論的一次性微小開銷)。


當您使用 flate、zlib 或 gzip 壓縮輸出時,您可以將輸出大小減小到約4.13 bytes/Entry,這大約是理論大小的 ~26%,我相信這會讓您滿意。如果沒有,您可以使用提供更高效率壓縮的庫,例如 bzip2,在上面的示例中2.04 bytes/Entry,它的結果是理論大小的12.7% !


(請注意,對于“真實”數據,壓縮率可能會高很多,因為我在測試中使用的鍵和值非常相似,因此可壓縮性非常好;對于真實數據,壓縮率仍然應該在 50% 左右)。


查看完整回答
反對 回復 2022-01-17
?
米脂

TA貢獻1836條經驗 獲得超3個贊

使用 protobuf 有效地編碼您的數據。


https://github.com/golang/protobuf


你的主要看起來像這樣:


package main


import (

    "fmt"

    "log"


    "github.com/golang/protobuf/proto"

)


func main() {

    e := &Entry{

        Key: proto.String("k1"),

        Val: proto.String("v1"),

    }

    data, err := proto.Marshal(e)

    if err != nil {

        log.Fatal("marshaling error: ", err)

    }

    fmt.Println(data)

}

您創建一個文件,example.proto,如下所示:


package main;


message Entry {

    required string Key = 1;

    required string Val = 2;

}

您可以通過運行從 proto 文件生成 go 代碼:


$ protoc --go_out=. *.proto

如果您愿意,可以檢查生成的文件。


您可以運行并查看結果輸出:


$ go run *.go

[10 2 107 49 18 2 118 49]


查看完整回答
反對 回復 2022-01-17
?
陪伴而非守候

TA貢獻1757條經驗 獲得超8個贊

您非常害怕的“手動編碼”在 Go 中使用標準encoding/binary包輕松完成。


您似乎將字符串長度值存儲為大端格式的 32 位整數,因此您可以繼續在 Go 中執行此操作:


package main


import (

    "bytes"

    "encoding/binary"

    "fmt"

    "io"

)


func encode(w io.Writer, s string) (n int, err error) {

    var hdr [4]byte

    binary.BigEndian.PutUint32(hdr[:], uint32(len(s)))

    n, err = w.Write(hdr[:])

    if err != nil {

        return

    }

    n2, err := io.WriteString(w, s)

    n += n2

    return

}


func main() {

    var buf bytes.Buffer


    for _, s := range []string{

        "ab",

        "cd",

        "de",

    } {

        _, err := encode(&buf, s)

        if err != nil {

            panic(err)

        }

    }

    fmt.Printf("%v\n", buf.Bytes())

}


請注意,在此示例中,我正在寫入字節緩沖區,但這僅用于演示目的 - 由于encode()寫入io.Writer,您可以將打開的文件、網絡套接字和其他任何實現該接口的文件傳遞給它。


查看完整回答
反對 回復 2022-01-17
  • 3 回答
  • 0 關注
  • 296 瀏覽
慕課專欄
更多

添加回答

舉報

0/150
提交
取消
微信客服

購課補貼
聯系客服咨詢優惠詳情

幫助反饋 APP下載

慕課網APP
您的移動學習伙伴

公眾號

掃描二維碼
關注慕課網微信公眾號