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% 左右)。
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]
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,您可以將打開的文件、網絡套接字和其他任何實現該接口的文件傳遞給它。
- 3 回答
- 0 關注
- 296 瀏覽
添加回答
舉報
