Table of Contents

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