====== Go Stdlib Wiki: package encoding/binary ======
===== 1. What it is =====
encoding/binary is a Go standard library package for encoding/decoding fixed-size numeric data (and structs made of fixed-size fields) to/from raw bytes—often used when you need a specific binary layout for files, network protocols, or hardware formats.
binary /ˈbaɪnəri/ = nhị phân
encode /ɪnˈkoʊd/ = mã hoá (đóng gói dữ liệu)
decode /diːˈkoʊd/ = giải mã (mở gói dữ liệu)
fixed-size /ˌfɪkst ˈsaɪz/ = kích thước cố định
raw bytes /rɔː baɪts/ = byte thô
===== 2. What it is for =====
Use encoding/binary when you want to:
Read/write numbers in a specific endianness (Little/Big endian) from/to a []byte or stream.
Serialize/deserialize simple structs with fixed-size numeric fields (careful with padding/alignment).
Implement or parse binary protocols (e.g., custom TCP protocol headers).
Work with binary file formats (headers, frames, records).
endianness /en.di.ˈæn.nəs/ = thứ tự sắp byte (little/big endian)
protocol /ˈproʊtəˌkɔːl/ = giao thức
serialize /ˈsɪrəˌlaɪz/ = tuần tự hoá (biến dữ liệu thành chuỗi byte)
===== 3. What it is NOT for =====
encoding/binary is not ideal for:
Complex object serialization (maps with variable lengths, nested slices, strings, etc.).
Cross-language “general-purpose” formats (use protobuf/CBOR/MessagePack/JSON depending on needs).
Human-readable output.
Why:
binary.Write/binary.Read require fixed-size types.
Strings/slices need a length prefix you must design yourself.
===== 4. Core concepts =====
==== 4.1 Byte order (LittleEndian vs BigEndian) ====
encoding/binary exposes byte orders:
binary.LittleEndian
binary.BigEndian
Rule of thumb:
Many network protocols use Big Endian (“network byte order”).
Many CPU-native/file formats may use Little Endian.
network byte order /ˈnetˌwɝːk baɪt ˈɔːrdər/ = thứ tự byte chuẩn mạng
import "encoding/binary"
var x uint32 = 0x11223344
buf := make([]byte, 4)
binary.BigEndian.PutUint32(buf, x) // [11 22 33 44] in hex
binary.LittleEndian.PutUint32(buf, x) // [44 33 22 11] in hex
==== 4.2 Working with numbers in a byte slice ====
Use PutUintXX / UintXX for fast conversion.
b := make([]byte, 8)
binary.LittleEndian.PutUint16(b[0:2], 513) // write
v16 := binary.LittleEndian.Uint16(b[0:2]) // read
binary.BigEndian.PutUint64(b[0:8], 123456789) // write
v64 := binary.BigEndian.Uint64(b[0:8]) // read
uint16/uint64 /ˌjuːɪnt sɪkˈstiːn/ = số nguyên không dấu 16/64-bit
===== 5. binary.Read and binary.Write =====
These operate on an io.Reader / io.Writer and encode/decode using a chosen byte order.
Signatures (conceptually):
binary.Read(r io.Reader, order ByteOrder, data any) error
binary.Write(w io.Writer, order ByteOrder, data any) error
stream /striːm/ = luồng dữ liệu
reader /ˈriːdər/ = bộ đọc
writer /ˈraɪtər/ = bộ ghi
==== 5.1 Example: write then read a struct header ====
Important constraints:
Fields should be fixed-size numeric types (e.g., uint32, int16, [16]byte).
Avoid string, []byte (variable length) inside the struct.
package main
import (
"bytes"
"encoding/binary"
"fmt"
)
type Header struct {
Magic [4]byte // fixed-size array is OK
Version uint16
Flags uint16
Length uint32
}
func main() {
h := Header{
Magic: [4]byte{'D', 'A', 'T', 'A'},
Version: 1,
Flags: 0,
Length: 1024,
}
var buf bytes.Buffer
// Write header in Big Endian
_ = binary.Write(&buf, binary.BigEndian, h)
// Read it back
var h2 Header
_ = binary.Read(&buf, binary.BigEndian, &h2)
fmt.Println(string(h2.Magic[:]), h2.Version, h2.Length)
}
===== 6. Typical patterns (store-able recipes) =====
==== 6.1 Designing a binary message ====
A common simple message layout:
4 bytes: magic
2 bytes: version
4 bytes: payload length
N bytes: payload
Why:
Fixed header makes parsing easy.
Length tells you how many bytes to read next.
==== 6.2 Writing variable-length data (strings / []byte) ====
You usually do:
Write length (fixed-size integer)
Write raw bytes
func WriteBytes(w *bytes.Buffer, p []byte) error { if err := binary.Write(w, binary.BigEndian, uint32(len(p))); err != nil { return err } _, err := w.Write(p) return err }
length prefix /leŋkθ ˈpriːfɪks/ = tiền tố độ dài
==== 6.3 Reading variable-length data ====
func ReadBytes(r *bytes.Reader) ([]byte, error) {
var n uint32
if err := binary.Read(r, binary.BigEndian, &n); err != nil {
return nil, err
}
p := make([]byte, n)
_, err := r.Read(p)
return p, err
}
Tip: for real streams, prefer io.ReadFull to ensure you read exactly n bytes.
===== 7. Notes & pitfalls =====
Structs may include padding due to alignment; that can break “expected layout” across compilers/architectures.
Safer: manually write each field in order.
binary.Read/Write can be slower than manual PutUintXX for hot paths.
Always define your protocol endianness explicitly (don’t rely on machine).
padding /ˈpædɪŋ/ = byte đệm
alignment /əˈlaɪnmənt/ = căn hàng bộ nhớ
explicitly /ɪkˈsplɪsɪtli/ = một cách rõ ràng
===== 8. Quick map: “what is what for” =====
binary.BigEndian / binary.LittleEndian — choose byte order
PutUint16/32/64 + Uint16/32/64 — fast byte-slice number encode/decode
binary.Read / binary.Write — read/write fixed-size values (or structs) from/to streams
Use encoding/gob when: Go-to-Go object serialization (not a fixed binary protocol)
Use encoding/json when: human-readable, interoperable text format