diff --git a/.github/workflows/test.yaml b/.github/workflows/test.yaml new file mode 100644 index 0000000..8a4dd9e --- /dev/null +++ b/.github/workflows/test.yaml @@ -0,0 +1,20 @@ +on: + push: + branches: ["dev"] + pull_request: + branches: ["main"] +name: Test +jobs: + test: + strategy: + matrix: + go-version: [1.19.x, 1.20.x, 1.21.x, 1.22.x, 1.23.x] + os: [ubuntu-latest, macos-latest, windows-latest] + runs-on: ${{ matrix.os }} + steps: + - uses: actions/setup-go@v4 + with: + go-version: "${{ matrix.go-version }}" + - uses: actions/checkout@v3 + - name: Test + run: go mod tidy && go test -v ./... diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..d151c7c --- /dev/null +++ b/.gitignore @@ -0,0 +1,27 @@ +# If you prefer the allow list template instead of the deny list, see community template: +# https://github.com/github/gitignore/blob/main/community/Golang/Go.AllowList.gitignore +# +# Binaries for programs and plugins +*.exe +*.exe~ +*.dll +*.so +*.dylib + +# Test binary, built with `go test -c` +*.test + +# Output of the go coverage tool, specifically when used with LiteIDE +*.out + +# Dependency directories (remove the comment below to include it) +# vendor/ + +# Go workspace file +# go.work +go.work.sum + +.vscode/ +.idea/ +.DS_Store +.cursorrules diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index 757ab37..0000000 --- a/.travis.yml +++ /dev/null @@ -1,10 +0,0 @@ -language: go -sudo: false - -script: go test -v - -go: - - 1.3 - - 1.12 - - 1.13 - - tip diff --git a/LICENSE b/LICENSE index 42e8263..5a86376 100644 --- a/LICENSE +++ b/LICENSE @@ -1,4 +1,6 @@ -Copyright (c) 2015 Ryan Hileman +MIT License + +Copyright (c) 2025 Kuma (路口 IT 大叔) Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal @@ -7,13 +9,13 @@ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: -The above copyright notice and this permission notice shall be included in -all copies or substantial portions of the Software. +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -THE SOFTWARE. +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/README.md b/README.md index e8c6f01..ac24394 100644 --- a/README.md +++ b/README.md @@ -1,103 +1,405 @@ -[![Build Status](https://travis-ci.org/lunixbochs/struc.svg?branch=master)](https://travis-ci.org/lunixbochs/struc) [![GoDoc](https://godoc.org/github.com/lunixbochs/struc?status.svg)](https://godoc.org/github.com/lunixbochs/struc) +English | [中文](./README_CN.md) -struc -==== +
+ logo +
-Struc exists to pack and unpack C-style structures from bytes, which is useful for binary files and network protocols. It could be considered an alternative to `encoding/binary`, which requires massive boilerplate for some similar operations. +[![Go Report Card](https://goreportcard.com/badge/github.com/shengyanli1982/struc/v2)](https://goreportcard.com/report/github.com/shengyanli1982/struc/v2) +[![Build Status](https://github.com/shengyanli1982/struc/actions/workflows/test.yaml/badge.svg)](https://github.com/shengyanli1982/struc/actions) +[![Go Reference](https://pkg.go.dev/badge/github.com/shengyanli1982/struc/v2.svg)](https://pkg.go.dev/github.com/shengyanli1982/struc/v2) -Take a look at an [example comparing `struc` and `encoding/binary`](https://bochs.info/p/cxvm9) +A high-performance Go library for binary data serialization with C-style struct definitions. -Struc considers usability first. That said, it does cache reflection data and aims to be competitive with `encoding/binary` struct packing in every way, including performance. +## Why struc v2? -Example struct ----- +- 🚀 **High Performance**: Optimized binary serialization with reflection caching +- 💡 **Simple API**: Intuitive struct tag-based configuration without boilerplate code +- 🛡️ **Type Safety**: Strong type checking with comprehensive error handling +- 🔄 **Flexible Encoding**: Support for both big and little endian byte orders +- 📦 **Rich Type Support**: Handles primitive types, arrays, slices, and custom padding +- 🎯 **Zero Dependencies**: Pure Go implementation with no external dependencies -```Go -type Example struct { - Var int `struc:"int32,sizeof=Str"` - Str string - Weird []byte `struc:"[8]int64"` - Var []int `struc:"[]int32,little"` -} +## Installation + +```bash +go get github.com/shengyanli1982/struc/v2 ``` -Struct tag format ----- +## Quick Start + +```go +package main + +import ( + "bytes" + "github.com/shengyanli1982/struc/v2" +) - - ```Var []int `struc:"[]int32,little,sizeof=StringField"` ``` will pack Var as a slice of little-endian int32, and link it as the size of `StringField`. - - `sizeof=`: Indicates this field is a number used to track the length of a another field. `sizeof` fields are automatically updated on `Pack()` based on the current length of the tracked field, and are used to size the target field during `Unpack()`. - - Bare values will be parsed as type and endianness. +type Message struct { + Size int `struc:"int32,sizeof=Payload"` // Automatically tracks payload size + Payload []byte // Dynamic binary data + Flags uint16 `struc:"little"` // Little-endian encoding +} + +func main() { + var buf bytes.Buffer -Endian formats ----- + // Pack data + msg := &Message{ + Payload: []byte("Hello, World!"), + Flags: 1234, + } + if err := struc.Pack(&buf, msg); err != nil { + panic(err) + } - - `big` (default) - - `little` + // Unpack data + result := &Message{} + if err := struc.Unpack(&buf, result); err != nil { + panic(err) + } +} +``` -Recognized types ----- +## Features - - `pad` - this type ignores field contents and is backed by a `[length]byte` containing nulls - - `bool` - - `byte` - - `int8`, `uint8` - - `int16`, `uint16` - - `int32`, `uint32` - - `int64`, `uint64` - - `float32` - - `float64` +### 1. Rich Type Support -Types can be indicated as arrays/slices using `[]` syntax. Example: `[]int64`, `[8]int32`. +- Primitive types: `bool`, `int8`-`int64`, `uint8`-`uint64`, `float32`, `float64` +- Composite types: strings, byte slices, arrays +- Special types: padding bytes for alignment -Bare slice types (those with no `[size]`) must have a linked `Sizeof` field. +### 2. Automatic Size Tracking -Private fields are ignored when packing and unpacking. +- Automatically manages lengths of variable-sized fields +- Eliminates manual size calculation and tracking +- Reduces potential errors in binary protocol implementations -Example code ----- +### 3. Performance Optimizations -```Go -package main +- Reflection caching for repeated operations +- Efficient memory allocation +- Optimized encoding/decoding paths -import ( - "bytes" - "github.com/lunixbochs/struc" -) +### 4. Smart Field Tags +```go type Example struct { - A int `struc:"big"` + Length int `struc:"int32,sizeof=Data"` // Size tracking + Data []byte // Dynamic data + Version uint16 `struc:"little"` // Endianness control + Padding [4]byte `struc:"[4]pad"` // Explicit padding +} +``` - // B will be encoded/decoded as a 16-bit int (a "short") - // but is stored as a native int in the struct - B int `struc:"int16"` +### 5. Struct Tag Reference - // the sizeof key links a buffer's size to any int field - Size int `struc:"int8,little,sizeof=Str"` - Str string +The `struc` tag supports various formats and options for precise binary data control: - // you can get freaky if you want - Str2 string `struc:"[5]int64"` +#### Basic Type Definition + +```go +type BasicTypes struct { + Int8Val int `struc:"int8"` // 8-bit integer + Int16Val int `struc:"int16"` // 16-bit integer + Int32Val int `struc:"int32"` // 32-bit integer + Int64Val int `struc:"int64"` // 64-bit integer + UInt8Val int `struc:"uint8"` // 8-bit unsigned integer + UInt16Val int `struc:"uint16"` // 16-bit unsigned integer + UInt32Val int `struc:"uint32"` // 32-bit unsigned integer + UInt64Val int `struc:"uint64"` // 64-bit unsigned integer + BoolVal bool `struc:"bool"` // Boolean value + Float32Val float32 `struc:"float32"` // 32-bit float + Float64Val float64 `struc:"float64"` // 64-bit float } +``` -func main() { - var buf bytes.Buffer - t := &Example{1, 2, 0, "test", "test2"} - err := struc.Pack(&buf, t) - o := &Example{} - err = struc.Unpack(&buf, o) +#### Array and Fixed-Size Fields + +```go +type ArrayTypes struct { + // Fixed-size byte array (4 bytes) + ByteArray []byte `struc:"[4]byte"` + // Fixed-size integer array (5 int32 values) + IntArray []int32 `struc:"[5]int32"` + // Padding bytes for alignment + Padding []byte `struc:"[3]pad"` + // Fixed-size string (treated as byte array) + FixedString string `struc:"[8]byte"` } ``` -Benchmark ----- +#### Dynamic Size and References + +```go +type DynamicTypes struct { + // Size field tracking the length of Data + Size int `struc:"int32,sizeof=Data"` + // Dynamic byte slice whose size is tracked by Size + Data []byte + // Size field using uint8 to track AnotherData + Size2 int `struc:"uint8,sizeof=AnotherData"` + // Another dynamic data field + AnotherData []byte + // Dynamic string field with size reference + StrSize int `struc:"uint16,sizeof=Text"` + Text string `struc:"[]byte"` +} +``` -`BenchmarkEncode` uses struc. `Stdlib` benchmarks use equivalent `encoding/binary` code. `Manual` encodes without any reflection, and should be considered an upper bound on performance (which generated code based on struc definitions should be able to achieve). +#### Byte Order Control +```go +type ByteOrderTypes struct { + // Big-endian encoded integer + BigInt int32 `struc:"big"` + // Little-endian encoded integer + LittleInt int32 `struc:"little"` + // Default to big-endian if not specified + DefaultInt int32 +} ``` -BenchmarkEncode 1000000 1265 ns/op -BenchmarkStdlibEncode 1000000 1855 ns/op -BenchmarkManualEncode 5000000 284 ns/op -BenchmarkDecode 1000000 1259 ns/op -BenchmarkStdlibDecode 1000000 1656 ns/op -BenchmarkManualDecode 20000000 89.0 ns/op + +#### Special Options + +```go +type SpecialTypes struct { + // Skip this field during packing/unpacking + Ignored int `struc:"skip"` + // Completely ignore this field (won't be included in binary) + Private string `struc:"-"` + // Size reference from another field + Data []byte `struc:"sizefrom=Size"` + // Custom type implementation + YourCustomType CustomBinaryer +} ``` + +Tag Format: `struc:"type,option1,option2"` + +- `type`: The binary type (e.g., int8, uint16, [4]byte) +- `big`/`little`: Byte order specification +- `sizeof=Field`: Specify this field tracks another field's size +- `sizefrom=Field`: Specify this field's size is tracked by another field +- `skip`: Skip this field during packing/unpacking (space is reserved in binary) +- `-`: Completely ignore this field (not included in binary) +- `[N]type`: Fixed-size array of type with length N +- `[]type`: Dynamic-size array/slice of type + +#### Why `omitempty` is not supported? + +Unlike JSON serialization where fields can be optionally omitted, binary serialization requires a strict and fixed byte layout. Here's why `omitempty` is not supported: + +1. **Fixed Binary Layout** + + - Binary protocols require precise byte positioning + - Each field must occupy its predefined position and size + - Omitting fields would break the byte alignment + +2. **Parsing Dependencies** + + - Binary data is parsed sequentially, byte by byte + - If fields are omitted, the byte stream becomes misaligned + - The receiving end cannot correctly reconstruct the data structure + +3. **Protocol Stability** + + - Binary protocols need strict version control + - Allowing optional fields would break protocol stability + - Makes it impossible to maintain backward compatibility + +4. **Debugging Complexity** + - With omitted fields, binary data becomes unpredictable + - Makes it extremely difficult to debug byte streams + - Increases the complexity of troubleshooting + +If you need to mark certain fields as optional, consider these alternatives: + +- Use explicit flag fields to indicate validity +- Use default values for optional fields +- Use the `struc:"-"` tag to completely exclude fields from serialization + +## Advanced Usage + +### Custom Type Implementation + +If you need complete control over how a type is serialized and deserialized in binary format, you can implement the `CustomBinaryer` interface: + +```go +type CustomBinaryer interface { + // Pack serializes data into a byte slice + Pack(p []byte, opt *Options) (int, error) + + // Unpack deserializes data from a Reader + Unpack(r io.Reader, length int, opt *Options) error + + // Size returns the size of serialized data + Size(opt *Options) int + + // String returns the string representation of the type + String() string +} +``` + +For example, implementing a 3-byte integer type: + +```go +// Usage example +type Message struct { + Value CustomBinaryer // Using custom type +} + +// Int3 is a custom 3-byte integer type +type Int3 uint32 + +func (i *Int3) Pack(p []byte, opt *Options) (int, error) { + // Convert 4-byte integer to 3 bytes + var tmp [4]byte + binary.BigEndian.PutUint32(tmp[:], uint32(*i)) + copy(p, tmp[1:]) // Only copy the last 3 bytes + return 3, nil +} + +func (i *Int3) Unpack(r io.Reader, length int, opt *Options) error { + var tmp [4]byte + if _, err := r.Read(tmp[1:]); err != nil { + return err + } + *i = Int3(binary.BigEndian.Uint32(tmp[:])) + return nil +} + +func (i *Int3) Size(opt *Options) int { + return 3 // Fixed 3-byte size +} + +func (i *Int3) String() string { + return strconv.FormatUint(uint64(*i), 10) +} +``` + +Benefits of custom types: + +- Complete control over binary format +- Support for special data layouts +- Ability to implement compression or encryption +- Suitable for handling legacy system formats + +## Best Practices + +1. **Use Appropriate Types** + + - Match Go types with their binary protocol counterparts + - Use fixed-size arrays when the size is known + - Use slices with `sizeof` for dynamic data + +2. **Error Handling** + + - Always check returned errors from Pack/Unpack + - Validate data sizes before processing + +3. **Performance Optimization** + + - Reuse structs when possible + - Consider using pools for frequently used structures + +4. **Memory Management** + + - When packing, the library pre-allocates a buffer with the exact size needed for the data + + ```go + bufferSize := packer.Sizeof(value, options) + buffer := make([]byte, bufferSize) + ``` + + - For unpacking, the library uses internal 4K buffers for efficient operations + - When unpacking, slice/string fields in your struct will directly reference these internal buffers + - These buffers will remain in memory as long as your struct fields reference them + + ```go + type Message struct { + Data []byte // This field will reference the internal buffer + } + + func processRetain() { + messages := make([]*Message, 0) + + // >> Important: + // The Field struct is just a metadata description object + // Its lifecycle end does not affect user struct fields that have been set via unsafe operations + // Because unsafe operations have directly modified the underlying pointer of user struct fields to point to the 4K buffer + // >> Therefore: + // Releasing the Field struct will not cause the slice references on the 4K buffer to disappear + // These references only disappear when the user structs using these slices are GC'ed + // The 4K buffer's lifecycle depends on the lifecycle of all user structs referencing it + + // Each unpacked message's Data field references the internal buffer + for i := 0; i < 10; i++ { + msg := &Message{} + // During unpacking: + // 1. unpackBasicTypeSlicePool provides 4K buffer + // 2. Field struct handles metadata + // 3. unsafe operations point msg.Data to part of 4K buffer + struc.Unpack(reader, msg) + // Even if Field struct is released now + // msg.Data still points to 4K buffer + // Only when msg is GC'ed will this reference disappear + messages = append(messages, msg) + // Internal buffer can't be GC'ed because msg.Data references it + // Field struct's lifecycle is irrelevant to 4K buffer references + // 4K buffer references are held by user structs + // Only when all user structs referencing this 4K buffer are GC'ed can the buffer be collected + } + } + ``` + + - To release the internal buffer reference, you can either set the field to nil or copy the data: + + ```go + func processRelease() { + msg := &Message{} + struc.Unpack(reader, msg) + + // Method 1: Simply set to nil if you don't need the data anymore + msg.Data = nil // Now msg.Data is nil, no longer references the internal buffer + + // Method 2: Copy data if you need to keep it + if needData { + dataCopy := make([]byte, len(msg.Data)) + copy(dataCopy, msg.Data) + msg.Data = dataCopy // Now msg.Data references our copy + } + + // The internal buffer can now be GC'ed if no other structs reference it + } + ``` + +## Performance Benchmarks + +```bash +$ go.exe test -benchmem -run=^$ -bench . github.com/shengyanli1982/struc/v2 +Starting pprof server on :6060 +goos: windows +goarch: amd64 +pkg: github.com/shengyanli1982/struc/v2 +cpu: 12th Gen Intel(R) Core(TM) i5-12400F +BenchmarkArrayEncode-12 3288741 366.7 ns/op 137 B/op 4 allocs/op +BenchmarkSliceEncode-12 3110095 389.8 ns/op 137 B/op 4 allocs/op +BenchmarkArrayDecode-12 3410102 343.3 ns/op 73 B/op 2 allocs/op +BenchmarkSliceDecode-12 2904127 423.7 ns/op 113 B/op 4 allocs/op +BenchmarkEncode-12 3297550 364.5 ns/op 56 B/op 2 allocs/op +BenchmarkStdlibEncode-12 8496386 139.0 ns/op 24 B/op 1 allocs/op +BenchmarkManualEncode-12 48760538 24.66 ns/op 64 B/op 1 allocs/op +BenchmarkDecode-12 3493039 329.7 ns/op 55 B/op 1 allocs/op +BenchmarkStdlibDecode-12 6607056 176.8 ns/op 32 B/op 2 allocs/op +BenchmarkManualDecode-12 100000000 11.71 ns/op 8 B/op 1 allocs/op +BenchmarkFullEncode-12 1000000 1546 ns/op 216 B/op 2 allocs/op +BenchmarkFullDecode-12 1000000 1684 ns/op 279 B/op 4 allocs/op +BenchmarkFieldPool-12 7039993 162.4 ns/op 56 B/op 2 allocs/op +BenchmarkGetFormatString/Simple-12 4950135 238.6 ns/op 21 B/op 2 allocs/op +BenchmarkGetFormatString/Complex-12 2522713 465.0 ns/op 48 B/op 3 allocs/op +``` + +## License + +MIT License - see LICENSE file for details diff --git a/README_CN.md b/README_CN.md new file mode 100644 index 0000000..6f6ead4 --- /dev/null +++ b/README_CN.md @@ -0,0 +1,405 @@ +[English](./README.md) | 中文 + +
+ logo +
+ +[![Go Report Card](https://goreportcard.com/badge/github.com/shengyanli1982/struc/v2)](https://goreportcard.com/report/github.com/shengyanli1982/struc/v2) +[![Build Status](https://github.com/shengyanli1982/struc/actions/workflows/test.yaml/badge.svg)](https://github.com/shengyanli1982/struc/actions) +[![Go Reference](https://pkg.go.dev/badge/github.com/shengyanli1982/struc/v2.svg)](https://pkg.go.dev/github.com/shengyanli1982/struc/v2) + +一个高性能的 Go 二进制数据序列化库,采用 C 风格的结构体定义。 + +## 为什么选择 struc v2? + +- 🚀 **卓越性能**:优化的二进制序列化,支持反射缓存 +- 💡 **简洁 API**:基于结构体标签的直观配置,无需样板代码 +- 🛡️ **类型安全**:强类型检查和全面的错误处理 +- 🔄 **灵活编码**:支持大端和小端字节序 +- 📦 **丰富类型支持**:支持原始类型、数组、切片和自定义填充 +- 🎯 **零依赖**:纯 Go 实现,无外部依赖 + +## 安装 + +```bash +go get github.com/shengyanli1982/struc/v2 +``` + +## 快速开始 + +```go +package main + +import ( + "bytes" + "github.com/shengyanli1982/struc/v2" +) + +type Message struct { + Size int `struc:"int32,sizeof=Payload"` // 自动追踪负载大小 + Payload []byte // 动态二进制数据 + Flags uint16 `struc:"little"` // 小端编码 +} + +func main() { + var buf bytes.Buffer + + // 打包数据 + msg := &Message{ + Payload: []byte("Hello, World!"), + Flags: 1234, + } + if err := struc.Pack(&buf, msg); err != nil { + panic(err) + } + + // 解包数据 + result := &Message{} + if err := struc.Unpack(&buf, result); err != nil { + panic(err) + } +} +``` + +## 特性 + +### 1. 丰富的类型支持 + +- 原始类型:`bool`、`int8`-`int64`、`uint8`-`uint64`、`float32`、`float64` +- 复合类型:字符串、字节切片、数组 +- 特殊类型:用于对齐的填充字节 + +### 2. 自动大小追踪 + +- 自动管理可变大小字段的长度 +- 消除手动大小计算和追踪 +- 减少二进制协议实现中的潜在错误 + +### 3. 性能优化 + +- 反射缓存以提高重复操作性能 +- 高效的内存分配 +- 优化的编码/解码路径 + +### 4. 智能字段标签 + +```go +type Example struct { + Length int `struc:"int32,sizeof=Data"` // 大小追踪 + Data []byte // 动态数据 + Version uint16 `struc:"little"` // 字节序控制 + Padding [4]byte `struc:"[4]pad"` // 显式填充 +} +``` + +### 5. 结构体标签参考 + +`struc` 标签支持多种格式和选项,用于精确控制二进制数据: + +#### 基本类型定义 + +```go +type BasicTypes struct { + Int8Val int `struc:"int8"` // 8位整数 + Int16Val int `struc:"int16"` // 16位整数 + Int32Val int `struc:"int32"` // 32位整数 + Int64Val int `struc:"int64"` // 64位整数 + UInt8Val int `struc:"uint8"` // 8位无符号整数 + UInt16Val int `struc:"uint16"` // 16位无符号整数 + UInt32Val int `struc:"uint32"` // 32位无符号整数 + UInt64Val int `struc:"uint64"` // 64位无符号整数 + BoolVal bool `struc:"bool"` // 布尔值 + Float32Val float32 `struc:"float32"` // 32位浮点数 + Float64Val float64 `struc:"float64"` // 64位浮点数 +} +``` + +#### 数组和固定大小字段 + +```go +type ArrayTypes struct { + // 固定大小字节数组(4字节) + ByteArray []byte `struc:"[4]byte"` + // 固定大小整数数组(5个int32值) + IntArray []int32 `struc:"[5]int32"` + // 用于对齐的填充字节 + Padding []byte `struc:"[3]pad"` + // 固定大小字符串(作为字节数组处理) + FixedString string `struc:"[8]byte"` +} +``` + +#### 动态大小和引用 + +```go +type DynamicTypes struct { + // 追踪 Data 长度的大小字段 + Size int `struc:"int32,sizeof=Data"` + // 大小由 Size 追踪的动态字节切片 + Data []byte + // 使用 uint8 追踪 AnotherData 的大小字段 + Size2 int `struc:"uint8,sizeof=AnotherData"` + // 另一个动态数据字段 + AnotherData []byte + // 带大小引用的动态字符串字段 + StrSize int `struc:"uint16,sizeof=Text"` + Text string `struc:"[]byte"` +} +``` + +#### 字节序控制 + +```go +type ByteOrderTypes struct { + // 大端编码整数 + BigInt int32 `struc:"big"` + // 小端编码整数 + LittleInt int32 `struc:"little"` + // 未指定则默认为大端 + DefaultInt int32 +} +``` + +#### 特殊选项 + +```go +type SpecialTypes struct { + // 在打包/解包时跳过此字段(二进制中保留空间) + Ignored int `struc:"skip"` + // 完全忽略此字段(不包含在二进制中) + Private string `struc:"-"` + // 从其他字段获取大小引用 + Data []byte `struc:"sizefrom=Size"` + // 自定义类型实现 + YourCustomType CustomBinaryer +} +``` + +标签格式:`struc:"type,option1,option2"` + +- `type`:二进制类型(如 int8、uint16、[4]byte) +- `big`/`little`:字节序指定 +- `sizeof=Field`:指定此字段追踪另一个字段的大小 +- `sizefrom=Field`:指定此字段的大小由另一个字段追踪 +- `skip`:在打包/解包时跳过此字段(二进制中保留空间) +- `-`:完全忽略此字段(不包含在二进制中) +- `[N]type`:长度为 N 的固定大小类型数组 +- `[]type`:动态大小的类型数组/切片 + +#### 为什么不支持 `omitempty`? + +与 JSON 序列化可以选择性地省略字段不同,二进制序列化需要严格且固定的字节布局。以下是不支持 `omitempty` 的原因: + +1. **固定的二进制布局** + + - 二进制协议要求精确的字节定位 + - 每个字段必须占据其预定义的位置和大小 + - 省略字段会破坏字节对齐 + +2. **解析依赖性** + + - 二进制数据是按字节顺序解析的 + - 如果省略字段,字节流会错位 + - 接收端无法正确重建数据结构 + +3. **协议稳定性** + + - 二进制协议需要严格的版本控制 + - 允许可选字段会破坏协议的稳定性 + - 无法保证向后兼容性 + +4. **调试复杂性** + - 字段省略会导致二进制数据变得不可预测 + - 极大增加了字节流调试的难度 + - 提高了问题排查的复杂度 + +如果你需要标记某些字段为可选,可以考虑以下替代方案: + +- 使用显式的标志字段来表示有效性 +- 为可选字段使用默认值 +- 使用 `struc:"-"` 标签完全排除字段不进行序列化 + +## 高级用法 + +### 自定义类型实现 + +如果你需要完全控制类型的二进制序列化和反序列化,可以实现 `CustomBinaryer` 接口: + +```go +type CustomBinaryer interface { + // Pack 将数据序列化到字节切片 + Pack(p []byte, opt *Options) (int, error) + + // Unpack 从 Reader 中反序列化数据 + Unpack(r io.Reader, length int, opt *Options) error + + // Size 返回序列化后的字节大小 + Size(opt *Options) int + + // String 返回类型的字符串表示 + String() string +} +``` + +例如,实现一个 3 字节整数类型: + +```go +// 使用示例 +type Message struct { + Value CustomBinaryer // 使用自定义类型 +} + +// Int3 是一个自定义的 3 字节整数类型 +type Int3 uint32 + +func (i *Int3) Pack(p []byte, opt *Options) (int, error) { + // 将 4 字节整数转换为 3 字节 + var tmp [4]byte + binary.BigEndian.PutUint32(tmp[:], uint32(*i)) + copy(p, tmp[1:]) // 只复制后 3 字节 + return 3, nil +} + +func (i *Int3) Unpack(r io.Reader, length int, opt *Options) error { + var tmp [4]byte + if _, err := r.Read(tmp[1:]); err != nil { + return err + } + *i = Int3(binary.BigEndian.Uint32(tmp[:])) + return nil +} + +func (i *Int3) Size(opt *Options) int { + return 3 // 固定 3 字节大小 +} + +func (i *Int3) String() string { + return strconv.FormatUint(uint64(*i), 10) +} +``` + +自定义类型的优势: + +- 完全控制二进制格式 +- 支持特殊的数据布局 +- 可以实现压缩或加密 +- 适合处理遗留系统的特殊格式 + +## 最佳实践 + +1. **使用适当的类型** + + - 将 Go 类型与其二进制协议对应物匹配 + - 当大小已知时使用固定大小数组 + - 对动态数据使用带 `sizeof` 的切片 + +2. **错误处理** + + - 始终检查 Pack/Unpack 返回的错误 + - 在处理之前验证数据大小 + +3. **性能优化** + + - 尽可能重用结构体 + - 考虑对频繁使用的结构使用对象池 + +4. **内存管理** + + - 库在打包时,会根据数据大小预分配精确大小的缓冲区 + + ```go + bufferSize := packer.Sizeof(value, options) + buffer := make([]byte, bufferSize) + ``` + + - 解包时,库使用内部 4K 缓冲区来实现高效解包 + - 解包时,结构体中的切片/字符串字段会直接引用这些内部缓冲区 + - 只要你的结构体字段还在引用这些缓冲区,它们就会保留在内存中 + + ```go + type Message struct { + Data []byte // 这个字段会引用内部缓冲区 + } + + func processRetain() { + messages := make([]*Message, 0) + + // >> 重要的是: + // Field 结构体只是一个元数据描述对象 + // 它的生命周期结束与否并不影响已经通过 unsafe 操作设置的用户结构体字段 + // 因为 unsafe 操作已经直接修改了用户结构体字段的底层指针,指向了 4K buffer + // >> 所以: + // Field 结构体的释放并不会导致 4K buffer 上的切片引用消失 + // 只有当使用这些切片的用户结构体被 GC 时,这些引用才会消失 + // 4K buffer 的生命周期取决于所有引用它的用户结构体的生命周期 + + // 每个解包的消息的 Data 字段都引用内部缓冲区 + for i := 0; i < 10; i++ { + msg := &Message{} + // 解包过程中: + // 1. unpackBasicTypeSlicePool 提供 4K buffer + // 2. Field 结构体处理元数据 + // 3. unsafe 操作将 msg.Data 指向 4K buffer 的一部分 + struc.Unpack(reader, msg) + // 这时即使 Field 结构体被释放 + // msg.Data 仍然指向 4K buffer + // 只有当 msg 被 GC,这个引用才会消失 + messages = append(messages, msg) + // 内部缓冲区无法被 GC,因为 msg.Data 引用着它 + // Field 结构体的生命周期与 4K buffer 的引用无关 + // 4K buffer 的引用由用户结构体持有 + // 只有当所有引用这个 4K buffer 的用户结构体都被 GC 时,这个 buffer 才可能被回收 + } + } + ``` + + - 要释放对内部缓冲区的引用,你可以将字段设为 nil 或复制数据: + + ```go + func processRelease() { + msg := &Message{} + struc.Unpack(reader, msg) + + // 方法1:如果不再需要数据,直接设为 nil + msg.Data = nil // 现在 msg.Data 为 nil,不再引用内部缓冲区 + + // 方法2:如果需要保留数据,进行复制 + if needData { + dataCopy := make([]byte, len(msg.Data)) + copy(dataCopy, msg.Data) + msg.Data = dataCopy // 现在 msg.Data 引用我们的副本 + } + + // 如果没有其他结构体引用,内部缓冲区现在可以被 GC 了 + } + ``` + +## 性能基准测试 + +```bash +$ go.exe test -benchmem -run=^$ -bench . github.com/shengyanli1982/struc/v2 +Starting pprof server on :6060 +goos: windows +goarch: amd64 +pkg: github.com/shengyanli1982/struc/v2 +cpu: 12th Gen Intel(R) Core(TM) i5-12400F +BenchmarkArrayEncode-12 3288741 366.7 ns/op 137 B/op 4 allocs/op +BenchmarkSliceEncode-12 3110095 389.8 ns/op 137 B/op 4 allocs/op +BenchmarkArrayDecode-12 3410102 343.3 ns/op 73 B/op 2 allocs/op +BenchmarkSliceDecode-12 2904127 423.7 ns/op 113 B/op 4 allocs/op +BenchmarkEncode-12 3297550 364.5 ns/op 56 B/op 2 allocs/op +BenchmarkStdlibEncode-12 8496386 139.0 ns/op 24 B/op 1 allocs/op +BenchmarkManualEncode-12 48760538 24.66 ns/op 64 B/op 1 allocs/op +BenchmarkDecode-12 3493039 329.7 ns/op 55 B/op 1 allocs/op +BenchmarkStdlibDecode-12 6607056 176.8 ns/op 32 B/op 2 allocs/op +BenchmarkManualDecode-12 100000000 11.71 ns/op 8 B/op 1 allocs/op +BenchmarkFullEncode-12 1000000 1546 ns/op 216 B/op 2 allocs/op +BenchmarkFullDecode-12 1000000 1684 ns/op 279 B/op 4 allocs/op +BenchmarkFieldPool-12 7039993 162.4 ns/op 56 B/op 2 allocs/op +BenchmarkGetFormatString/Simple-12 4950135 238.6 ns/op 21 B/op 2 allocs/op +BenchmarkGetFormatString/Complex-12 2522713 465.0 ns/op 48 B/op 3 allocs/op +``` + +## 许可证 + +MIT 许可证 - 详见 LICENSE 文件 diff --git a/assets/logo.png b/assets/logo.png new file mode 100644 index 0000000..7e74c5b Binary files /dev/null and b/assets/logo.png differ diff --git a/bench_test.go b/bench_test.go index 126719b..ed99a48 100644 --- a/bench_test.go +++ b/bench_test.go @@ -3,9 +3,19 @@ package struc import ( "bytes" "encoding/binary" + "net/http" + _ "net/http/pprof" "testing" ) +func init() { + // 启动 pprof http 服务 + go func() { + println("Starting pprof server on :6060") + println(http.ListenAndServe("localhost:6060", nil)) + }() +} + type BenchExample struct { Test [5]byte A int32 @@ -17,7 +27,7 @@ type BenchExample struct { func BenchmarkArrayEncode(b *testing.B) { for i := 0; i < b.N; i++ { var buf bytes.Buffer - if err := Pack(&buf, arrayReference); err != nil { + if err := Pack(&buf, testArrayExample); err != nil { b.Fatal(err) } } @@ -26,7 +36,7 @@ func BenchmarkArrayEncode(b *testing.B) { func BenchmarkSliceEncode(b *testing.B) { for i := 0; i < b.N; i++ { var buf bytes.Buffer - if err := Pack(&buf, sliceReference); err != nil { + if err := Pack(&buf, testSliceExample); err != nil { b.Fatal(err) } } @@ -35,7 +45,7 @@ func BenchmarkSliceEncode(b *testing.B) { func BenchmarkArrayDecode(b *testing.B) { var out ExampleArray for i := 0; i < b.N; i++ { - buf := bytes.NewBuffer(arraySliceReferenceBytes) + buf := bytes.NewBuffer(testArraySliceBytes) if err := Unpack(buf, &out); err != nil { b.Fatal(err) } @@ -45,7 +55,7 @@ func BenchmarkArrayDecode(b *testing.B) { func BenchmarkSliceDecode(b *testing.B) { var out ExampleSlice for i := 0; i < b.N; i++ { - buf := bytes.NewBuffer(arraySliceReferenceBytes) + buf := bytes.NewBuffer(testArraySliceBytes) if err := Unpack(buf, &out); err != nil { b.Fatal(err) } @@ -61,26 +71,28 @@ type BenchStrucExample struct { Data []byte } -var benchRef = &BenchExample{ +var testBenchExample = &BenchExample{ [5]byte{1, 2, 3, 4, 5}, 1, 2, 3, 4, [4]byte{1, 2, 3, 4}, 8, } -var eightBytes = []byte("8bytestr") +var testEightByteString = []byte("8bytestr") -var benchStrucRef = &BenchStrucExample{ +var testBenchStrucExample = &BenchStrucExample{ [5]byte{1, 2, 3, 4, 5}, 1, 2, 3, 4, [4]byte{1, 2, 3, 4}, - 8, eightBytes, + 8, testEightByteString, } func BenchmarkEncode(b *testing.B) { + var buf bytes.Buffer + b.ResetTimer() for i := 0; i < b.N; i++ { - var buf bytes.Buffer - err := Pack(&buf, benchStrucRef) + buf.Reset() + err := Pack(&buf, testBenchStrucExample) if err != nil { b.Fatal(err) } @@ -88,13 +100,15 @@ func BenchmarkEncode(b *testing.B) { } func BenchmarkStdlibEncode(b *testing.B) { + var buf bytes.Buffer + b.ResetTimer() for i := 0; i < b.N; i++ { - var buf bytes.Buffer - err := binary.Write(&buf, binary.BigEndian, benchRef) + buf.Reset() + err := binary.Write(&buf, binary.BigEndian, testBenchExample) if err != nil { b.Fatal(err) } - _, err = buf.Write(eightBytes) + _, err = buf.Write(testEightByteString) if err != nil { b.Fatal(err) } @@ -103,7 +117,7 @@ func BenchmarkStdlibEncode(b *testing.B) { func BenchmarkManualEncode(b *testing.B) { order := binary.BigEndian - s := benchStrucRef + s := testBenchStrucExample for i := 0; i < b.N; i++ { var buf bytes.Buffer tmp := make([]byte, 29) @@ -125,32 +139,37 @@ func BenchmarkManualEncode(b *testing.B) { func BenchmarkDecode(b *testing.B) { var out BenchStrucExample var buf bytes.Buffer - if err := Pack(&buf, benchStrucRef); err != nil { + if err := Pack(&buf, testBenchStrucExample); err != nil { b.Fatal(err) } - bufBytes := buf.Bytes() + bufBytes := make([]byte, buf.Len()) + copy(bufBytes, buf.Bytes()) + b.ResetTimer() for i := 0; i < b.N; i++ { - buf := bytes.NewReader(bufBytes) - err := Unpack(buf, &out) + buf.Reset() + buf.Write(bufBytes) + err := Unpack(&buf, &out) if err != nil { b.Fatal(err) } out.Data = nil } } - func BenchmarkStdlibDecode(b *testing.B) { var out BenchExample var buf bytes.Buffer - binary.Write(&buf, binary.BigEndian, *benchRef) - _, err := buf.Write(eightBytes) + binary.Write(&buf, binary.BigEndian, *testBenchExample) + _, err := buf.Write(testEightByteString) if err != nil { b.Fatal(err) } - bufBytes := buf.Bytes() + bufBytes := make([]byte, buf.Len()) + copy(bufBytes, buf.Bytes()) + b.ResetTimer() for i := 0; i < b.N; i++ { - buf := bytes.NewReader(bufBytes) - err := binary.Read(buf, binary.BigEndian, &out) + buf.Reset() + buf.Write(bufBytes) + err := binary.Read(&buf, binary.BigEndian, &out) if err != nil { b.Fatal(err) } @@ -165,7 +184,7 @@ func BenchmarkStdlibDecode(b *testing.B) { func BenchmarkManualDecode(b *testing.B) { var o BenchStrucExample var buf bytes.Buffer - if err := Pack(&buf, benchStrucRef); err != nil { + if err := Pack(&buf, testBenchStrucExample); err != nil { b.Fatal(err) } tmp := buf.Bytes() @@ -184,9 +203,11 @@ func BenchmarkManualDecode(b *testing.B) { } func BenchmarkFullEncode(b *testing.B) { + var buf bytes.Buffer + b.ResetTimer() for i := 0; i < b.N; i++ { - var buf bytes.Buffer - if err := Pack(&buf, reference); err != nil { + buf.Reset() + if err := Pack(&buf, testExample); err != nil { b.Fatal(err) } } @@ -194,10 +215,61 @@ func BenchmarkFullEncode(b *testing.B) { func BenchmarkFullDecode(b *testing.B) { var out Example + var buf bytes.Buffer + b.ResetTimer() for i := 0; i < b.N; i++ { - buf := bytes.NewBuffer(referenceBytes) - if err := Unpack(buf, &out); err != nil { + buf.Reset() + buf.Write(testExampleBytes) + if err := Unpack(&buf, &out); err != nil { + b.Fatal(err) + } + } +} + +func BenchmarkFieldPool(b *testing.B) { + type TestStruct struct { + A int `struc:"int32"` + B string `struc:"[16]byte"` + C float64 `struc:"float64"` + } + + data := &TestStruct{A: 1, B: "test", C: 3.14} + var buf bytes.Buffer + + b.ResetTimer() + for i := 0; i < b.N; i++ { + buf.Reset() + if err := Pack(&buf, data); err != nil { b.Fatal(err) } } } + +func BenchmarkGetFormatString(b *testing.B) { + b.Run("Simple", func(b *testing.B) { + type Simple struct { + A int32 + B string `struc:"[8]byte"` + C float64 + } + data := &Simple{A: 1, B: "test", C: 3.14} + + b.ResetTimer() + for i := 0; i < b.N; i++ { + _, err := GetFormatString(data) + if err != nil { + b.Fatal(err) + } + } + }) + + b.Run("Complex", func(b *testing.B) { + b.ResetTimer() + for i := 0; i < b.N; i++ { + _, err := GetFormatString(testBenchStrucExample) + if err != nil { + b.Fatal(err) + } + } + }) +} diff --git a/binary.go b/binary.go index 4899d08..da07c1b 100644 --- a/binary.go +++ b/binary.go @@ -4,49 +4,143 @@ import ( "encoding/binary" "io" "reflect" + "sync" ) -type byteWriter struct { - buf []byte - pos int +// binaryWriterPool 用于复用 binaryWriter 对象,减少内存分配和 GC 压力 +// 通过 sync.Pool 实现对象池化,提高性能 +// +// binaryWriterPool is used to reuse binaryWriter objects to reduce memory allocations and GC pressure +// Implements object pooling through sync.Pool to improve performance +var binaryWriterPool = sync.Pool{ + New: func() interface{} { + return &binaryWriter{} + }, } -func (b byteWriter) Write(p []byte) (int, error) { - capacity := len(b.buf) - b.pos - if capacity < len(p) { - p = p[:capacity] +// binaryWriter 实现了 io.Writer 接口,用于高效的字节写入 +// 通过内部缓冲区和位置指针管理写入操作 +// +// binaryWriter implements io.Writer interface for efficient byte writing +// Manages write operations through internal buffer and position pointer +type binaryWriter struct { + buf []byte // 内部缓冲区 / Internal buffer + pos int // 当前写入位置 / Current write position +} + +// Write 实现 io.Writer 接口,将字节切片写入缓冲区 +// 如果缓冲区容量不足,会截断写入数据 +// +// Write implements io.Writer interface, writes byte slice to buffer +// If buffer capacity is insufficient, write data will be truncated +func (b *binaryWriter) Write(p []byte) (int, error) { + // 计算剩余可写容量 + // Calculate remaining writable capacity + remainingCapacity := len(b.buf) - b.pos + if remainingCapacity < len(p) { + // 如果容量不足,截断写入数据 + // If capacity is insufficient, truncate write data + p = p[:remainingCapacity] } if len(p) > 0 { + // 复制数据并更新位置 + // Copy data and update position copy(b.buf[b.pos:], p) b.pos += len(p) } return len(p), nil } +// reset 重置 binaryWriter 的状态以便复用 +// 清空内部缓冲区和位置指针 +// +// reset resets the binaryWriter state for reuse +// Clears internal buffer and position pointer +func (b *binaryWriter) reset(buf []byte) { + // 重置缓冲区和位置指针 + // Reset buffer and position pointer + b.buf = buf + b.pos = 0 +} + +// getBinaryWriter 从对象池获取 binaryWriter 实例 +// 并初始化其内部缓冲区 +// +// getBinaryWriter gets a binaryWriter instance from the pool +// And initializes its internal buffer +func getBinaryWriter(buf []byte) *binaryWriter { + writer := binaryWriterPool.Get().(*binaryWriter) + writer.reset(buf) + return writer +} + +// putBinaryWriter 将 binaryWriter 实例放回对象池 +// 在放回前会清空其内部状态 +// +// putBinaryWriter puts the binaryWriter instance back to the pool +// Clears its internal state before returning +func putBinaryWriter(writer *binaryWriter) { + writer.reset(nil) + binaryWriterPool.Put(writer) +} + +// binaryFallback 提供二进制数据的基本处理能力 +// 用于处理不支持自定义序列化的类型 +// +// binaryFallback provides basic binary data processing capabilities +// Used to handle types that don't support custom serialization type binaryFallback reflect.Value +// String 返回二进制回退处理器的类型字符串 +// 用于调试和错误信息展示 +// +// String returns the type string of binary fallback handler +// Used for debugging and error message display func (b binaryFallback) String() string { - return b.String() + return reflect.Value(b).Type().String() } +// Sizeof 返回值的二进制大小 +// 使用 encoding/binary 包的 Size 函数计算 +// +// Sizeof returns the binary size of the value +// Uses encoding/binary package's Size function for calculation func (b binaryFallback) Sizeof(val reflect.Value, options *Options) int { return binary.Size(val.Interface()) } +// Pack 将值打包到缓冲区中 +// 使用 encoding/binary 包的 Write 函数进行序列化 +// +// Pack packs the value into the buffer +// Uses encoding/binary package's Write function for serialization func (b binaryFallback) Pack(buf []byte, val reflect.Value, options *Options) (int, error) { - tmp := byteWriter{buf: buf} - var order binary.ByteOrder = binary.BigEndian - if options.Order != nil { - order = options.Order + // 从对象池获取临时写入器 + // Get temporary writer from object pool + tempWriter := getBinaryWriter(buf) + defer putBinaryWriter(tempWriter) + + // 获取字节序,默认使用大端序 + // Get byte order, use big-endian by default + byteOrder := options.Order + if byteOrder == nil { + byteOrder = binary.BigEndian } - err := binary.Write(tmp, order, val.Interface()) - return tmp.pos, err + err := binary.Write(tempWriter, byteOrder, val.Interface()) + return tempWriter.pos, err } -func (b binaryFallback) Unpack(r io.Reader, val reflect.Value, options *Options) error { - var order binary.ByteOrder = binary.BigEndian - if options.Order != nil { - order = options.Order +// Unpack 从读取器中解包值 +// 使用 encoding/binary 包的 Read 函数进行反序列化 +// +// Unpack unpacks value from reader +// Uses encoding/binary package's Read function for deserialization +func (b binaryFallback) Unpack(reader io.Reader, val reflect.Value, options *Options) error { + // 获取字节序,默认使用大端序 + // Get byte order, use big-endian by default + byteOrder := options.Order + if byteOrder == nil { + byteOrder = binary.BigEndian } - return binary.Read(r, order, val.Interface()) + return binary.Read(reader, byteOrder, val.Interface()) } diff --git a/custom.go b/custom.go index c468dce..9e8ba9b 100644 --- a/custom.go +++ b/custom.go @@ -5,29 +5,64 @@ import ( "reflect" ) -type Custom interface { +// CustomBinaryer 定义了自定义类型的序列化和反序列化接口 +// 实现此接口的类型可以控制自己的二进制格式 +type CustomBinaryer interface { + // Pack 将数据打包到字节切片中 + // 参数: + // - p: 目标字节切片 + // - opt: 序列化选项 + // 返回: + // - int: 写入的字节数 + // - error: 错误信息 Pack(p []byte, opt *Options) (int, error) + + // Unpack 从 Reader 中读取并解包数据 + // 参数: + // - r: 数据源读取器 + // - length: 要读取的数据长度 + // - opt: 反序列化选项 + // 返回: + // - error: 错误信息 Unpack(r io.Reader, length int, opt *Options) error + + // Size 返回序列化后的数据大小 + // 参数: + // - opt: 序列化选项 + // 返回: + // - int: 序列化后的字节数 Size(opt *Options) int + + // String 返回类型的字符串表示 String() string } -type customFallback struct { - custom Custom +// customBinaryerFallback 提供了 Custom 接口的基本实现 +// 作为自定义类型序列化的回退处理器 +type customBinaryerFallback struct { + custom CustomBinaryer // 实际的自定义类型实例 } -func (c customFallback) Pack(p []byte, val reflect.Value, opt *Options) (int, error) { - return c.custom.Pack(p, opt) +// Pack 将自定义类型的值打包到缓冲区中 +// 直接调用底层自定义类型的 Pack 方法 +func (c customBinaryerFallback) Pack(buf []byte, val reflect.Value, options *Options) (int, error) { + return c.custom.Pack(buf, options) } -func (c customFallback) Unpack(r io.Reader, val reflect.Value, opt *Options) error { - return c.custom.Unpack(r, 1, opt) +// Unpack 从读取器中解包自定义类型的值 +// 调用底层自定义类型的 Unpack 方法,长度固定为1 +func (c customBinaryerFallback) Unpack(reader io.Reader, val reflect.Value, options *Options) error { + return c.custom.Unpack(reader, 1, options) } -func (c customFallback) Sizeof(val reflect.Value, opt *Options) int { - return c.custom.Size(opt) +// Sizeof 返回自定义类型值的大小 +// 直接调用底层自定义类型的 Size 方法 +func (c customBinaryerFallback) Sizeof(val reflect.Value, options *Options) int { + return c.custom.Size(options) } -func (c customFallback) String() string { +// String 返回自定义类型的字符串表示 +// 直接调用底层自定义类型的 String 方法 +func (c customBinaryerFallback) String() string { return c.custom.String() } diff --git a/custom_float16.go b/custom_float16.go index 722be76..e81f4b5 100644 --- a/custom_float16.go +++ b/custom_float16.go @@ -7,72 +7,112 @@ import ( "strconv" ) +// Float16 表示一个16位浮点数, 内部使用 float64 存储以获得更好的计算精度 +// 但在序列化和反序列化时使用16位格式 +// +// 格式 (IEEE 754-2008 binary16): +// 1 位: 符号位 (0=正数, 1=负数) +// 5 位: 指数位 (偏移值为15) +// 10 位: 小数位 (隐含前导1) type Float16 float64 -func (f *Float16) Pack(p []byte, opt *Options) (int, error) { - order := opt.Order - if order == nil { - order = binary.BigEndian +// float16SlicePool 为 Float16 操作提供线程安全的缓冲池 +// 使用 BytesSlicePool 管理共享字节切片,减少内存分配 +var float16SlicePool = NewBytesSlicePool(0) + +// Pack 将 Float16 值序列化为16位二进制格式 +// 二进制格式遵循 IEEE 754-2008 binary16 规范 +// 支持特殊值:±0, ±∞, NaN +func (f *Float16) Pack(buffer []byte, options *Options) (int, error) { + if len(buffer) < 2 { + return 0, io.ErrShortBuffer } - sign := uint16(0) - if *f < 0 { - sign = 1 + + byteOrder := options.Order + if byteOrder == nil { + byteOrder = binary.BigEndian } - var frac, exp uint16 - if math.IsInf(float64(*f), 0) { - exp = 0x1f - frac = 0 - } else if math.IsNaN(float64(*f)) { - exp = 0x1f - frac = 1 + + // 此转换基于 github.com/stdlib-js/math-float64-to-float16 + // 能正确处理特殊值并将次正规数刷新为零 + bits := math.Float64bits(float64(*f)) + + sign := uint16((bits >> 48) & 0x8000) + exp := int((bits >> 52) & 0x07FF) + mant := bits & 0x000FFFFFFFFFFFFF + + var res uint16 + if exp == 0x07FF { // NaN or Inf + res = sign | 0x7C00 + if mant != 0 { // NaN + res |= 0x0200 // 确保尾数非零 + } } else { - bits := math.Float64bits(float64(*f)) - exp64 := (bits >> 52) & 0x7ff - if exp64 != 0 { - exp = uint16((exp64 - 1023 + 15) & 0x1f) + // 重新偏移指数 + exp = exp - 1023 + 15 + if exp >= 0x1F { // 上溢 + res = sign | 0x7C00 + } else if exp <= 0 { // 下溢, 刷新为零 + res = sign + } else { // 正常数字 + mant >>= 42 + res = sign | uint16(exp<<10) | uint16(mant) } - frac = uint16((bits >> 42) & 0x3ff) } - var out uint16 - out |= sign << 15 - out |= exp << 10 - out |= frac & 0x3ff - order.PutUint16(p, out) + + byteOrder.PutUint16(buffer, res) return 2, nil } -func (f *Float16) Unpack(r io.Reader, length int, opt *Options) error { - order := opt.Order - if order == nil { - order = binary.BigEndian + +// Unpack 将16位二进制格式反序列化为 Float16 值 +// 二进制格式遵循 IEEE 754-2008 binary16 规范 +// 支持特殊值:±0, ±∞, NaN +func (f *Float16) Unpack(reader io.Reader, length int, options *Options) error { + // 从对象池获取缓冲区 + buffer := float16SlicePool.GetSlice(2) + + // 获取字节序,如果未指定则使用大端序 + byteOrder := options.Order + if byteOrder == nil { + byteOrder = binary.BigEndian } - var tmp [2]byte - if _, err := r.Read(tmp[:]); err != nil { + + // 读取2字节数据 + if _, err := io.ReadFull(reader, buffer); err != nil { return err } - val := order.Uint16(tmp[:2]) - sign := (val >> 15) & 1 - exp := int16((val >> 10) & 0x1f) - frac := val & 0x3ff - if exp == 0x1f { - if frac != 0 { - *f = Float16(math.NaN()) - } else { - *f = Float16(math.Inf(int(sign)*-2 + 1)) - } - } else { - var bits uint64 - bits |= uint64(sign) << 63 - bits |= uint64(frac) << 42 - if exp > 0 { - bits |= uint64(exp-15+1023) << 52 + + value := byteOrder.Uint16(buffer) + + sign := uint64(value>>15) & 1 + exp16 := (value >> 10) & 0x1f + mant16 := value & 0x3ff + + var bits64 uint64 + if exp16 == 0x1f { // Inf or NaN + bits64 = sign<<63 | uint64(0x7ff)<<52 + if mant16 != 0 { + bits64 |= 1 << 51 // 设为 quiet NaN } - *f = Float16(math.Float64frombits(bits)) + } else if exp16 == 0 { // 零或次正规数 (刷新为零) + bits64 = sign << 63 + } else { // 正常数字 + exp64 := uint64(exp16) + 1023 - 15 + mant64 := uint64(mant16) + bits64 = sign<<63 | exp64<<52 | mant64<<42 } + + *f = Float16(math.Float64frombits(bits64)) return nil } -func (f *Float16) Size(opt *Options) int { + +// Size 返回 Float16 的字节大小, 固定为2 +func (f *Float16) Size(options *Options) int { return 2 } + +// String 返回 Float16 值的字符串表示 +// 使用 'g' 格式和32位精度进行格式化 func (f *Float16) String() string { return strconv.FormatFloat(float64(*f), 'g', -1, 32) } diff --git a/errors.go b/errors.go new file mode 100644 index 0000000..f3546b0 --- /dev/null +++ b/errors.go @@ -0,0 +1,220 @@ +package struc + +import ( + "fmt" +) + +// Error 是 struc 包的自定义错误类型 +// 提供统一的错误处理接口 +type Error struct { + Code ErrorCode + Message string + Context map[string]interface{} +} + +// ErrorCode 定义了错误代码枚举 +type ErrorCode int + +const ( + // ErrInvalidType 无效类型错误 + ErrInvalidType ErrorCode = iota + 1 + + // ErrBufferTooSmall 缓冲区太小错误 + ErrBufferTooSmall + + // ErrUnsupportedType 不支持的类型错误 + ErrUnsupportedType + + // ErrInvalidOptions 无效配置选项错误 + ErrInvalidOptions + + // ErrFieldMismatch 字段不匹配错误 + ErrFieldMismatch + + // ErrCustomTypeFailed 自定义类型处理失败错误 + ErrCustomTypeFailed + + // ErrTypeRegistration 类型注册失败错误 + ErrTypeRegistration + + // ErrSizeCalculation 大小计算错误 + ErrSizeCalculation + + // ErrPackingFailed 打包失败错误 + ErrPackingFailed + + // ErrUnpackingFailed 解包失败错误 + ErrUnpackingFailed +) + +// errorMessages 定义了错误代码对应的错误消息 +var errorMessages = map[ErrorCode]string{ + ErrInvalidType: "invalid type", + ErrBufferTooSmall: "buffer too small", + ErrUnsupportedType: "unsupported type", + ErrInvalidOptions: "invalid options", + ErrFieldMismatch: "field mismatch", + ErrCustomTypeFailed: "custom type operation failed", + ErrTypeRegistration: "type registration failed", + ErrSizeCalculation: "size calculation failed", + ErrPackingFailed: "packing failed", + ErrUnpackingFailed: "unpacking failed", +} + +// NewError 创建一个新的错误 +func NewError(code ErrorCode, message string, context ...map[string]interface{}) *Error { + err := &Error{ + Code: code, + Message: message, + Context: make(map[string]interface{}), + } + + // 如果有上下文信息,合并到 Context 中 + if len(context) > 0 && context[0] != nil { + for k, v := range context[0] { + err.Context[k] = v + } + } + + return err +} + +// Error 实现 error 接口 +func (e *Error) Error() string { + if e.Message != "" { + return fmt.Sprintf("struc: %s", e.Message) + } + if msg, exists := errorMessages[e.Code]; exists { + return fmt.Sprintf("struc: %s", msg) + } + return fmt.Sprintf("struc: unknown error (code: %d)", e.Code) +} + +// WithContext 添加上下文信息 +func (e *Error) WithContext(key string, value interface{}) *Error { + e.Context[key] = value + return e +} + +// Unwrap 支持错误链 +func (e *Error) Unwrap() error { + return nil +} + +// ==================== 便捷错误创建函数 ==================== + +// ErrInvalidTypef 创建无效类型错误 +func ErrInvalidTypef(format string, args ...interface{}) *Error { + return NewError(ErrInvalidType, fmt.Sprintf(format, args...)) +} + +// ErrBufferTooSmallf 创建缓冲区太小错误 +func ErrBufferTooSmallf(format string, args ...interface{}) *Error { + return NewError(ErrBufferTooSmall, fmt.Sprintf(format, args...)) +} + +// ErrUnsupportedTypef 创建不支持的类型错误 +func ErrUnsupportedTypef(format string, args ...interface{}) *Error { + return NewError(ErrUnsupportedType, fmt.Sprintf(format, args...)) +} + +// ErrInvalidOptionsf 创建无效配置选项错误 +func ErrInvalidOptionsf(format string, args ...interface{}) *Error { + return NewError(ErrInvalidOptions, fmt.Sprintf(format, args...)) +} + +// ErrFieldMismatchf 创建字段不匹配错误 +func ErrFieldMismatchf(format string, args ...interface{}) *Error { + return NewError(ErrFieldMismatch, fmt.Sprintf(format, args...)) +} + +// ErrCustomTypeFailedf 创建自定义类型处理失败错误 +func ErrCustomTypeFailedf(format string, args ...interface{}) *Error { + return NewError(ErrCustomTypeFailed, fmt.Sprintf(format, args...)) +} + +// ErrTypeRegistrationf 创建类型注册失败错误 +func ErrTypeRegistrationf(format string, args ...interface{}) *Error { + return NewError(ErrTypeRegistration, fmt.Sprintf(format, args...)) +} + +// ErrSizeCalculationf 创建大小计算错误 +func ErrSizeCalculationf(format string, args ...interface{}) *Error { + return NewError(ErrSizeCalculation, fmt.Sprintf(format, args...)) +} + +// ErrPackingFailedf 创建打包失败错误 +func ErrPackingFailedf(format string, args ...interface{}) *Error { + return NewError(ErrPackingFailed, fmt.Sprintf(format, args...)) +} + +// ErrUnpackingFailedf 创建解包失败错误 +func ErrUnpackingFailedf(format string, args ...interface{}) *Error { + return NewError(ErrUnpackingFailed, fmt.Sprintf(format, args...)) +} + +// ==================== 错误检查工具函数 ==================== + +// IsInvalidType 检查是否为无效类型错误 +func IsInvalidType(err error) bool { + if e, ok := err.(*Error); ok { + return e.Code == ErrInvalidType + } + return false +} + +// IsBufferTooSmall 检查是否为缓冲区太小错误 +func IsBufferTooSmall(err error) bool { + if e, ok := err.(*Error); ok { + return e.Code == ErrBufferTooSmall + } + return false +} + +// IsUnsupportedType 检查是否为不支持的类型错误 +func IsUnsupportedType(err error) bool { + if e, ok := err.(*Error); ok { + return e.Code == ErrUnsupportedType + } + return false +} + +// IsInvalidOptions 检查是否为无效配置选项错误 +func IsInvalidOptions(err error) bool { + if e, ok := err.(*Error); ok { + return e.Code == ErrInvalidOptions + } + return false +} + +// IsFieldMismatch 检查是否为字段不匹配错误 +func IsFieldMismatch(err error) bool { + if e, ok := err.(*Error); ok { + return e.Code == ErrFieldMismatch + } + return false +} + +// IsCustomTypeFailed 检查是否为自定义类型处理失败错误 +func IsCustomTypeFailed(err error) bool { + if e, ok := err.(*Error); ok { + return e.Code == ErrCustomTypeFailed + } + return false +} + +// ==================== 错误包装函数 ==================== + +// WrapError 包装现有错误为 struc 错误 +func WrapError(code ErrorCode, err error, message string) *Error { + if message == "" { + message = err.Error() + } + return NewError(code, message).WithContext("wrapped_error", err) +} + +// WrapErrorf 包装现有错误为 struc 错误(格式化) +func WrapErrorf(code ErrorCode, err error, format string, args ...interface{}) *Error { + message := fmt.Sprintf(format, args...) + return WrapError(code, err, message) +} \ No newline at end of file diff --git a/field.go b/field.go index b1f766b..05c951a 100644 --- a/field.go +++ b/field.go @@ -1,288 +1,518 @@ +// Package struc implements binary packing and unpacking for Go structs. +// struc 包实现了 Go 结构体的二进制打包和解包功能。 package struc import ( "bytes" "encoding/binary" "fmt" - "math" "reflect" + "unsafe" ) +// Field 表示结构体中的单个字段 +// 包含了字段的所有元数据信息,用于二进制打包和解包 type Field struct { - Name string - Ptr bool - Index int - Type Type - defType Type - Array bool - Slice bool - Len int - Order binary.ByteOrder - Sizeof []int - Sizefrom []int - Fields Fields - kind reflect.Kind + Name string // 字段名称 + IsPointer bool // 字段是否为指针类型 + Index int // 字段在结构体中的索引 + Type Type // 字段的二进制类型 + defType Type // 默认的二进制类型 + IsArray bool // 字段是否为数组 + IsSlice bool // 字段是否为切片 + Length int // 数组/固定切片的长度 + ByteOrder binary.ByteOrder // 字段的字节序 + Sizeof []int // sizeof 引用的字段索引 + Sizefrom []int // 大小引用的字段索引 + NestFields Fields // 嵌套结构体的字段 + kind reflect.Kind // Go 的反射类型 } +// ==================== 基础工具函数 ==================== + +// String 返回字段的字符串表示, 用于调试和日志记录 func (f *Field) String() string { - var out string + // 处理空字段或无效类型 + if f.Type == Invalid { + return "{type: invalid, len: 0}" + } + if f.Type == Pad { - return fmt.Sprintf("{type: Pad, len: %d}", f.Len) - } else { - out = fmt.Sprintf("type: %s, order: %v", f.Type.String(), f.Order) + return fmt.Sprintf("{type: %s, len: %d}", f.Type, f.Length) + } + + buffer := acquireBuffer() + defer releaseBuffer(buffer) + + buffer.WriteString("{") + fmt.Fprintf(buffer, "type: %s", f.Type) + + if f.ByteOrder != nil { + fmt.Fprintf(buffer, ", order: %v", f.ByteOrder) } if f.Sizefrom != nil { - out += fmt.Sprintf(", sizefrom: %v", f.Sizefrom) - } else if f.Len > 0 { - out += fmt.Sprintf(", len: %d", f.Len) + fmt.Fprintf(buffer, ", sizefrom: %v", f.Sizefrom) + } else if f.Length > 0 { + fmt.Fprintf(buffer, ", len: %d", f.Length) } if f.Sizeof != nil { - out += fmt.Sprintf(", sizeof: %v", f.Sizeof) + fmt.Fprintf(buffer, ", sizeof: %v", f.Sizeof) } - return "{" + out + "}" + buffer.WriteString("}") + + return buffer.String() } -func (f *Field) Size(val reflect.Value, options *Options) int { - typ := f.Type.Resolve(options) - size := 0 - if typ == Struct { - vals := []reflect.Value{val} - if f.Slice { - vals = make([]reflect.Value, val.Len()) - for i := 0; i < val.Len(); i++ { - vals[i] = val.Index(i) - } +// determineByteOrder 返回要使用的字节序 +// 优先使用选项中指定的字节序,否则使用字段自身的字节序 +func (f *Field) determineByteOrder(options *Options) binary.ByteOrder { + if options.Order != nil { + return options.Order + } + return f.ByteOrder +} + +// getIntegerValue 从 reflect.Value 中提取整数值 +// 处理布尔值、有符号和无符号整数 +func (f *Field) getIntegerValue(fieldValue reflect.Value) uint64 { + switch f.kind { + case reflect.Bool: + if fieldValue.Bool() { + return 1 } - for _, val := range vals { - size += f.Fields.Sizeof(val, options) + return 0 + case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: + return uint64(fieldValue.Int()) + default: + return fieldValue.Uint() + } +} + +// ==================== 大小计算相关函数 ==================== + +// Size 计算字段在二进制格式中占用的字节数 +// 考虑了对齐和填充要求 +func (f *Field) Size(fieldValue reflect.Value, options *Options) int { + resolvedType := f.Type.Resolve(options) + totalSize := 0 + + switch resolvedType { + case Struct: + totalSize = f.calculateStructSize(fieldValue, options) + case Pad: + totalSize = f.Length + case CustomType: + totalSize = f.calculateCustomSize(fieldValue, options) + default: + totalSize = f.calculateBasicSize(fieldValue, resolvedType, options) + } + + return f.alignSize(totalSize, options) +} + +// calculateStructSize 计算结构体类型的字节大小 +// 处理普通结构体和结构体切片 +func (f *Field) calculateStructSize(fieldValue reflect.Value, options *Options) int { + if f.IsSlice { + sliceLength := fieldValue.Len() + totalSize := 0 + for i := 0; i < sliceLength; i++ { + totalSize += f.NestFields.Sizeof(fieldValue.Index(i), options) } - } else if typ == Pad { - size = f.Len - } else if typ == CustomType { - return val.Addr().Interface().(Custom).Size(options) - } else if f.Slice || f.kind == reflect.String { - length := val.Len() - if f.Len > 1 { - length = f.Len + return totalSize + } + return f.NestFields.Sizeof(fieldValue, options) +} + +// calculateCustomSize 计算自定义类型的字节大小 +// 通过调用类型的 Size 方法获取 +func (f *Field) calculateCustomSize(fieldValue reflect.Value, options *Options) int { + if customType, ok := fieldValue.Addr().Interface().(CustomBinaryer); ok { + return customType.Size(options) + } + return 0 +} + +// calculateBasicSize 计算基本类型的字节大小 +// 处理固定大小类型和变长类型(如切片和字符串) +func (f *Field) calculateBasicSize(fieldValue reflect.Value, resolvedType Type, options *Options) int { + elementSize := resolvedType.Size() + if f.IsSlice || f.kind == reflect.String { + length := fieldValue.Len() + if f.Length > 1 { + length = f.Length // 使用指定的固定长度 } - size = length * typ.Size() - } else { - size = typ.Size() + return length * elementSize } - align := options.ByteAlign - if align > 0 && size < align { - size = align + return elementSize +} + +// alignSize 根据 ByteAlign 选项对齐大小 +// 确保字段按指定的字节边界对齐 +func (f *Field) alignSize(size int, options *Options) int { + if alignment := options.ByteAlign; alignment > 0 { + if remainder := size % alignment; remainder != 0 { + size += alignment - remainder + } } return size } -func (f *Field) packVal(buf []byte, val reflect.Value, length int, options *Options) (size int, err error) { - order := f.Order - if options.Order != nil { - order = options.Order +// ==================== 打包相关函数 ==================== + +// Pack 将字段值打包到缓冲区中 +// 处理所有类型的字段,包括填充、切片和单个值 +func (f *Field) Pack(buffer []byte, fieldValue reflect.Value, length int, options *Options) (int, error) { + if resolvedType := f.Type.Resolve(options); resolvedType == Pad { + return f.packPaddingBytes(buffer, length) } - if f.Ptr { - val = val.Elem() + + if f.IsSlice { + return f.packSliceValue(buffer, fieldValue, length, options) } - typ := f.Type.Resolve(options) - switch typ { - case Struct: - return f.Fields.Pack(buf, val, options) - case Bool, Int8, Int16, Int32, Int64, Uint8, Uint16, Uint32, Uint64: - size = typ.Size() - var n uint64 - switch f.kind { - case reflect.Bool: - if val.Bool() { - n = 1 - } else { - n = 0 + return f.packSingleValue(buffer, fieldValue, length, options) +} + +// packSingleValue 将单个值打包到缓冲区中 +func (f *Field) packSingleValue(buffer []byte, fieldValue reflect.Value, length int, options *Options) (size int, err error) { + byteOrder := f.determineByteOrder(options) + if f.IsPointer { + fieldValue = fieldValue.Elem() + } + + resolvedType := f.Type.Resolve(options) + + // 优化: 对基本类型进行快速处理 + if resolvedType.IsBasicType() { + elementSize := resolvedType.Size() + switch resolvedType { + case Bool, Int8, Int16, Int32, Int64, Uint8, Uint16, Uint32, Uint64: + // 处理整数和布尔类型 + intValue := f.getIntegerValue(fieldValue) + if err := f.writeInteger(buffer, intValue, resolvedType, byteOrder); err != nil { + return 0, fmt.Errorf("failed to write integer: %w", err) } - case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: - n = uint64(val.Int()) - default: - n = val.Uint() - } - switch typ { - case Bool: - if n != 0 { - buf[0] = 1 - } else { - buf[0] = 0 + return elementSize, nil + case Float32, Float64: + // 处理浮点数类型 + floatValue := fieldValue.Float() + if err := f.writeFloat(buffer, floatValue, resolvedType, byteOrder); err != nil { + return 0, fmt.Errorf("failed to write float: %w", err) } - case Int8, Uint8: - buf[0] = byte(n) - case Int16, Uint16: - order.PutUint16(buf, uint16(n)) - case Int32, Uint32: - order.PutUint32(buf, uint32(n)) - case Int64, Uint64: - order.PutUint64(buf, uint64(n)) - } - case Float32, Float64: - size = typ.Size() - n := val.Float() - switch typ { - case Float32: - order.PutUint32(buf, math.Float32bits(float32(n))) - case Float64: - order.PutUint64(buf, math.Float64bits(n)) + return elementSize, nil } + } + + switch resolvedType { + case Struct: + return f.NestFields.Pack(buffer, fieldValue, options) case String: - switch f.kind { - case reflect.String: - size = val.Len() - copy(buf, []byte(val.String())) - default: - // TODO: handle kind != bytes here - size = val.Len() - copy(buf, val.Bytes()) - } + return f.packString(buffer, fieldValue) case CustomType: - return val.Addr().Interface().(Custom).Pack(buf, options) + return f.packCustom(buffer, fieldValue, options) default: - panic(fmt.Sprintf("no pack handler for type: %s", typ)) + return 0, fmt.Errorf("unsupported type for packing: %v", resolvedType) } - return } -func (f *Field) Pack(buf []byte, val reflect.Value, length int, options *Options) (int, error) { - typ := f.Type.Resolve(options) - if typ == Pad { - for i := 0; i < length; i++ { - buf[i] = 0 +// packPaddingBytes 打包填充字节到缓冲区 +// 使用 memclr 快速将指定长度的空间清零 +func (f *Field) packPaddingBytes(buffer []byte, length int) (int, error) { + memclr(buffer[:length]) + return length, nil +} + +// packString 打包字符串或字节切片到缓冲区 +func (f *Field) packString(buffer []byte, fieldValue reflect.Value) (int, error) { + var data []byte + switch f.kind { + case reflect.String: + data = []byte(fieldValue.String()) + default: + data = fieldValue.Bytes() + } + dataSize := len(data) + copy(buffer, data) + return dataSize, nil +} + +// packCustom 打包自定义类型到缓冲区 +// 通过调用类型的 Pack 方法实现 +func (f *Field) packCustom(buffer []byte, fieldValue reflect.Value, options *Options) (int, error) { + if customType, ok := fieldValue.Addr().Interface().(CustomBinaryer); ok { + return customType.Pack(buffer, options) + } + return 0, fmt.Errorf("failed to pack custom type: %v", fieldValue.Type()) +} + +// packSliceValue 打包切片值到缓冲区 +// 处理字节切片和其他类型的切片 +func (f *Field) packSliceValue(buffer []byte, fieldValue reflect.Value, length int, options *Options) (int, error) { + resolvedType := f.Type.Resolve(options) + byteOrder := f.determineByteOrder(options) + elementSize := resolvedType.Size() + dataLength := fieldValue.Len() + totalSize := length * elementSize + + // 对字节切片和字符串类型进行优化处理 + if !f.IsArray && resolvedType == Uint8 && (f.defType == Uint8 || f.kind == reflect.String) { + var data []byte + if f.kind == reflect.String { + data = []byte(fieldValue.String()) + } else { + data = fieldValue.Bytes() + } + copy(buffer, data) + if dataLength < length { + memclr(buffer[dataLength:totalSize]) } - return length, nil - } - if f.Slice { - // special case strings and byte slices for performance - end := val.Len() - if !f.Array && typ == Uint8 && (f.defType == Uint8 || f.kind == reflect.String) { - var tmp []byte - if f.kind == reflect.String { - tmp = []byte(val.String()) - } else { - tmp = val.Bytes() + return totalSize, nil + } + + // 对基本类型进行优化处理 + if resolvedType.IsBasicType() && !f.IsArray { + // 如果是小端序或没有指定字节序,可以直接复制 + if byteOrder == nil || byteOrder == binary.LittleEndian { + if dataLength > 0 { + typedmemmove( + unsafe.Pointer(&buffer[0]), + unsafe.Pointer(fieldValue.Pointer()), + uintptr(dataLength*elementSize), + ) } - copy(buf, tmp) - if end < length { - // TODO: allow configuring pad byte? - rep := bytes.Repeat([]byte{0}, length-end) - copy(buf[end:], rep) - return length, nil + if dataLength < length { + memclr(buffer[dataLength*elementSize : totalSize]) } - return val.Len(), nil - } - pos := 0 - var zero reflect.Value - if end < length { - zero = reflect.Zero(val.Type().Elem()) + return totalSize, nil } + + // 对于大端序的基本类型,需要逐个处理字节序 for i := 0; i < length; i++ { - cur := zero - if i < end { - cur = val.Index(i) + pos := i * elementSize + var value uint64 + if i < dataLength { + elem := fieldValue.Index(i) + value = f.getIntegerValue(elem) } - if n, err := f.packVal(buf[pos:], cur, 1, options); err != nil { - return pos, err - } else { - pos += n + if err := f.writeInteger(buffer[pos:], value, resolvedType, byteOrder); err != nil { + return 0, fmt.Errorf("failed to pack slice element %d: %w", i, err) } } - return pos, nil - } else { - return f.packVal(buf, val, length, options) + return totalSize, nil } -} -func (f *Field) unpackVal(buf []byte, val reflect.Value, length int, options *Options) error { - order := f.Order - if options.Order != nil { - order = options.Order - } - if f.Ptr { - val = val.Elem() - } - typ := f.Type.Resolve(options) - switch typ { - case Float32, Float64: - var n float64 - switch typ { - case Float32: - n = float64(math.Float32frombits(order.Uint32(buf))) - case Float64: - n = math.Float64frombits(order.Uint64(buf)) - } - switch f.kind { - case reflect.Float32, reflect.Float64: - val.SetFloat(n) - default: - return fmt.Errorf("struc: refusing to unpack float into field %s of type %s", f.Name, f.kind.String()) + // 对于复杂类型(结构体、自定义类型等),仍然需要逐个处理 + position := 0 + var zeroValue reflect.Value + if dataLength < length { + zeroValue = reflect.Zero(fieldValue.Type().Elem()) + } + + for i := 0; i < length; i++ { + currentValue := zeroValue + if i < dataLength { + currentValue = fieldValue.Index(i) } - case Bool, Int8, Int16, Int32, Int64, Uint8, Uint16, Uint32, Uint64: - var n uint64 - switch typ { - case Int8: - n = uint64(int64(int8(buf[0]))) - case Int16: - n = uint64(int64(int16(order.Uint16(buf)))) - case Int32: - n = uint64(int64(int32(order.Uint32(buf)))) - case Int64: - n = uint64(int64(order.Uint64(buf))) - case Bool, Uint8: - n = uint64(buf[0]) - case Uint16: - n = uint64(order.Uint16(buf)) - case Uint32: - n = uint64(order.Uint32(buf)) - case Uint64: - n = uint64(order.Uint64(buf)) + bytesWritten, err := f.packSingleValue(buffer[position:], currentValue, 1, options) + if err != nil { + return position, fmt.Errorf("failed to pack slice element %d: %w", i, err) } - switch f.kind { - case reflect.Bool: - val.SetBool(n != 0) - case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: - val.SetInt(int64(n)) - default: - val.SetUint(n) + position += bytesWritten + } + + return position, nil +} + +// writeInteger 将整数值写入缓冲区 +// 支持所有整数类型和布尔类型的写入 +func (f *Field) writeInteger(buffer []byte, intValue uint64, resolvedType Type, byteOrder binary.ByteOrder) error { + switch resolvedType { + case Bool: + if intValue != 0 { + buffer[0] = 1 + } else { + buffer[0] = 0 } + case Int8, Uint8: + buffer[0] = byte(intValue) + case Int16, Uint16: + unsafePutUint16(buffer, uint16(intValue), byteOrder) + case Int32, Uint32: + unsafePutUint32(buffer, uint32(intValue), byteOrder) + case Int64, Uint64: + unsafePutUint64(buffer, intValue, byteOrder) default: - panic(fmt.Sprintf("no unpack handler for type: %s", typ)) + return fmt.Errorf("unsupported integer type: %v", resolvedType) } return nil } -func (f *Field) Unpack(buf []byte, val reflect.Value, length int, options *Options) error { - typ := f.Type.Resolve(options) - if typ == Pad || f.kind == reflect.String { - if typ == Pad { - return nil +// writeFloat 将浮点数值写入缓冲区 +// 根据类型(Float32/Float64)将浮点数转换为对应的二进制格式 +// 使用指定的字节序写入缓冲区 +func (f *Field) writeFloat(buffer []byte, floatValue float64, resolvedType Type, byteOrder binary.ByteOrder) error { + switch resolvedType { + case Float32: + unsafePutFloat32(buffer, float32(floatValue), byteOrder) + case Float64: + unsafePutFloat64(buffer, floatValue, byteOrder) + default: + return fmt.Errorf("unsupported float type: %v", resolvedType) + } + return nil +} + +// ==================== 解包相关函数 ==================== + +// Unpack 从缓冲区中解包字段值 +// 处理所有类型的字段值的解包 +func (f *Field) Unpack(buffer []byte, fieldValue reflect.Value, length int, options *Options) error { + resolvedType := f.Type.Resolve(options) + + if resolvedType == Pad || f.kind == reflect.String { + return f.unpackPaddingOrStringValue(buffer, fieldValue, resolvedType) + } + + if f.IsSlice { + return f.unpackSliceValue(buffer, fieldValue, length, options) + } + + return f.unpackSingleValue(buffer, fieldValue, length, options) +} + +// unpackPaddingOrStringValue 处理填充或字符串类型的解包 +// 忽略填充类型,将字节数据转换为字符串 +func (f *Field) unpackPaddingOrStringValue(buffer []byte, fieldValue reflect.Value, resolvedType Type) error { + if resolvedType == Pad { + return nil + } + unsafeSetString(fieldValue, buffer, len(buffer)) + return nil +} + +// unpackSliceValue 处理切片类型的解包 +// 使用 unsafe 优化切片处理,减少内存拷贝 +func (f *Field) unpackSliceValue(buffer []byte, fieldValue reflect.Value, length int, options *Options) error { + resolvedType := f.Type.Resolve(options) + byteOrder := f.determineByteOrder(options) + + if !f.IsArray && resolvedType == Uint8 && (f.defType == Uint8 || f.kind == reflect.String) { + if f.kind == reflect.String { + unsafeSetString(fieldValue, buffer, length) } else { - val.SetString(string(buf)) - return nil + unsafeSetSlice(fieldValue, buffer, length) + } + return nil + } + + if !f.IsArray { + if fieldValue.Cap() < length { + fieldValue.Set(reflect.MakeSlice(fieldValue.Type(), length, length)) + } else if fieldValue.Len() < length { + fieldValue.Set(fieldValue.Slice(0, length)) } - } else if f.Slice { - if val.Cap() < length { - val.Set(reflect.MakeSlice(val.Type(), length, length)) - } else if val.Len() < length { - val.Set(val.Slice(0, length)) + } + + if resolvedType.IsBasicType() && (byteOrder == nil || byteOrder == binary.LittleEndian) { + unsafeMoveSlice(fieldValue, reflect.ValueOf(buffer)) + return nil + } + + // 对于其他情况,逐个处理元素 + elementSize := resolvedType.Size() + for i := 0; i < length; i++ { + elementValue := fieldValue.Index(i) + pos := i * elementSize + if err := f.unpackSingleValue(buffer[pos:pos+elementSize], elementValue, elementSize, options); err != nil { + return fmt.Errorf("failed to unpack slice element %d: %w", i, err) } - // special case byte slices for performance - if !f.Array && typ == Uint8 && f.defType == Uint8 { - copy(val.Bytes(), buf[:length]) + } + + return nil +} + +// unpackSingleValue 从缓冲区中解包单个值 +func (f *Field) unpackSingleValue(buffer []byte, fieldValue reflect.Value, length int, options *Options) error { + byteOrder := f.determineByteOrder(options) + if f.IsPointer { + fieldValue = fieldValue.Elem() + } + + resolvedType := f.Type.Resolve(options) + + // 优化: 对基本类型进行快速处理 + if resolvedType.IsBasicType() { + switch resolvedType { + case Float32, Float64: + // 处理浮点数类型 + var floatValue float64 + switch resolvedType { + case Float32: + floatValue = float64(unsafeGetFloat32(buffer, byteOrder)) + case Float64: + floatValue = unsafeGetFloat64(buffer, byteOrder) + } + if f.kind == reflect.Float32 || f.kind == reflect.Float64 { + fieldValue.SetFloat(floatValue) + return nil + } + return fmt.Errorf("struc: refusing to unpack float into field %s of type %s", f.Name, f.kind.String()) + case Bool, Int8, Int16, Int32, Int64, Uint8, Uint16, Uint32, Uint64: + // 处理整数和布尔类型 + intValue := f.readInteger(buffer, resolvedType, byteOrder) + switch f.kind { + case reflect.Bool: + fieldValue.SetBool(intValue != 0) + case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: + fieldValue.SetInt(int64(intValue)) + default: + fieldValue.SetUint(intValue) + } return nil } - pos := 0 - size := typ.Size() - for i := 0; i < length; i++ { - if err := f.unpackVal(buf[pos:pos+size], val.Index(i), 1, options); err != nil { - return err - } - pos += size + } + + switch resolvedType { + case Struct: + return f.NestFields.Unpack(bytes.NewReader(buffer), fieldValue, options) + case String: + if f.kind != reflect.String { + return fmt.Errorf("cannot unpack string into field %s of type %s", f.Name, f.kind) } + str := unsafeBytes2String(buffer[:length]) + fieldValue.SetString(str) return nil - } else { - return f.unpackVal(buf, val, length, options) + case CustomType: + if customType, ok := fieldValue.Addr().Interface().(CustomBinaryer); ok { + return customType.Unpack(bytes.NewReader(buffer), length, options) + } + return fmt.Errorf("failed to unpack custom type: %v", fieldValue.Type()) + default: + return fmt.Errorf("unsupported type for unpacking: %v", resolvedType) + } +} + +// readInteger 从缓冲区读取整数值 +// 支持所有整数类型的读取,包括有符号和无符号类型 +func (f *Field) readInteger(buffer []byte, resolvedType Type, byteOrder binary.ByteOrder) uint64 { + switch resolvedType { + case Int8: + return uint64(int64(int8(buffer[0]))) + case Int16: + return uint64(int64(int16(unsafeGetUint16(buffer, byteOrder)))) + case Int32: + return uint64(int64(int32(unsafeGetUint32(buffer, byteOrder)))) + case Int64: + return uint64(int64(unsafeGetUint64(buffer, byteOrder))) + case Bool, Uint8: + return uint64(buffer[0]) + case Uint16: + return uint64(unsafeGetUint16(buffer, byteOrder)) + case Uint32: + return uint64(unsafeGetUint32(buffer, byteOrder)) + case Uint64: + return unsafeGetUint64(buffer, byteOrder) + default: + return 0 } } diff --git a/field_descriptor.go b/field_descriptor.go new file mode 100644 index 0000000..f0dc07c --- /dev/null +++ b/field_descriptor.go @@ -0,0 +1,79 @@ +package struc + +import ( + "encoding/binary" + "reflect" +) + +// FieldDescriptor 表示结构体字段的元数据信息 +// 包含字段的所有描述信息,用于二进制打包和解包 +// 这是Field类职责分离后的第一个组件,专门负责字段描述 +// 保持与现有Field类相同的字段定义,确保向后兼容 +type FieldDescriptor struct { + // 字段基本信息 + Name string // 字段名称 + IsPointer bool // 字段是否为指针类型 + Index int // 字段在结构体中的索引 + Type Type // 字段的二进制类型 + defType Type // 默认的二进制类型 + IsArray bool // 字段是否为数组 + IsSlice bool // 字段是否为切片 + Length int // 数组/固定切片的长度 + ByteOrder binary.ByteOrder // 字段的字节序 + Sizeof []int // sizeof 引用的字段索引 + Sizefrom []int // 大小引用的字段索引 + NestFields Fields // 嵌套结构体的字段 + kind reflect.Kind // Go 的反射类型 +} + +// NewFieldDescriptor 创建一个新的字段描述符 +// 使用与现有Field类相同的字段初始化逻辑 +func NewFieldDescriptor() *FieldDescriptor { + return &FieldDescriptor{ + Length: 1, + ByteOrder: binary.BigEndian, // 默认使用大端字节序 + } +} + +// CopyFrom 从现有Field对象复制字段描述信息 +// 用于在重构过程中保持字段定义的一致性 +func (fd *FieldDescriptor) CopyFrom(field *Field) { + fd.Name = field.Name + fd.IsPointer = field.IsPointer + fd.Index = field.Index + fd.Type = field.Type + fd.defType = field.defType + fd.IsArray = field.IsArray + fd.IsSlice = field.IsSlice + fd.Length = field.Length + fd.ByteOrder = field.ByteOrder + fd.Sizeof = field.Sizeof + fd.Sizefrom = field.Sizefrom + fd.NestFields = field.NestFields + fd.kind = field.kind +} + +// GetKind 返回字段的反射类型 +func (fd *FieldDescriptor) GetKind() reflect.Kind { + return fd.kind +} + +// GetType 返回字段的二进制类型 +func (fd *FieldDescriptor) GetType() Type { + return fd.Type +} + +// IsCustomType 检查字段是否为自定义类型 +func (fd *FieldDescriptor) IsCustomType() bool { + return fd.Type == CustomType +} + +// IsStructType 检查字段是否为结构体类型 +func (fd *FieldDescriptor) IsStructType() bool { + return fd.Type == Struct +} + +// GetLength 返回字段的长度 +func (fd *FieldDescriptor) GetLength() int { + return fd.Length +} \ No newline at end of file diff --git a/field_facade.go b/field_facade.go new file mode 100644 index 0000000..2f9d0fc --- /dev/null +++ b/field_facade.go @@ -0,0 +1,61 @@ +package struc + +import ( + "reflect" +) + +// FieldFacade 是 Field 类的外观模式实现 +// 通过组合各个职责分离的组件,提供统一的 Field 接口 +// 保持与现有 Field 类相同的接口,确保向后兼容 +type FieldFacade struct { + descriptor *FieldDescriptor + sizeCalculator FieldSizeCalculator + packer FieldPacker + unpacker FieldUnpacker +} + +// NewFieldFacade 创建一个新的 Field 外观类 +func NewFieldFacade(descriptor *FieldDescriptor) *FieldFacade { + return &FieldFacade{ + descriptor: descriptor, + sizeCalculator: NewFieldSizeCalculator(descriptor), + packer: NewFieldPacker(descriptor), + unpacker: NewFieldUnpacker(descriptor), + } +} + +// Size 计算字段在二进制格式中占用的字节数 +// 考虑了对齐和填充要求 +func (f *FieldFacade) Size(fieldValue reflect.Value, options *Options) int { + return f.sizeCalculator.Size(fieldValue, options) +} + +// Pack 将字段值序列化到字节缓冲区中 +func (f *FieldFacade) Pack(buffer []byte, fieldValue reflect.Value, length int, options *Options) (int, error) { + return f.packer.Pack(buffer, fieldValue, length, options) +} + +// Unpack 从字节缓冲区中解包字段值 +func (f *FieldFacade) Unpack(buffer []byte, fieldValue reflect.Value, length int, options *Options) error { + return f.unpacker.Unpack(buffer, fieldValue, length, options) +} + +// GetDescriptor 返回字段描述符 +func (f *FieldFacade) GetDescriptor() *FieldDescriptor { + return f.descriptor +} + +// GetSizeCalculator 返回大小计算器 +func (f *FieldFacade) GetSizeCalculator() FieldSizeCalculator { + return f.sizeCalculator +} + +// GetPacker 返回打包器 +func (f *FieldFacade) GetPacker() FieldPacker { + return f.packer +} + +// GetUnpacker 返回解包器 +func (f *FieldFacade) GetUnpacker() FieldUnpacker { + return f.unpacker +} \ No newline at end of file diff --git a/field_packer.go b/field_packer.go new file mode 100644 index 0000000..b6add07 --- /dev/null +++ b/field_packer.go @@ -0,0 +1,208 @@ +package struc + +import ( + "encoding/binary" + "fmt" + "reflect" +) + +// FieldPacker 定义了字段打包的接口 +// 这是Field类职责分离后的第三个组件,专门负责打包逻辑 +// 包含所有与字段打包相关的方法 +type FieldPacker interface { + // Pack 将字段值序列化到字节缓冲区中 + Pack(buffer []byte, fieldValue reflect.Value, length int, options *Options) (int, error) + + // PackSingleValue 打包单个字段值 + PackSingleValue(buffer []byte, fieldValue reflect.Value, length int, options *Options) (int, error) + + // PackSliceValue 打包切片字段值 + PackSliceValue(buffer []byte, fieldValue reflect.Value, length int, options *Options) (int, error) + + // PackString 打包字符串字段值 + PackString(buffer []byte, fieldValue reflect.Value) (int, error) + + // PackCustom 打包自定义类型字段值 + PackCustom(buffer []byte, fieldValue reflect.Value, options *Options) (int, error) + + // WriteInteger 写入整数值到缓冲区 + WriteInteger(buffer []byte, intValue uint64, resolvedType Type, byteOrder binary.ByteOrder) error + + // WriteFloat 写入浮点数值到缓冲区 + WriteFloat(buffer []byte, floatValue float64, resolvedType Type, byteOrder binary.ByteOrder) error + + // PackPaddingBytes 打包填充字节 + PackPaddingBytes(buffer []byte, length int) (int, error) +} + +// DefaultFieldPacker 是FieldPacker的默认实现 +// 包含从现有Field类迁移的打包逻辑 +type DefaultFieldPacker struct { + descriptor *FieldDescriptor +} + +// NewFieldPacker 创建一个新的字段打包器 +func NewFieldPacker(descriptor *FieldDescriptor) FieldPacker { + return &DefaultFieldPacker{ + descriptor: descriptor, + } +} + +// Pack 将字段值序列化到字节缓冲区中 +func (p *DefaultFieldPacker) Pack(buffer []byte, fieldValue reflect.Value, length int, options *Options) (int, error) { + if p.descriptor.IsSlice { + return p.PackSliceValue(buffer, fieldValue, length, options) + } + return p.PackSingleValue(buffer, fieldValue, length, options) +} + +// PackSingleValue 打包单个字段值 +func (p *DefaultFieldPacker) PackSingleValue(buffer []byte, fieldValue reflect.Value, length int, options *Options) (int, error) { + resolvedType := p.descriptor.Type.Resolve(options) + byteOrder := p.determineByteOrder(options) + + switch resolvedType { + case Pad: + return p.PackPaddingBytes(buffer, length) + case String: + return p.PackString(buffer, fieldValue) + case CustomType: + return p.PackCustom(buffer, fieldValue, options) + case Struct: + return p.descriptor.NestFields.Pack(buffer, fieldValue, options) + } + + // 处理基本类型 + switch resolvedType { + case Float32, Float64: + floatValue := fieldValue.Float() + if err := p.WriteFloat(buffer, floatValue, resolvedType, byteOrder); err != nil { + return 0, err + } + default: + intValue := p.getIntegerValue(fieldValue) + if err := p.WriteInteger(buffer, intValue, resolvedType, byteOrder); err != nil { + return 0, err + } + } + + return resolvedType.Size(), nil +} + +// PackSliceValue 打包切片字段值 +func (p *DefaultFieldPacker) PackSliceValue(buffer []byte, fieldValue reflect.Value, length int, options *Options) (int, error) { + resolvedType := p.descriptor.Type.Resolve(options) + byteOrder := p.determineByteOrder(options) + + position := 0 + sliceLength := fieldValue.Len() + + if length >= 0 && sliceLength != length { + return 0, fmt.Errorf("struc: field %s has length %d but expected %d", p.descriptor.Name, sliceLength, length) + } + + for i := 0; i < sliceLength; i++ { + elementValue := fieldValue.Index(i) + + switch resolvedType { + case Float32, Float64: + floatValue := elementValue.Float() + if err := p.WriteFloat(buffer[position:], floatValue, resolvedType, byteOrder); err != nil { + return position, err + } + default: + intValue := p.getIntegerValue(elementValue) + if err := p.WriteInteger(buffer[position:], intValue, resolvedType, byteOrder); err != nil { + return position, err + } + } + position += resolvedType.Size() + } + + return position, nil +} + +// PackString 打包字符串字段值 +func (p *DefaultFieldPacker) PackString(buffer []byte, fieldValue reflect.Value) (int, error) { + str := fieldValue.String() + strBytes := unsafeString2Bytes(str) + copy(buffer, strBytes) + return len(strBytes), nil +} + +// PackCustom 打包自定义类型字段值 +func (p *DefaultFieldPacker) PackCustom(buffer []byte, fieldValue reflect.Value, options *Options) (int, error) { + if customType, ok := fieldValue.Addr().Interface().(CustomBinaryer); ok { + return customType.Pack(buffer, options) + } + return 0, fmt.Errorf("failed to pack custom type: %v", fieldValue.Type()) +} + +// PackPaddingBytes 打包填充字节 +func (p *DefaultFieldPacker) PackPaddingBytes(buffer []byte, length int) (int, error) { + for i := 0; i < length; i++ { + buffer[i] = 0 + } + return length, nil +} + +// WriteInteger 写入整数值到缓冲区 +func (p *DefaultFieldPacker) WriteInteger(buffer []byte, intValue uint64, resolvedType Type, byteOrder binary.ByteOrder) error { + switch resolvedType { + case Int8: + buffer[0] = byte(int8(intValue)) + case Int16: + unsafePutUint16(buffer, uint16(int16(intValue)), byteOrder) + case Int32: + unsafePutUint32(buffer, uint32(int32(intValue)), byteOrder) + case Int64: + unsafePutUint64(buffer, uint64(int64(intValue)), byteOrder) + case Bool, Uint8: + buffer[0] = byte(intValue) + case Uint16: + unsafePutUint16(buffer, uint16(intValue), byteOrder) + case Uint32: + unsafePutUint32(buffer, uint32(intValue), byteOrder) + case Uint64: + unsafePutUint64(buffer, uint64(intValue), byteOrder) + default: + return fmt.Errorf("unsupported integer type: %v", resolvedType) + } + return nil +} + +// WriteFloat 写入浮点数值到缓冲区 +func (p *DefaultFieldPacker) WriteFloat(buffer []byte, floatValue float64, resolvedType Type, byteOrder binary.ByteOrder) error { + switch resolvedType { + case Float32: + unsafePutFloat32(buffer, float32(floatValue), byteOrder) + case Float64: + unsafePutFloat64(buffer, floatValue, byteOrder) + default: + return fmt.Errorf("unsupported float type: %v", resolvedType) + } + return nil +} + +// determineByteOrder 返回要使用的字节序 +func (p *DefaultFieldPacker) determineByteOrder(options *Options) binary.ByteOrder { + if options.Order != nil { + return options.Order + } + return p.descriptor.ByteOrder +} + +// getIntegerValue 从 reflect.Value 中提取整数值 +func (p *DefaultFieldPacker) getIntegerValue(fieldValue reflect.Value) uint64 { + switch p.descriptor.kind { + case reflect.Bool: + if fieldValue.Bool() { + return 1 + } + return 0 + case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: + return uint64(fieldValue.Int()) + default: + return fieldValue.Uint() + } +} \ No newline at end of file diff --git a/field_refactored.go b/field_refactored.go new file mode 100644 index 0000000..6053836 --- /dev/null +++ b/field_refactored.go @@ -0,0 +1,109 @@ +package struc + +import ( + "encoding/binary" + "reflect" +) + +// FieldRefactored 是重构后的 Field 类 +// 使用职责分离的组件来实现功能,保持与原有接口的兼容性 +type FieldRefactored struct { + facade *FieldFacade +} + +// NewFieldRefactored 创建一个新的重构后的 Field 类 +func NewFieldRefactored() *FieldRefactored { + descriptor := NewFieldDescriptor() + facade := NewFieldFacade(descriptor) + return &FieldRefactored{ + facade: facade, + } +} + +// CopyFrom 从现有 Field 对象复制字段描述信息 +func (f *FieldRefactored) CopyFrom(field *Field) { + f.facade.GetDescriptor().CopyFrom(field) +} + +// Size 计算字段在二进制格式中占用的字节数 +// 考虑了对齐和填充要求 +func (f *FieldRefactored) Size(fieldValue reflect.Value, options *Options) int { + return f.facade.Size(fieldValue, options) +} + +// Pack 将字段值序列化到字节缓冲区中 +func (f *FieldRefactored) Pack(buffer []byte, fieldValue reflect.Value, length int, options *Options) (int, error) { + return f.facade.Pack(buffer, fieldValue, length, options) +} + +// Unpack 从字节缓冲区中解包字段值 +func (f *FieldRefactored) Unpack(buffer []byte, fieldValue reflect.Value, length int, options *Options) error { + return f.facade.Unpack(buffer, fieldValue, length, options) +} + +// GetDescriptor 返回字段描述符 +func (f *FieldRefactored) GetDescriptor() *FieldDescriptor { + return f.facade.GetDescriptor() +} + +// ==================== 保持向后兼容的字段访问方法 ==================== + +// Name 返回字段名称 +func (f *FieldRefactored) Name() string { + return f.facade.GetDescriptor().Name +} + +// IsPointer 返回字段是否为指针类型 +func (f *FieldRefactored) IsPointer() bool { + return f.facade.GetDescriptor().IsPointer +} + +// Index 返回字段在结构体中的索引 +func (f *FieldRefactored) Index() int { + return f.facade.GetDescriptor().Index +} + +// Type 返回字段的二进制类型 +func (f *FieldRefactored) Type() Type { + return f.facade.GetDescriptor().Type +} + +// IsArray 返回字段是否为数组 +func (f *FieldRefactored) IsArray() bool { + return f.facade.GetDescriptor().IsArray +} + +// IsSlice 返回字段是否为切片 +func (f *FieldRefactored) IsSlice() bool { + return f.facade.GetDescriptor().IsSlice +} + +// Length 返回数组/固定切片的长度 +func (f *FieldRefactored) Length() int { + return f.facade.GetDescriptor().Length +} + +// ByteOrder 返回字段的字节序 +func (f *FieldRefactored) ByteOrder() binary.ByteOrder { + return f.facade.GetDescriptor().ByteOrder +} + +// Sizeof 返回 sizeof 引用的字段索引 +func (f *FieldRefactored) Sizeof() []int { + return f.facade.GetDescriptor().Sizeof +} + +// Sizefrom 返回大小引用的字段索引 +func (f *FieldRefactored) Sizefrom() []int { + return f.facade.GetDescriptor().Sizefrom +} + +// NestFields 返回嵌套结构体的字段 +func (f *FieldRefactored) NestFields() Fields { + return f.facade.GetDescriptor().NestFields +} + +// Kind 返回 Go 的反射类型 +func (f *FieldRefactored) Kind() reflect.Kind { + return f.facade.GetDescriptor().GetKind() +} \ No newline at end of file diff --git a/field_size_calculator.go b/field_size_calculator.go new file mode 100644 index 0000000..42eb9fd --- /dev/null +++ b/field_size_calculator.go @@ -0,0 +1,107 @@ +package struc + +import ( + "reflect" +) + +// FieldSizeCalculator 定义了字段大小计算的接口 +// 这是Field类职责分离后的第二个组件,专门负责大小计算逻辑 +// 包含所有与字段大小计算相关的方法 +type FieldSizeCalculator interface { + // Size 计算字段在二进制格式中占用的字节数 + // 考虑了对齐和填充要求 + Size(fieldValue reflect.Value, options *Options) int + + // CalculateStructSize 计算结构体类型的字节大小 + // 处理普通结构体和结构体切片 + CalculateStructSize(fieldValue reflect.Value, options *Options) int + + // CalculateCustomSize 计算自定义类型的字节大小 + // 通过调用类型的 Size 方法获取 + CalculateCustomSize(fieldValue reflect.Value, options *Options) int + + // CalculateBasicSize 计算基本类型的字节大小 + // 处理基本类型和平台相关类型 + CalculateBasicSize(fieldValue reflect.Value, resolvedType Type, options *Options) int + + // AlignSize 根据字节对齐要求调整大小 + // 考虑字节对齐和填充 + AlignSize(size int, options *Options) int +} + +// DefaultFieldSizeCalculator 是FieldSizeCalculator的默认实现 +// 包含从现有Field类迁移的大小计算逻辑 +type DefaultFieldSizeCalculator struct { + descriptor *FieldDescriptor +} + +// NewFieldSizeCalculator 创建一个新的字段大小计算器 +func NewFieldSizeCalculator(descriptor *FieldDescriptor) FieldSizeCalculator { + return &DefaultFieldSizeCalculator{ + descriptor: descriptor, + } +} + +// Size 计算字段在二进制格式中占用的字节数 +// 考虑了对齐和填充要求 +func (c *DefaultFieldSizeCalculator) Size(fieldValue reflect.Value, options *Options) int { + resolvedType := c.descriptor.Type.Resolve(options) + totalSize := 0 + + switch resolvedType { + case Struct: + totalSize = c.CalculateStructSize(fieldValue, options) + case Pad: + totalSize = c.descriptor.Length + case CustomType: + totalSize = c.CalculateCustomSize(fieldValue, options) + default: + totalSize = c.CalculateBasicSize(fieldValue, resolvedType, options) + } + + return c.AlignSize(totalSize, options) +} + +// CalculateStructSize 计算结构体类型的字节大小 +// 处理普通结构体和结构体切片 +func (c *DefaultFieldSizeCalculator) CalculateStructSize(fieldValue reflect.Value, options *Options) int { + if c.descriptor.IsSlice { + sliceLength := fieldValue.Len() + totalSize := 0 + for i := 0; i < sliceLength; i++ { + totalSize += c.descriptor.NestFields.Sizeof(fieldValue.Index(i), options) + } + return totalSize + } + return c.descriptor.NestFields.Sizeof(fieldValue, options) +} + +// CalculateCustomSize 计算自定义类型的字节大小 +// 通过调用类型的 Size 方法获取 +func (c *DefaultFieldSizeCalculator) CalculateCustomSize(fieldValue reflect.Value, options *Options) int { + if customType, ok := fieldValue.Addr().Interface().(CustomBinaryer); ok { + return customType.Size(options) + } + return 0 +} + +// CalculateBasicSize 计算基本类型的字节大小 +// 处理基本类型和平台相关类型 +func (c *DefaultFieldSizeCalculator) CalculateBasicSize(fieldValue reflect.Value, resolvedType Type, options *Options) int { + if c.descriptor.IsSlice { + return fieldValue.Len() * resolvedType.Size() + } + return resolvedType.Size() +} + +// AlignSize 根据字节对齐要求调整大小 +// 考虑字节对齐和填充 +func (c *DefaultFieldSizeCalculator) AlignSize(size int, options *Options) int { + if options.ByteAlign > 0 && size > 0 { + remainder := size % options.ByteAlign + if remainder > 0 { + size += options.ByteAlign - remainder + } + } + return size +} \ No newline at end of file diff --git a/field_unpacker.go b/field_unpacker.go new file mode 100644 index 0000000..978df8c --- /dev/null +++ b/field_unpacker.go @@ -0,0 +1,186 @@ +package struc + +import ( + "bytes" + "encoding/binary" + "fmt" + "reflect" +) + +// FieldUnpacker 定义了字段解包的接口 +// 这是Field类职责分离后的第四个组件,专门负责解包逻辑 +// 包含所有与字段解包相关的方法 +type FieldUnpacker interface { + // Unpack 从字节缓冲区中解包字段值 + Unpack(buffer []byte, fieldValue reflect.Value, length int, options *Options) error + + // UnpackSingleValue 解包单个字段值 + UnpackSingleValue(buffer []byte, fieldValue reflect.Value, length int, options *Options) error + + // UnpackSliceValue 解包切片字段值 + UnpackSliceValue(buffer []byte, fieldValue reflect.Value, length int, options *Options) error + + // UnpackPaddingOrStringValue 解包填充或字符串字段值 + UnpackPaddingOrStringValue(buffer []byte, fieldValue reflect.Value, resolvedType Type) error + + // ReadInteger 从缓冲区读取整数值 + ReadInteger(buffer []byte, resolvedType Type, byteOrder binary.ByteOrder) uint64 +} + +// DefaultFieldUnpacker 是FieldUnpacker的默认实现 +// 包含从现有Field类迁移的解包逻辑 +type DefaultFieldUnpacker struct { + descriptor *FieldDescriptor +} + +// NewFieldUnpacker 创建一个新的字段解包器 +func NewFieldUnpacker(descriptor *FieldDescriptor) FieldUnpacker { + return &DefaultFieldUnpacker{ + descriptor: descriptor, + } +} + +// Unpack 从字节缓冲区中解包字段值 +func (u *DefaultFieldUnpacker) Unpack(buffer []byte, fieldValue reflect.Value, length int, options *Options) error { + if u.descriptor.IsSlice { + return u.UnpackSliceValue(buffer, fieldValue, length, options) + } + return u.UnpackSingleValue(buffer, fieldValue, length, options) +} + +// UnpackSingleValue 解包单个字段值 +func (u *DefaultFieldUnpacker) UnpackSingleValue(buffer []byte, fieldValue reflect.Value, length int, options *Options) error { + resolvedType := u.descriptor.Type.Resolve(options) + byteOrder := u.determineByteOrder(options) + + if resolvedType == Pad || resolvedType == String { + return u.UnpackPaddingOrStringValue(buffer, fieldValue, resolvedType) + } + + switch resolvedType { + case Float32, Float64: + floatValue := u.readFloat(buffer, resolvedType, byteOrder) + if u.descriptor.kind == reflect.Float32 || u.descriptor.kind == reflect.Float64 { + fieldValue.SetFloat(floatValue) + return nil + } + return fmt.Errorf("struc: refusing to unpack float into field %s of type %s", u.descriptor.Name, u.descriptor.kind.String()) + + case Bool, Int8, Int16, Int32, Int64, Uint8, Uint16, Uint32, Uint64: + // 处理整数和布尔类型 + intValue := u.ReadInteger(buffer, resolvedType, byteOrder) + switch u.descriptor.kind { + case reflect.Bool: + fieldValue.SetBool(intValue != 0) + case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: + fieldValue.SetInt(int64(intValue)) + default: + fieldValue.SetUint(intValue) + } + return nil + + case Struct: + return u.descriptor.NestFields.Unpack(bytes.NewReader(buffer), fieldValue, options) + + case String: + if u.descriptor.kind != reflect.String { + return fmt.Errorf("cannot unpack string into field %s of type %s", u.descriptor.Name, u.descriptor.kind) + } + str := unsafeBytes2String(buffer[:length]) + fieldValue.SetString(str) + return nil + + case CustomType: + if customType, ok := fieldValue.Addr().Interface().(CustomBinaryer); ok { + return customType.Unpack(bytes.NewReader(buffer), length, options) + } + return fmt.Errorf("failed to unpack custom type: %v", fieldValue.Type()) + + default: + return fmt.Errorf("unsupported type for unpacking: %v", resolvedType) + } +} + +// UnpackSliceValue 解包切片字段值 +func (u *DefaultFieldUnpacker) UnpackSliceValue(buffer []byte, fieldValue reflect.Value, length int, options *Options) error { + resolvedType := u.descriptor.Type.Resolve(options) + + // 如果是数组则使用原值, 否则创建切片 + sliceValue := fieldValue + if !u.descriptor.IsArray { + sliceValue = reflect.MakeSlice(fieldValue.Type(), length, length) + } + + elementSize := resolvedType.Size() + for i := 0; i < length; i++ { + start := i * elementSize + end := start + elementSize + if end > len(buffer) { + return fmt.Errorf("buffer too small for slice unpacking") + } + + elementValue := sliceValue.Index(i) + if err := u.UnpackSingleValue(buffer[start:end], elementValue, 1, options); err != nil { + return err + } + } + + if !u.descriptor.IsArray { + fieldValue.Set(sliceValue) + } + return nil +} + +// UnpackPaddingOrStringValue 解包填充或字符串字段值 +func (u *DefaultFieldUnpacker) UnpackPaddingOrStringValue(buffer []byte, fieldValue reflect.Value, resolvedType Type) error { + if resolvedType == Pad { + // 填充字段不需要设置值 + return nil + } + // 字符串类型已经在UnpackSingleValue中处理 + return fmt.Errorf("unexpected type for padding/string unpacking: %v", resolvedType) +} + +// ReadInteger 从缓冲区读取整数值 +func (u *DefaultFieldUnpacker) ReadInteger(buffer []byte, resolvedType Type, byteOrder binary.ByteOrder) uint64 { + switch resolvedType { + case Int8: + return uint64(int64(int8(buffer[0]))) + case Int16: + return uint64(int64(int16(unsafeGetUint16(buffer, byteOrder)))) + case Int32: + return uint64(int64(int32(unsafeGetUint32(buffer, byteOrder)))) + case Int64: + return uint64(int64(unsafeGetUint64(buffer, byteOrder))) + case Bool, Uint8: + return uint64(buffer[0]) + case Uint16: + return uint64(unsafeGetUint16(buffer, byteOrder)) + case Uint32: + return uint64(unsafeGetUint32(buffer, byteOrder)) + case Uint64: + return unsafeGetUint64(buffer, byteOrder) + default: + return 0 + } +} + +// determineByteOrder 返回要使用的字节序 +func (u *DefaultFieldUnpacker) determineByteOrder(options *Options) binary.ByteOrder { + if options.Order != nil { + return options.Order + } + return u.descriptor.ByteOrder +} + +// readFloat 从缓冲区读取浮点数值 +func (u *DefaultFieldUnpacker) readFloat(buffer []byte, resolvedType Type, byteOrder binary.ByteOrder) float64 { + switch resolvedType { + case Float32: + return float64(unsafeGetFloat32(buffer, byteOrder)) + case Float64: + return unsafeGetFloat64(buffer, byteOrder) + default: + return 0 + } +} \ No newline at end of file diff --git a/fields.go b/fields.go index 50dc7b2..fb22f36 100644 --- a/fields.go +++ b/fields.go @@ -1,3 +1,5 @@ +// Package struc implements binary packing and unpacking for Go structs. +// struc 包实现了 Go 结构体的二进制打包和解包功能。 package struc import ( @@ -8,168 +10,215 @@ import ( "strings" ) +// unpackBasicTypeSlicePool 是全局共享的字节块实例 +// 用于在 unpackBasicType 方法内共享字节切片,减少内存分配 +var unpackBasicTypeSlicePool = NewBytesSlicePool(0) + +// Fields 是字段切片类型,用于管理结构体的字段集合 +// 它提供了字段的序列化、反序列化和大小计算等功能 type Fields []*Field -func (f Fields) SetByteOrder(order binary.ByteOrder) { +// SetByteOrder 为所有字段设置字节序 +// 这会影响字段值的二进制表示方式 +func (f Fields) SetByteOrder(byteOrder binary.ByteOrder) { for _, field := range f { if field != nil { - field.Order = order + field.ByteOrder = byteOrder } } } +// String 返回字段集合的字符串表示 +// 主要用于调试和日志记录 func (f Fields) String() string { - fields := make([]string, len(f)) + fieldStrings := make([]string, len(f)) for i, field := range f { if field != nil { - fields[i] = field.String() + fieldStrings[i] = field.String() } } - return "{" + strings.Join(fields, ", ") + "}" + return "{" + strings.Join(fieldStrings, ", ") + "}" } -func (f Fields) Sizeof(val reflect.Value, options *Options) int { - for val.Kind() == reflect.Ptr { - val = val.Elem() +// Sizeof 计算字段集合在内存中的总大小(字节数) +// 考虑了对齐和填充要求 +func (f Fields) Sizeof(structValue reflect.Value, options *Options) int { + // 解引用所有指针,获取实际的结构体值 + for structValue.Kind() == reflect.Ptr { + structValue = structValue.Elem() } - size := 0 + + totalSize := 0 for i, field := range f { if field != nil { - size += field.Size(val.Field(i), options) + totalSize += field.Size(structValue.Field(i), options) } } - return size + return totalSize } -func (f Fields) sizefrom(val reflect.Value, index []int) int { - field := val.FieldByIndex(index) - switch field.Kind() { +// sizefrom 根据引用字段的值确定切片或数组的长度 +// 支持有符号和无符号整数类型的长度字段 +func (f Fields) sizefrom(structValue reflect.Value, fieldIndex []int) int { + lengthField := structValue.FieldByIndex(fieldIndex) + + switch lengthField.Kind() { case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: - return int(field.Int()) + return int(lengthField.Int()) case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64: - n := int(field.Uint()) - // all the builtin array length types are native int - // so this guards against weird truncation - if n < 0 { + lengthValue := int(lengthField.Uint()) + if lengthValue < 0 { return 0 } - return n + return lengthValue default: - name := val.Type().FieldByIndex(index).Name - panic(fmt.Sprintf("sizeof field %T.%s not an integer type", val.Interface(), name)) + fieldName := structValue.Type().FieldByIndex(fieldIndex).Name + panic(fmt.Sprintf("sizeof field %T.%s not an integer type", structValue.Interface(), fieldName)) } } -func (f Fields) Pack(buf []byte, val reflect.Value, options *Options) (int, error) { - for val.Kind() == reflect.Ptr { - val = val.Elem() +// Pack 将字段集合打包到字节缓冲区中 +// 支持基本类型、结构体、切片和自定义类型 +func (f Fields) Pack(buffer []byte, structValue reflect.Value, options *Options) (int, error) { + // 解引用指针,直到获取到非指针类型 + for structValue.Kind() == reflect.Ptr { + structValue = structValue.Elem() } - pos := 0 + + position := 0 // 当前缓冲区位置 + for i, field := range f { if field == nil { continue } - v := val.Field(i) - length := field.Len + + fieldValue := structValue.Field(i) + fieldLength := field.Length + if field.Sizefrom != nil { - length = f.sizefrom(val, field.Sizefrom) + fieldLength = f.sizefrom(structValue, field.Sizefrom) } - if length <= 0 && field.Slice { - length = v.Len() + if fieldLength <= 0 && field.IsSlice { + fieldLength = fieldValue.Len() } + if field.Sizeof != nil { - length := val.FieldByIndex(field.Sizeof).Len() + sizeofLength := structValue.FieldByIndex(field.Sizeof).Len() + switch field.kind { case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: - // allocating a new int here has fewer side effects (doesn't update the original struct) - // but it's a wasteful allocation - // the old method might work if we just cast the temporary int/uint to the target type - v = reflect.New(v.Type()).Elem() - v.SetInt(int64(length)) + fieldValue.SetInt(int64(sizeofLength)) case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64: - v = reflect.New(v.Type()).Elem() - v.SetUint(uint64(length)) + fieldValue.SetUint(uint64(sizeofLength)) default: - panic(fmt.Sprintf("sizeof field is not int or uint type: %s, %s", field.Name, v.Type())) + panic(fmt.Sprintf("sizeof field is not int or uint type: %s, %s", field.Name, fieldValue.Type())) } } - if n, err := field.Pack(buf[pos:], v, length, options); err != nil { - return n, err - } else { - pos += n + + bytesWritten, err := field.Pack(buffer[position:], fieldValue, fieldLength, options) + if err != nil { + return bytesWritten, err } + position += bytesWritten } - return pos, nil + return position, nil } -func (f Fields) Unpack(r io.Reader, val reflect.Value, options *Options) error { - for val.Kind() == reflect.Ptr { - val = val.Elem() +// Release 释放 Fields 切片中的所有 Field 对象 +// 用于内存管理和资源回收 +func (f Fields) Release() { + releaseFields(f) +} + +// unpackStruct 处理结构体类型的解包 +func (f Fields) unpackStruct(reader io.Reader, fieldValue reflect.Value, field *Field, fieldLength int, options *Options) error { + if field.IsSlice { + return f.unpackStructSlice(reader, fieldValue, fieldLength, field.IsArray, options) + } + return f.unpackSingleStruct(reader, fieldValue, options) +} + +// unpackStructSlice 处理结构体切片的解包 +func (f Fields) unpackStructSlice(reader io.Reader, fieldValue reflect.Value, fieldLength int, isArray bool, options *Options) error { + // 如果是数组则使用原值, 否则创建切片 + sliceValue := fieldValue + if !isArray { + sliceValue = reflect.MakeSlice(fieldValue.Type(), fieldLength, fieldLength) } - var tmp [8]byte - var buf []byte + + for i := 0; i < fieldLength; i++ { + elementValue := sliceValue.Index(i) + fields, err := parseFields(elementValue) + if err != nil { + return err + } + if err := fields.Unpack(reader, elementValue, options); err != nil { + return err + } + } + + if !isArray { + fieldValue.Set(sliceValue) + } + return nil +} + +// unpackSingleStruct 处理单个结构体的解包 +func (f Fields) unpackSingleStruct(reader io.Reader, fieldValue reflect.Value, options *Options) error { + fields, err := parseFields(fieldValue) + if err != nil { + return err + } + return fields.Unpack(reader, fieldValue, options) +} + +// unpackBasicType 处理基本类型和自定义类型的解包 +func (f Fields) unpackBasicType(reader io.Reader, fieldValue reflect.Value, field *Field, fieldLength int, options *Options) error { + resolvedType := field.Type.Resolve(options) + if resolvedType == CustomType { + return fieldValue.Addr().Interface().(CustomBinaryer).Unpack(reader, fieldLength, options) + } + + dataSize := fieldLength * resolvedType.Size() + buffer := unpackBasicTypeSlicePool.GetSlice(dataSize) + + if _, err := io.ReadFull(reader, buffer); err != nil { + return err + } + + return field.Unpack(buffer[:dataSize], fieldValue, fieldLength, options) +} + +// Unpack 从 Reader 中读取数据并解包到字段集合中 +// 支持基本类型、结构体、切片和自定义类型 +func (f Fields) Unpack(reader io.Reader, structValue reflect.Value, options *Options) error { + // 解引用指针,直到获取到非指针类型 + for structValue.Kind() == reflect.Ptr { + structValue = structValue.Elem() + } + for i, field := range f { if field == nil { continue } - v := val.Field(i) - length := field.Len + + fieldValue := structValue.Field(i) + fieldLength := field.Length if field.Sizefrom != nil { - length = f.sizefrom(val, field.Sizefrom) + fieldLength = f.sizefrom(structValue, field.Sizefrom) } - if v.Kind() == reflect.Ptr && !v.Elem().IsValid() { - v.Set(reflect.New(v.Type().Elem())) + + if fieldValue.Kind() == reflect.Ptr && !fieldValue.Elem().IsValid() { + fieldValue.Set(reflect.New(fieldValue.Type().Elem())) } + if field.Type == Struct { - if field.Slice { - vals := v - if !field.Array { - vals = reflect.MakeSlice(v.Type(), length, length) - } - for i := 0; i < length; i++ { - v := vals.Index(i) - fields, err := parseFields(v) - if err != nil { - return err - } - if err := fields.Unpack(r, v, options); err != nil { - return err - } - } - if !field.Array { - v.Set(vals) - } - } else { - // TODO: DRY (we repeat the inner loop above) - fields, err := parseFields(v) - if err != nil { - return err - } - if err := fields.Unpack(r, v, options); err != nil { - return err - } + if err := f.unpackStruct(reader, fieldValue, field, fieldLength, options); err != nil { + return err } - continue } else { - typ := field.Type.Resolve(options) - if typ == CustomType { - if err := v.Addr().Interface().(Custom).Unpack(r, length, options); err != nil { - return err - } - } else { - size := length * field.Type.Resolve(options).Size() - if size < 8 { - buf = tmp[:size] - } else { - buf = make([]byte, size) - } - if _, err := io.ReadFull(r, buf); err != nil { - return err - } - err := field.Unpack(buf[:size], v, length, options) - if err != nil { - return err - } + if err := f.unpackBasicType(reader, fieldValue, field, fieldLength, options); err != nil { + return err } } } diff --git a/fields_test.go b/fields_test.go index 47eb2ca..22a5e5e 100644 --- a/fields_test.go +++ b/fields_test.go @@ -2,21 +2,84 @@ package struc import ( "bytes" + "encoding/binary" "reflect" "testing" ) -var refVal = reflect.ValueOf(reference) +var testRefValue = reflect.ValueOf(testExample) func TestFieldsParse(t *testing.T) { - if _, err := parseFields(refVal); err != nil { + if _, err := parseFields(testRefValue); err != nil { t.Fatal(err) } } func TestFieldsString(t *testing.T) { - fields, _ := parseFields(refVal) - fields.String() + tests := []struct { + name string + fields Fields + expected string + }{ + { + name: "Empty Fields", + fields: Fields{}, + expected: "{}", + }, + { + name: "Single Pad Field", + fields: Fields{ + { + Type: Pad, + Length: 4, + }, + }, + expected: "{{type: pad, len: 4}}", + }, + { + name: "Multiple Fields", + fields: Fields{ + { + Name: "Int32Field", + Type: Int32, + ByteOrder: binary.BigEndian, + }, + { + Name: "StringField", + Type: String, + Length: 10, + }, + nil, // Test nil field handling + {}, // Test empty field handling + }, + expected: "{{type: int32, order: BigEndian}, {type: string, len: 10}, , {type: invalid, len: 0}}", + }, + { + name: "Fields with Sizeof and Sizefrom", + fields: Fields{ + { + Name: "Length", + Type: Int32, + Sizeof: []int{1}, + }, + { + Name: "Data", + Type: String, + Sizefrom: []int{0}, + }, + }, + expected: "{{type: int32, sizeof: [1]}, {type: string, sizefrom: [0]}}", + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + result := tt.fields.String() + if result != tt.expected { + t.Errorf("Fields.String() = %v, want %v", result, tt.expected) + } + }) + } } type sizefromStruct struct { diff --git a/format.go b/format.go new file mode 100644 index 0000000..a1dbbac --- /dev/null +++ b/format.go @@ -0,0 +1,280 @@ +package struc + +import ( + "bytes" + "encoding/binary" + "fmt" + "reflect" +) + +// 格式映射表定义了 Go 类型到二进制格式字符的映射关系 +var formatMap = map[Type]string{ + Int8: "b", // signed char + Uint8: "B", // unsigned char + Int16: "h", // short + Uint16: "H", // unsigned short + Int32: "i", // int + Uint32: "I", // unsigned int + Int64: "q", // long long + Uint64: "Q", // unsigned long long + Float32: "f", // float + Float64: "d", // double + String: "s", // char[] + Bool: "?", // boolean + Pad: "x", // padding +} + +// GetFormatString 返回结构体的格式字符串,用于描述二进制数据的布局。 +// 格式类似于 Python 的 struct 模块,例如 "<10sHHb"。 +func GetFormatString(data interface{}) (string, error) { + // 获取并验证输入数据 + value, err := validateInput(data) + if err != nil { + return "", err + } + + // 解析字段 + fields, err := parseFields(value) + if err != nil && value.NumField() > 0 { + return "", fmt.Errorf("failed to parse fields: %w", err) + } + + // 确定字节序并生成格式字符串 + buf := acquireBuffer() + defer releaseBuffer(buf) + + if err := buildFormatStringWithBuffer(fields, buf); err != nil { + return "", err + } + + return buf.String(), nil +} + +// validateInput 验证输入数据并返回结构体的反射值。 +// 如果输入不是结构体或结构体指针,则返回错误。 +func validateInput(data interface{}) (reflect.Value, error) { + value := reflect.ValueOf(data) + + // 解引用所有指针 + for value.Kind() == reflect.Ptr { + if value.IsNil() { + return reflect.Value{}, fmt.Errorf("data must be a struct or pointer to struct") + } + value = value.Elem() + } + + if value.Kind() != reflect.Struct { + return reflect.Value{}, fmt.Errorf("data must be a struct or pointer to struct") + } + + return value, nil +} + +// buildFormatStringWithBuffer 使用缓冲区构建格式字符串 +func buildFormatStringWithBuffer(fields Fields, buf *bytes.Buffer) error { + // 确定初始字节序,默认为大端序 + initialEndianness := ">" + var initialOrder binary.ByteOrder = binary.BigEndian + for _, field := range fields { + if field != nil && field.ByteOrder == binary.LittleEndian { + initialEndianness = "<" + initialOrder = binary.LittleEndian + break + } + } + + // 生成字段格式 + if err := formatFields(buf, fields, initialEndianness, initialOrder); err != nil { + return err + } + + return nil +} + +// formatFields 处理字段集合的格式化 +func formatFields(buf *bytes.Buffer, fields Fields, parentEndianness string, currentOrder binary.ByteOrder) error { + state := &formatState{ + buffer: buf, + lastEndian: -1, + isFirst: true, + curOrder: currentOrder, + parentOrder: parentEndianness, + } + + for _, field := range fields { + if field == nil { + continue + } + + if err := state.processField(field); err != nil { + return err + } + } + + return nil +} + +// formatState 维护格式化过程中的状态信息 +type formatState struct { + buffer *bytes.Buffer // 格式字符串缓冲区 + lastEndian int // 上一个字节序标记的位置 + isFirst bool // 是否是第一个字段 + curOrder binary.ByteOrder // 当前字节序 + parentOrder string // 父级字节序 +} + +// processField 处理单个字段的格式化 +func (s *formatState) processField(field *Field) error { + startPos := s.buffer.Len() + + if err := s.handleEndianness(field, startPos); err != nil { + return err + } + + tmpBuf := acquireBuffer() + defer releaseBuffer(tmpBuf) + if err := formatField(tmpBuf, field); err != nil { + return err + } + + fieldFormat := tmpBuf.String() + if fieldFormat != "" { + s.buffer.WriteString(fieldFormat) + s.isFirst = false + } + + return nil +} + +// handleEndianness 处理字段的字节序 +func (s *formatState) handleEndianness(field *Field, startPos int) error { + if field.ByteOrder == nil || field.ByteOrder == s.curOrder { + if s.isFirst { + s.buffer.WriteString(s.parentOrder) + s.lastEndian = s.buffer.Len() - 1 + } + return nil + } + + // 需要切换字节序 + if s.isFirst { + s.writeEndianness(field.ByteOrder) + } else if s.lastEndian >= 0 && startPos == s.lastEndian+1 { + // 如果上一个字节序标记后没有任何有效字符,直接替换 + oldStr := s.buffer.String() + s.buffer.Reset() + s.buffer.WriteString(oldStr[:s.lastEndian]) + s.writeEndianness(field.ByteOrder) + } else { + s.writeEndianness(field.ByteOrder) + } + + return nil +} + +// writeEndianness 写入字节序标记到格式字符串中 +func (s *formatState) writeEndianness(order binary.ByteOrder) { + if order == binary.LittleEndian { + s.buffer.WriteString("<") // 小端序 + s.curOrder = binary.LittleEndian + } else { + s.buffer.WriteString(">") // 大端序 + s.curOrder = binary.BigEndian + } + s.lastEndian = s.buffer.Len() - 1 +} + +// formatField 处理单个字段的格式化 +func formatField(buf *bytes.Buffer, field *Field) error { + if field.Type == Struct { + return formatFields(buf, field.NestFields, "", binary.BigEndian) + } + + if len(field.Sizeof) > 0 { + return formatSizeofField(buf, field) + } + + // 跳过 sizefrom 字段 + if len(field.Sizefrom) > 0 { + return nil + } + + if field.IsArray || field.IsSlice { + return formatArrayField(buf, field) + } + + return formatBasicField(buf, field) +} + +// formatSizeofField 处理 sizeof 字段的格式化 +func formatSizeofField(buf *bytes.Buffer, field *Field) error { + formatChar, ok := formatMap[field.Type] + if !ok { + return fmt.Errorf("unsupported sizeof type for field %s", field.Name) + } + buf.WriteString(formatChar) + return nil +} + +// formatArrayField 处理数组和切片字段的格式化 +func formatArrayField(buf *bytes.Buffer, field *Field) error { + if field.Length <= 0 && len(field.Sizefrom) == 0 { + return fmt.Errorf("field `%s` is a slice with no length or sizeof field", field.Name) + } + + baseType := field.defType + if baseType == 0 { + baseType = field.Type + } + + if field.Type == Pad { + fmt.Fprintf(buf, "%d%s", field.Length, formatMap[Pad]) + return nil + } + + if baseType == Uint8 || baseType == String || field.Type == String { + fmt.Fprintf(buf, "%d%s", field.Length, formatMap[String]) + return nil + } + + return formatArrayElements(buf, field.Length, baseType) +} + +// formatArrayElements 处理数组元素的格式化 +func formatArrayElements(buf *bytes.Buffer, length int, baseType Type) error { + formatChar, ok := formatMap[baseType] + if !ok { + return fmt.Errorf("unsupported array element type: %v", baseType) + } + + for i := 0; i < length; i++ { + buf.WriteString(formatChar) + } + return nil +} + +// formatBasicField 处理基本类型字段的格式化 +func formatBasicField(buf *bytes.Buffer, field *Field) error { + formatChar, ok := formatMap[field.Type] + if !ok { + return fmt.Errorf("unsupported type for field %s: %v", field.Name, field.Type) + } + + switch field.Type { + case String: + if field.Length > 0 { + fmt.Fprintf(buf, "%d%s", field.Length, formatChar) + } else if len(field.Sizefrom) == 0 { + return fmt.Errorf("field `%s` is a string with no length or sizeof field", field.Name) + } + case Pad: + if field.Length > 0 { + fmt.Fprintf(buf, "%d%s", field.Length, formatChar) + } else { + buf.WriteString(formatChar) + } + default: + buf.WriteString(formatChar) + } + return nil +} diff --git a/format_test.go b/format_test.go new file mode 100644 index 0000000..74e8c19 --- /dev/null +++ b/format_test.go @@ -0,0 +1,199 @@ +package struc + +import ( + "testing" +) + +// 基本类型测试结构体 +// Basic types test struct +type FormatTestBasic struct { + A int8 `struc:"int8"` + B uint8 `struc:"uint8"` + C int16 `struc:"int16"` + D uint16 `struc:"uint16,little"` + E int32 `struc:"int32"` + F uint32 `struc:"uint32"` + G int64 `struc:"int64"` + H uint64 `struc:"uint64"` + I float32 `struc:"float32"` + J float64 `struc:"float64"` +} + +// 字符串和字节切片测试结构体 +// String and byte slice test struct +type FormatTestString struct { + CLen int `struc:"int32,sizeof=C"` // C 的长度字段 / Length field for C + A string `struc:"[10]byte"` // 固定长度字符串 / Fixed-length string + B []byte `struc:"[5]byte"` // 固定长度字节切片 / Fixed-length byte slice + C string `struc:"[]byte"` // 动态长度字符串 / Dynamic-length string + DLen int `struc:"int16,sizeof=D"` // D 的长度字段 / Length field for D + D []byte `struc:"[]byte"` // 动态长度字节切片 / Dynamic-length byte slice +} + +// 填充字节测试结构体 +// Padding bytes test struct +type FormatTestPadding struct { + A int32 `struc:"int32"` + B []byte `struc:"[4]pad"` // 4字节填充 / 4-byte padding + C uint16 `struc:"uint16"` + D []byte `struc:"[2]pad"` // 2字节填充 / 2-byte padding + E int64 `struc:"int64"` +} + +type FormatTestPaddingOnly struct { + A []byte `struc:"[4]pad"` // 4字节填充 / 4-byte padding + B []byte `struc:"[2]pad"` // 2字节填充 / 2-byte padding +} + +// 大小端混合测试结构体 +// Mixed endianness test struct +type FormatTestEndian struct { + A uint16 `struc:"uint16,big"` // 大端序 / Big-endian + B uint32 `struc:"uint32,little"` // 小端序 / Little-endian + C uint64 `struc:"uint64,big"` // 大端序 / Big-endian + D int16 `struc:"int16,little"` // 小端序 / Little-endian +} + +// 大小引用测试结构体 +// Size reference test struct +type FormatTestSizeof struct { + Size int `struc:"int32,sizeof=Data"` + Data []byte + Length int `struc:"uint16,sizeof=Text"` + Text string `struc:"[]byte"` + Count int `struc:"int8,sizeof=Array"` + Array []int `struc:"[]int32"` +} + +// 嵌套结构体测试 +// Nested struct test +type FormatTestNested struct { + // Level 1 + Header struct { + Size uint32 `struc:"uint32"` + Version uint16 `struc:"uint16"` + Magic [4]byte `struc:"[4]byte"` + Reserved []byte `struc:"[8]pad"` + Skip1 int `struc:"skip"` // 跳过此字段 / Skip this field + } + // Level 2 + Body struct { + DataSize int32 `struc:"int32,sizeof=Data"` + Data []byte `struc:"[]byte"` + Type uint8 `struc:"uint8"` + Flags [2]int16 `struc:"[2]int16"` + Skip2 float32 `struc:"-"` // 忽略此字段 / Ignore this field + // Level 3 + Details struct { + Timestamp int64 `struc:"int64"` + Value float64 `struc:"float64"` + Name string `struc:"[16]byte"` + Skip3 bool `struc:"skip"` // 跳过此字段 / Skip this field + // Level 4 + Statistics struct { + Min float32 `struc:"float32"` + Max float32 `struc:"float32"` + Count uint32 `struc:"uint32,little"` + Skip4 string `struc:"-"` // 忽略此字段 / Ignore this field + // Level 5 + Metadata struct { + Tags [2]uint8 `struc:"[2]uint8"` + Status int16 `struc:"int16,big"` + Priority uint16 `struc:"uint16,little"` + Checksum [4]byte `struc:"[4]byte"` + Skip5 int64 `struc:"skip"` // 跳过此字段 / Skip this field + } + } + } + } +} + +// 数组测试结构体 +// Array test struct +type FormatTestArray struct { + IntArray [4]int32 `struc:"[4]int32"` + ByteArray [8]byte `struc:"[8]byte"` + FloatArray [2]float32 `struc:"[2]float32"` +} + +func TestGetFormatString(t *testing.T) { + tests := []struct { + name string + data interface{} + want string + wantErr bool + errMsg string + }{ + { + name: "basic types", + data: &FormatTestBasic{}, + want: ">bBhiIqQfd", // 按照 formatMap 映射: Int8(b), Uint8(B), Int16(h), Uint16(H), Int32(i), Uint32(I), Int64(q), Uint64(Q), Float32(f), Float64(d) + }, + { + name: "string types", + data: &FormatTestString{}, + want: ">i10s5sh", // 按照 formatMap 映射: Int32(i), [10]byte(10s), [5]byte(5s), Int16(h) + }, + { + name: "padding", + data: &FormatTestPadding{}, + want: ">i4xH2xq", // 按照 formatMap 映射: Int32(i), Pad(4x), Uint16(H), Pad(2x), Int64(q) + }, + { + name: "padding only", + data: &FormatTestPaddingOnly{}, + want: ">4x2x", // 按照 formatMap 映射: Pad(4x), Pad(2x) + }, + { + name: "mixed endianness", + data: &FormatTestEndian{}, + want: ">HQiHb", // 按照 formatMap 映射: Int32(i), Uint16(H), Int8(b) + }, + { + name: "nested struct", + data: &FormatTestNested{}, + want: ">IH4s8xiBhhqd16sff2sh4s", // 按照 formatMap 映射: Uint32(I), Uint16(H), [4]byte(4s), Int8(i), Uint8(B), Int16(h), Int64(q), String(16s), Float32(f), Float64(d), Int32(i), Int16(h), Uint16(H), [4]byte(4s) + }, + { + name: "array types", + data: &FormatTestArray{}, + want: ">iiii8sff", // 按照 formatMap 映射: Int32(i,i,i,i), String(8s), Float32(f,f) + }, + { + name: "non-struct", + data: 123, + wantErr: true, + errMsg: "data must be a struct or pointer to struct", + }, + { + name: "nil pointer", + data: (*FormatTestBasic)(nil), + wantErr: true, + errMsg: "data must be a struct or pointer to struct", + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got, err := GetFormatString(tt.data) + if (err != nil) != tt.wantErr { + t.Errorf("GetFormatString() error = %v, wantErr %v", err, tt.wantErr) + return + } + if tt.wantErr { + if err.Error() != tt.errMsg { + t.Errorf("GetFormatString() error message = %v, want %v", err.Error(), tt.errMsg) + } + return + } + if got != tt.want { + t.Errorf("GetFormatString() = %v, want %v", got, tt.want) + } + }) + } +} diff --git a/go.mod b/go.mod index e2b291e..ba4aaf4 100644 --- a/go.mod +++ b/go.mod @@ -1,3 +1,3 @@ -module github.com/lunixbochs/struc +module github.com/shengyanli1982/struc/v2 -go 1.12 +go 1.19 diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..e69de29 diff --git a/options.go b/options.go new file mode 100644 index 0000000..dd2dc1d --- /dev/null +++ b/options.go @@ -0,0 +1,46 @@ +package struc + +import ( + "encoding/binary" + "fmt" +) + +// defaultPackingOptions 是默认的打包选项实例 +// 用于避免重复分配内存,提高性能 +var defaultPackingOptions = &Options{} + +// Options 定义了打包和解包的配置选项 +// 包含字节对齐、指针大小和字节序等设置 +type Options struct { + // ByteAlign 指定打包字段的字节对齐方式 + // 值为 0 表示不进行对齐,其他值表示按该字节数对齐 + ByteAlign int + + // PtrSize 指定指针的大小(以位为单位) + // 可选值:8、16、32 或 64, 默认值:32 + PtrSize int + + // Order 指定字节序(大端或小端) + // 如果为 nil,则使用大端序 + Order binary.ByteOrder +} + +// Validate 验证选项的有效性 +// 检查指针大小是否合法,并设置默认值 +func (o *Options) Validate() error { + if o.PtrSize == 0 { + o.PtrSize = 32 // 设置默认指针大小 + } else { + switch o.PtrSize { + case 8, 16, 32, 64: + // 有效的指针大小 + default: + return fmt.Errorf("invalid Options.PtrSize: %d (must be 8, 16, 32, or 64)", o.PtrSize) + } + } + return nil +} + +func init() { + _ = defaultPackingOptions.Validate() +} diff --git a/options_builder.go b/options_builder.go new file mode 100644 index 0000000..5dfbf62 --- /dev/null +++ b/options_builder.go @@ -0,0 +1,106 @@ +package struc + +import ( + "encoding/binary" +) + +// OptionsBuilder 是配置选项的建造者模式实现 +// 提供链式调用的方式来构建 Options 对象 +type OptionsBuilder struct { + options *Options +} + +// NewOptionsBuilder 创建一个新的配置选项建造者 +func NewOptionsBuilder() *OptionsBuilder { + return &OptionsBuilder{ + options: &Options{}, + } +} + +// WithByteAlign 设置字节对齐方式 +func (b *OptionsBuilder) WithByteAlign(align int) *OptionsBuilder { + b.options.ByteAlign = align + return b +} + +// WithPtrSize 设置指针大小 +func (b *OptionsBuilder) WithPtrSize(size int) *OptionsBuilder { + b.options.PtrSize = size + return b +} + +// WithByteOrder 设置字节序 +func (b *OptionsBuilder) WithByteOrder(order binary.ByteOrder) *OptionsBuilder { + b.options.Order = order + return b +} + +// WithLittleEndian 设置为小端字节序 +func (b *OptionsBuilder) WithLittleEndian() *OptionsBuilder { + b.options.Order = binary.LittleEndian + return b +} + +// WithBigEndian 设置为大端字节序 +func (b *OptionsBuilder) WithBigEndian() *OptionsBuilder { + b.options.Order = binary.BigEndian + return b +} + +// Build 构建最终的 Options 对象并验证 +func (b *OptionsBuilder) Build() (*Options, error) { + if err := b.options.Validate(); err != nil { + return nil, err + } + return b.options, nil +} + +// MustBuild 构建最终的 Options 对象,如果验证失败则 panic +func (b *OptionsBuilder) MustBuild() *Options { + options, err := b.Build() + if err != nil { + panic(err) + } + return options +} + +// ==================== 便捷构建函数 ==================== + +// DefaultOptions 返回默认配置选项 +func DefaultOptions() *Options { + return defaultPackingOptions +} + +// LittleEndianOptions 返回小端字节序的配置选项 +func LittleEndianOptions() *Options { + builder := NewOptionsBuilder() + return builder.WithLittleEndian().MustBuild() +} + +// BigEndianOptions 返回大端字节序的配置选项 +func BigEndianOptions() *Options { + builder := NewOptionsBuilder() + return builder.WithBigEndian().MustBuild() +} + +// AlignedOptions 返回指定字节对齐的配置选项 +func AlignedOptions(align int) *Options { + builder := NewOptionsBuilder() + return builder.WithByteAlign(align).MustBuild() +} + +// PtrSizeOptions 返回指定指针大小的配置选项 +func PtrSizeOptions(size int) *Options { + builder := NewOptionsBuilder() + return builder.WithPtrSize(size).MustBuild() +} + +// CustomOptions 返回自定义配置选项 +func CustomOptions(align, ptrSize int, order binary.ByteOrder) *Options { + builder := NewOptionsBuilder() + return builder. + WithByteAlign(align). + WithPtrSize(ptrSize). + WithByteOrder(order). + MustBuild() +} \ No newline at end of file diff --git a/packable_test.go b/packable_test.go index ec2bed9..c55e078 100644 --- a/packable_test.go +++ b/packable_test.go @@ -3,121 +3,71 @@ package struc import ( "bytes" "fmt" + "reflect" "testing" ) -var packableReference = []byte{ +var testPackableBytes = []byte{ 1, 0, 2, 0, 0, 0, 3, 0, 0, 0, 0, 0, 0, 0, 4, 5, 0, 6, 0, 0, 0, 7, 0, 0, 0, 0, 0, 0, 0, 8, 9, 10, 11, 12, 13, 14, 15, 16, 0, 17, 0, 18, 0, 19, 0, 20, 0, 21, 0, 22, 0, 23, 0, 24, } -func TestPackable(t *testing.T) { - var ( - buf bytes.Buffer +type testCase struct { + name string + value interface{} + expected interface{} +} - i8 int8 = 1 - i16 int16 = 2 - i32 int32 = 3 - i64 int64 = 4 - u8 uint8 = 5 - u16 uint16 = 6 - u32 uint32 = 7 - u64 uint64 = 8 +func TestPackable(t *testing.T) { + var buf bytes.Buffer - u8a = [8]uint8{9, 10, 11, 12, 13, 14, 15, 16} - u16a = [8]uint16{17, 18, 19, 20, 21, 22, 23, 24} - ) - // pack tests - if err := Pack(&buf, i8); err != nil { - t.Fatal(err) - } - if err := Pack(&buf, i16); err != nil { - t.Fatal(err) - } - if err := Pack(&buf, i32); err != nil { - t.Fatal(err) - } - if err := Pack(&buf, i64); err != nil { - t.Fatal(err) - } - if err := Pack(&buf, u8); err != nil { - t.Fatal(err) - } - if err := Pack(&buf, u16); err != nil { - t.Fatal(err) - } - if err := Pack(&buf, u32); err != nil { - t.Fatal(err) - } - if err := Pack(&buf, u64); err != nil { - t.Fatal(err) - } - if err := Pack(&buf, u8a[:]); err != nil { - t.Fatal(err) - } - if err := Pack(&buf, u16a[:]); err != nil { - t.Fatal(err) - } - if !bytes.Equal(buf.Bytes(), packableReference) { - fmt.Println(buf.Bytes()) - fmt.Println(packableReference) - t.Fatal("Packable Pack() did not match reference.") - } - // unpack tests - i8 = 0 - i16 = 0 - i32 = 0 - i64 = 0 - u8 = 0 - u16 = 0 - u32 = 0 - u64 = 0 - if err := Unpack(&buf, &i8); err != nil { - t.Fatal(err) - } - if err := Unpack(&buf, &i16); err != nil { - t.Fatal(err) - } - if err := Unpack(&buf, &i32); err != nil { - t.Fatal(err) + // 定义测试用例 + cases := []testCase{ + {"int8", int8(1), int8(1)}, + {"int16", int16(2), int16(2)}, + {"int32", int32(3), int32(3)}, + {"int64", int64(4), int64(4)}, + {"uint8", uint8(5), uint8(5)}, + {"uint16", uint16(6), uint16(6)}, + {"uint32", uint32(7), uint32(7)}, + {"uint64", uint64(8), uint64(8)}, + {"uint8 array", [8]uint8{9, 10, 11, 12, 13, 14, 15, 16}, [8]uint8{9, 10, 11, 12, 13, 14, 15, 16}}, + {"uint16 array", [8]uint16{17, 18, 19, 20, 21, 22, 23, 24}, [8]uint16{17, 18, 19, 20, 21, 22, 23, 24}}, } - if err := Unpack(&buf, &i64); err != nil { - t.Fatal(err) - } - if err := Unpack(&buf, &u8); err != nil { - t.Fatal(err) - } - if err := Unpack(&buf, &u16); err != nil { - t.Fatal(err) - } - if err := Unpack(&buf, &u32); err != nil { - t.Fatal(err) - } - if err := Unpack(&buf, &u64); err != nil { - t.Fatal(err) - } - if err := Unpack(&buf, u8a[:]); err != nil { - t.Fatal(err) - } - if err := Unpack(&buf, u16a[:]); err != nil { - t.Fatal(err) - } - // unpack checks - if i8 != 1 || i16 != 2 || i32 != 3 || i64 != 4 { - t.Fatal("Signed integer unpack failed.") - } - if u8 != 5 || u16 != 6 || u32 != 7 || u64 != 8 { - t.Fatal("Unsigned integer unpack failed.") - } - for i := 0; i < 8; i++ { - if u8a[i] != uint8(i+9) { - t.Fatal("uint8 array unpack failed.") + + // Pack 测试 + t.Run("Pack", func(t *testing.T) { + for _, tc := range cases { + t.Run(tc.name, func(t *testing.T) { + if err := Pack(&buf, tc.value); err != nil { + t.Fatalf("Pack %s failed: %v", tc.name, err) + } + }) } - } - for i := 0; i < 8; i++ { - if u16a[i] != uint16(i+17) { - t.Fatal("uint16 array unpack failed.") + + if !bytes.Equal(buf.Bytes(), testPackableBytes) { + fmt.Println(buf.Bytes()) + fmt.Println(testPackableBytes) + t.Fatal("Packable Pack() did not match reference.") } - } + }) + + // Unpack 测试 + t.Run("Unpack", func(t *testing.T) { + for _, tc := range cases { + t.Run(tc.name, func(t *testing.T) { + // 创建一个与期望类型相同的变量来存储解包结果 + value := reflect.New(reflect.TypeOf(tc.expected)).Interface() + if err := Unpack(&buf, value); err != nil { + t.Fatalf("Unpack %s failed: %v", tc.name, err) + } + // 解引用指针并比较值 + actual := reflect.ValueOf(value).Elem().Interface() + if !reflect.DeepEqual(actual, tc.expected) { + t.Errorf("%s: expected %v, got %v", tc.name, tc.expected, actual) + } + }) + } + }) } diff --git a/packer.go b/packer.go index a3a91a2..38ac65a 100644 --- a/packer.go +++ b/packer.go @@ -5,9 +5,18 @@ import ( "reflect" ) +// Packer 定义了数据打包和解包的基本接口 +// 实现此接口的类型可以将自身序列化为字节流,并从字节流中反序列化 type Packer interface { + // Pack 将值序列化到字节缓冲区中 Pack(buf []byte, val reflect.Value, options *Options) (int, error) + + // Unpack 从 Reader 中读取数据并反序列化到值中 Unpack(r io.Reader, val reflect.Value, options *Options) error + + // Sizeof 返回值序列化后的字节大小 Sizeof(val reflect.Value, options *Options) int + + // String 返回类型的字符串表示 String() string } diff --git a/parse.go b/parse.go index be0afa7..7f5cb97 100644 --- a/parse.go +++ b/parse.go @@ -11,220 +11,280 @@ import ( "sync" ) -// struc:"int32,big,sizeof=Data,skip,sizefrom=Len" +// 标签格式示例:struc:"int32,big,sizeof=Data,skip,sizefrom=Len" +// 标签选项说明: +// - int32: 字段类型 +// - big/little: 字节序 +// - sizeof=Field: 指定字段大小来源 +// - skip: 跳过该字段 +// - sizefrom=Field: 指定长度来源字段 +// strucTag 定义了结构体字段标签的解析结果 +// 包含了字段的类型、字节序、大小引用等信息 type strucTag struct { - Type string - Order binary.ByteOrder - Sizeof string - Skip bool - Sizefrom string + Type string // 字段类型(如 int32, uint8 等) + Order binary.ByteOrder // 字节序(大端或小端) + Sizeof string // 大小引用字段名 + Skip bool // 是否跳过该字段 + Sizefrom string // 长度来源字段名 } -func parseStrucTag(tag reflect.StructTag) *strucTag { - t := &strucTag{ +// parseStrucTag 解析结构体字段的标签 +// 支持 struc 和 struct 两种标签名(向后兼容) +func parseStrucTag(fieldTag reflect.StructTag) *strucTag { + // 初始化标签结构体,默认使用大端字节序 + parsedTag := &strucTag{ Order: binary.BigEndian, } - tagStr := tag.Get("struc") - if tagStr == "" { - // someone's going to typo this (I already did once) - // sorry if you made a module actually using this tag - // and you're mad at me now - tagStr = tag.Get("struct") - } - for _, s := range strings.Split(tagStr, ",") { - if strings.HasPrefix(s, "sizeof=") { - tmp := strings.SplitN(s, "=", 2) - t.Sizeof = tmp[1] - } else if strings.HasPrefix(s, "sizefrom=") { - tmp := strings.SplitN(s, "=", 2) - t.Sizefrom = tmp[1] - } else if s == "big" { - t.Order = binary.BigEndian - } else if s == "little" { - t.Order = binary.LittleEndian - } else if s == "skip" { - t.Skip = true - } else { - t.Type = s + + // 获取 struc 标签,如果不存在则尝试获取 struct 标签 + tagString := fieldTag.Get("struc") + if tagString == "" { + tagString = fieldTag.Get("struct") + } + + // 处理 "-" 标签,表示完全忽略该字段 + if tagString == "-" { + parsedTag.Skip = true + return parsedTag + } + + for _, option := range strings.Split(tagString, ",") { + if strings.HasPrefix(option, "sizeof=") { + parts := strings.SplitN(option, "=", 2) + parsedTag.Sizeof = parts[1] + } else if strings.HasPrefix(option, "sizefrom=") { + parts := strings.SplitN(option, "=", 2) + parsedTag.Sizefrom = parts[1] + } else if option == "big" { + parsedTag.Order = binary.BigEndian + } else if option == "little" { + parsedTag.Order = binary.LittleEndian + } else if option == "skip" { + parsedTag.Skip = true + } else if option != "" { + parsedTag.Type = option } } - return t + return parsedTag } -var typeLenRe = regexp.MustCompile(`^\[(\d*)\]`) +// arrayLengthParseRegex 用于匹配数组长度的正则表达式: [数字] +var arrayLengthParseRegex = regexp.MustCompile(`^\[(\d*)\]`) -func parseField(f reflect.StructField) (fd *Field, tag *strucTag, err error) { - tag = parseStrucTag(f.Tag) +// parseStructField 解析单个结构体字段 +func parseStructField(structField reflect.StructField) (fieldDesc *Field, fieldTag *strucTag, err error) { + fieldTag = parseStrucTag(structField.Tag) var ok bool - fd = &Field{ - Name: f.Name, - Len: 1, - Order: tag.Order, - Slice: false, - kind: f.Type.Kind(), - } - switch fd.kind { + + fieldDesc = acquireField() + + fieldDesc.Name = structField.Name + fieldDesc.Length = 1 + fieldDesc.ByteOrder = fieldTag.Order + fieldDesc.IsSlice = false + fieldDesc.kind = structField.Type.Kind() + + switch fieldDesc.kind { case reflect.Array: - fd.Slice = true - fd.Array = true - fd.Len = f.Type.Len() - fd.kind = f.Type.Elem().Kind() + fieldDesc.IsSlice = true + fieldDesc.IsArray = true + fieldDesc.Length = structField.Type.Len() + fieldDesc.kind = structField.Type.Elem().Kind() case reflect.Slice: - fd.Slice = true - fd.Len = -1 - fd.kind = f.Type.Elem().Kind() + fieldDesc.IsSlice = true + fieldDesc.Length = -1 + fieldDesc.kind = structField.Type.Elem().Kind() case reflect.Ptr: - fd.Ptr = true - fd.kind = f.Type.Elem().Kind() + fieldDesc.IsPointer = true + fieldDesc.kind = structField.Type.Elem().Kind() } - // check for custom types - tmp := reflect.New(f.Type) - if _, ok := tmp.Interface().(Custom); ok { - fd.Type = CustomType + + // 检查是否为自定义类型 + tempValue := reflect.New(structField.Type) + if _, ok := tempValue.Interface().(CustomBinaryer); ok { + fieldDesc.Type = CustomType return } + var defTypeOk bool - fd.defType, defTypeOk = reflectTypeMap[fd.kind] - // find a type in the struct tag - pureType := typeLenRe.ReplaceAllLiteralString(tag.Type, "") - if fd.Type, ok = typeLookup[pureType]; ok { - fd.Len = 1 - match := typeLenRe.FindAllStringSubmatch(tag.Type, -1) - if len(match) > 0 && len(match[0]) > 1 { - fd.Slice = true - first := match[0][1] - // Field.Len = -1 indicates a []slice - if first == "" { - fd.Len = -1 + fieldDesc.defType, defTypeOk = typeKindToType[fieldDesc.kind] + + // 从结构体标签中查找类型 + pureType := arrayLengthParseRegex.ReplaceAllLiteralString(fieldTag.Type, "") + if fieldDesc.Type, ok = typeStrToType[pureType]; ok { + fieldDesc.Length = 1 + // 解析数组长度 + matches := arrayLengthParseRegex.FindAllStringSubmatch(fieldTag.Type, -1) + if len(matches) > 0 && len(matches[0]) > 1 { + fieldDesc.IsSlice = true + lengthStr := matches[0][1] + if lengthStr == "" { + fieldDesc.Length = -1 // 动态长度切片 } else { - fd.Len, err = strconv.Atoi(first) + fieldDesc.Length, err = strconv.Atoi(lengthStr) } } return } - // the user didn't specify a type - switch f.Type { + + // 处理特殊类型 Size_t 和 Off_t + switch structField.Type { case reflect.TypeOf(Size_t(0)): - fd.Type = SizeType + fieldDesc.Type = SizeType case reflect.TypeOf(Off_t(0)): - fd.Type = OffType + fieldDesc.Type = OffType default: if defTypeOk { - fd.Type = fd.defType + fieldDesc.Type = fieldDesc.defType } else { - err = errors.New(fmt.Sprintf("struc: Could not resolve field '%v' type '%v'.", f.Name, f.Type)) + releaseField(fieldDesc) + err = fmt.Errorf("struc: Could not resolve field '%v' type '%v'.", structField.Name, structField.Type) + fieldDesc = nil } } return } -func parseFieldsLocked(v reflect.Value) (Fields, error) { - // we need to repeat this logic because parseFields() below can't be recursively called due to locking - for v.Kind() == reflect.Ptr { - v = v.Elem() +// handleSizeofTag 处理字段的 sizeof 标签 +func handleSizeofTag(fieldDesc *Field, fieldTag *strucTag, structType reflect.Type, field reflect.StructField, sizeofMap map[string][]int) error { + if fieldTag.Sizeof != "" { + targetField, ok := structType.FieldByName(fieldTag.Sizeof) + if !ok { + return fmt.Errorf("struc: `sizeof=%s` field does not exist", fieldTag.Sizeof) + } + fieldDesc.Sizeof = targetField.Index + sizeofMap[fieldTag.Sizeof] = field.Index } - t := v.Type() - if v.NumField() < 1 { - return nil, errors.New("struc: Struct has no fields.") + return nil +} + +// handleSizefromTag 处理字段的 sizefrom 标签 +func handleSizefromTag(fieldDesc *Field, fieldTag *strucTag, structType reflect.Type, field reflect.StructField, sizeofMap map[string][]int) error { + if sizefrom, ok := sizeofMap[field.Name]; ok { + fieldDesc.Sizefrom = sizefrom } - sizeofMap := make(map[string][]int) - fields := make(Fields, v.NumField()) - for i := 0; i < t.NumField(); i++ { - field := t.Field(i) - f, tag, err := parseField(field) - if tag.Skip { - continue + if fieldTag.Sizefrom != "" { + sourceField, ok := structType.FieldByName(fieldTag.Sizefrom) + if !ok { + return fmt.Errorf("struc: `sizefrom=%s` field does not exist", fieldTag.Sizefrom) } + fieldDesc.Sizefrom = sourceField.Index + } + return nil +} + +// handleNestedStruct 处理嵌套结构体字段 +func handleNestedStruct(fieldDesc *Field, field reflect.StructField) error { + if fieldDesc.Type == Struct { + fieldType := field.Type + if fieldDesc.IsPointer { + fieldType = fieldType.Elem() + } + if fieldDesc.IsSlice { + fieldType = fieldType.Elem() + } + tempValue := reflect.New(fieldType) + nestedFields, err := parseFields(tempValue.Elem()) if err != nil { + return err + } + fieldDesc.NestFields = nestedFields + } + return nil +} + +// validateSliceLength 验证切片长度 +func validateSliceLength(fieldDesc *Field, field reflect.StructField) error { + if fieldDesc.Length == -1 && fieldDesc.Sizefrom == nil { + return fmt.Errorf("struc: field `%s` is a slice with no length or sizeof field", field.Name) + } + return nil +} + +// parseFieldsLocked 在加锁状态下解析结构体的所有字段 +func parseFieldsLocked(structValue reflect.Value) (Fields, error) { + for structValue.Kind() == reflect.Ptr { + structValue = structValue.Elem() + } + structType := structValue.Type() + + if structValue.NumField() < 1 { + return nil, errors.New("struc: Struct has no fields.") + } + + sizeofMap := acquireSizeofMap() + defer releaseSizeofMap(sizeofMap) + + fields := make(Fields, structValue.NumField()) + + for i := 0; i < structType.NumField(); i++ { + field := structType.Field(i) + + fieldDesc, fieldTag, err := parseStructField(field) + if err != nil { + releaseFields(fields) return nil, err } - if !v.Field(i).CanSet() { + + if fieldTag.Skip || !structValue.Field(i).CanSet() { continue } - f.Index = i - if tag.Sizeof != "" { - target, ok := t.FieldByName(tag.Sizeof) - if !ok { - return nil, fmt.Errorf("struc: `sizeof=%s` field does not exist", tag.Sizeof) - } - f.Sizeof = target.Index - sizeofMap[tag.Sizeof] = field.Index - } - if sizefrom, ok := sizeofMap[field.Name]; ok { - f.Sizefrom = sizefrom + + fieldDesc.Index = i + + if err := handleSizeofTag(fieldDesc, fieldTag, structType, field, sizeofMap); err != nil { + releaseFields(fields) + return nil, err } - if tag.Sizefrom != "" { - source, ok := t.FieldByName(tag.Sizefrom) - if !ok { - return nil, fmt.Errorf("struc: `sizefrom=%s` field does not exist", tag.Sizefrom) - } - f.Sizefrom = source.Index + + if err := handleSizefromTag(fieldDesc, fieldTag, structType, field, sizeofMap); err != nil { + releaseFields(fields) + return nil, err } - if f.Len == -1 && f.Sizefrom == nil { - return nil, fmt.Errorf("struc: field `%s` is a slice with no length or sizeof field", field.Name) + + if err := validateSliceLength(fieldDesc, field); err != nil { + releaseFields(fields) + return nil, err } - // recurse into nested structs - // TODO: handle loops (probably by indirecting the []Field and putting pointer in cache) - if f.Type == Struct { - typ := field.Type - if f.Ptr { - typ = typ.Elem() - } - if f.Slice { - typ = typ.Elem() - } - f.Fields, err = parseFieldsLocked(reflect.New(typ)) - if err != nil { - return nil, err - } + + if err := handleNestedStruct(fieldDesc, field); err != nil { + releaseFields(fields) + return nil, err } - fields[i] = f + + fields[i] = fieldDesc } return fields, nil } -var fieldCache = make(map[reflect.Type]Fields) -var fieldCacheLock sync.RWMutex -var parseLock sync.Mutex +// parsedStructFieldCache 存储每个结构体类型的已解析字段 (并发安全) +var ( + parsedStructFieldCache = sync.Map{} +) -func fieldCacheLookup(t reflect.Type) Fields { - fieldCacheLock.RLock() - defer fieldCacheLock.RUnlock() - if cached, ok := fieldCache[t]; ok { - return cached +// fieldCacheLookup 查找类型的缓存字段 +func fieldCacheLookup(structType reflect.Type) Fields { + if cached, ok := parsedStructFieldCache.Load(structType); ok { + return cached.(Fields) } return nil } -func parseFields(v reflect.Value) (Fields, error) { - for v.Kind() == reflect.Ptr { - v = v.Elem() - } - t := v.Type() - - // fast path: hopefully the field parsing is already cached - if cached := fieldCacheLookup(t); cached != nil { - return cached, nil - } - - // hold a global lock so multiple goroutines can't parse (the same) fields at once - parseLock.Lock() - defer parseLock.Unlock() - - // check cache a second time, in case parseLock was just released by - // another thread who filled the cache for us - if cached := fieldCacheLookup(t); cached != nil { +// parseFields 解析结构体的所有字段 +// 首先尝试从缓存中获取,如果未命中则进行解析并缓存结果 +func parseFields(structValue reflect.Value) (Fields, error) { + structType := structValue.Type() + if cached := fieldCacheLookup(structType); cached != nil { return cached, nil } - // no luck, time to parse and fill the cache ourselves - fields, err := parseFieldsLocked(v) + fields, err := parseFieldsLocked(structValue) if err != nil { return nil, err } - fieldCacheLock.Lock() - fieldCache[t] = fields - fieldCacheLock.Unlock() + + parsedStructFieldCache.Store(structType, fields) + return fields, nil } diff --git a/parse_test.go b/parse_test.go index 861fdd1..c32736c 100644 --- a/parse_test.go +++ b/parse_test.go @@ -60,3 +60,53 @@ func TestNestedParseError(t *testing.T) { t.Fatal("failed to error on bad nested struct") } } + +// 测试忽略字段的结构体 +// Test struct with ignored fields +type ignoreFieldsStruct struct { + Public int `struc:"int32"` + Ignored bool `struc:"-"` + Compat string `struct:"-"` + Private float64 `struc:"float64"` +} + +// TestIgnoreFields 测试 struc:"-" 标签的功能 +// 验证带有 "-" 标签的字段会被正确忽略 +// +// TestIgnoreFields tests the struc:"-" tag functionality +// Verifies that fields with "-" tag are correctly ignored +func TestIgnoreFields(t *testing.T) { + val := reflect.ValueOf(&ignoreFieldsStruct{}) + fields, err := parseFields(val.Elem()) + if err != nil { + t.Fatalf("Failed to parse struct with ignored fields: %v", err) + } + + // 检查字段数量是否正确(应该只有 Public 和 Private 两个字段) + // Check if the number of fields is correct (should only have Public and Private fields) + expectedFields := 2 + actualFields := 0 + for _, field := range fields { + if field != nil { + actualFields++ + } + } + if actualFields != expectedFields { + t.Errorf("Expected %d fields, got %d fields", expectedFields, actualFields) + } + + // 验证 Ignored 和 Compat 字段被正确标记为跳过 + // Verify that Ignored and Compat fields are correctly marked as skipped + if fields[1] != nil || fields[2] != nil { + t.Error("Fields marked with '-' tag were not properly ignored") + } + + // 验证 Public 和 Private 字段被正确解析 + // Verify that Public and Private fields are correctly parsed + if fields[0] == nil || fields[0].Type != Int32 { + t.Error("Public field was not properly parsed") + } + if fields[3] == nil || fields[3].Type != Float64 { + t.Error("Private field was not properly parsed") + } +} diff --git a/pool.go b/pool.go new file mode 100644 index 0000000..e57b42b --- /dev/null +++ b/pool.go @@ -0,0 +1,187 @@ +package struc + +import ( + "bytes" + "encoding/binary" + "reflect" + "runtime" + "sync" + "sync/atomic" +) + +// MaxBufferCapSize 定义了缓冲区的最大容量限制 +// 超过此限制的缓冲区不会被放入对象池 +const MaxBufferCapSize = 1 << 20 + +// MaxBytesSliceSize 定义了字节切片的最大容量限制 +// 超过此限制的字节切片不会被放入对象池 +const MaxBytesSliceSize = 4096 + +// bufferPool 用于减少[]byte的内存分配 +var bufferPool = sync.Pool{ + New: func() interface{} { + return bytes.NewBuffer(make([]byte, 0, 1024)) + }, +} + +// fieldPool 是 Field 对象的全局池 +var fieldPool = sync.Pool{ + New: func() interface{} { + return &Field{ + Length: 1, + ByteOrder: binary.BigEndian, // 默认使用大端字节序 + } + }, +} + +// sizeofMapPool 是用于复用 sizeofMap 的对象池 +var sizeofMapPool = sync.Pool{ + New: func() interface{} { + return make(map[string][]int) + }, +} + +// acquireSizeofMap 从对象池获取一个 sizeofMap +func acquireSizeofMap() map[string][]int { + return sizeofMapPool.Get().(map[string][]int) +} + +// releaseSizeofMap 将 sizeofMap 放回对象池 +func releaseSizeofMap(m map[string][]int) { + if m == nil { + return + } + // 清空 map + for k := range m { + delete(m, k) + } + sizeofMapPool.Put(m) +} + +// acquireBuffer 从对象池获取缓冲区 +func acquireBuffer() *bytes.Buffer { + return bufferPool.Get().(*bytes.Buffer) +} + +// releaseBuffer 将缓冲区放回对象池 +func releaseBuffer(buf *bytes.Buffer) { + if buf == nil || buf.Cap() > MaxBufferCapSize { + return + } + + buf.Reset() + bufferPool.Put(buf) +} + +// acquireField 从对象池获取一个 Field 对象 +func acquireField() *Field { + return fieldPool.Get().(*Field) +} + +// releaseField 将 Field 对象放回对象池 +func releaseField(f *Field) { + if f == nil { + return + } + // 重置字段状态 + f.Name = "" + f.IsPointer = false + f.Index = 0 + f.Type = 0 + f.defType = 0 + f.IsArray = false + f.IsSlice = false + f.Length = 1 + f.ByteOrder = binary.BigEndian + f.Sizeof = nil + f.Sizefrom = nil + f.NestFields = nil + f.kind = reflect.Invalid + + fieldPool.Put(f) +} + +// releaseFields 将 Fields 切片中的所有 Field 对象放回对象池 +func releaseFields(fields Fields) { + if fields == nil { + return + } + for _, f := range fields { + releaseField(f) + } +} + +// BytesSlicePool 是一个用于管理共享字节切片的结构体 +// 它提供了线程安全的切片分配和重用功能 +type BytesSlicePool struct { + bytes []byte // 底层字节数组 + offset int32 // 当前偏移量 + size int // 当前块大小 + mu sync.Mutex // 互斥锁用于保护并发访问 +} + +// NewBytesSlicePool 创建一个新的 BytesSlicePool 实例 +// 初始化时,会分配一个 4096 字节的字节数组 +func NewBytesSlicePool(size int) *BytesSlicePool { + // 如果 size 小于等于 0 或者大于 MaxBytesSliceSize,则使用 MaxBytesSliceSize + if size > MaxBytesSliceSize || size <= 0 { + size = MaxBytesSliceSize + } + + return &BytesSlicePool{ + bytes: make([]byte, size), + offset: 0, + size: size, + mu: sync.Mutex{}, + } +} + +// GetSlice 返回指定大小的字节切片 +// 如果当前块空间不足,会分配新的块并重置偏移量 +func (b *BytesSlicePool) GetSlice(size int) []byte { + // 如果请求的大小超过了最大限制,直接分配新的切片 + if size > b.size { + return make([]byte, size) + } + + // 快速路径:尝试有限次数的原子操作,使用退避策略减少 CPU 压力 + for i := 0; i < 4; i++ { // 最多尝试 4 次 / Maximum 4 attempts + currentOffset := atomic.LoadInt32(&b.offset) + + if int(currentOffset)+size > b.size { + break + } + + newOffset := currentOffset + int32(size) + if atomic.CompareAndSwapInt32(&b.offset, currentOffset, newOffset) { + return b.bytes[currentOffset:newOffset] + } + + // 简单的退避策略,防止 CPU 过热 + if i > 0 { + for j := 0; j < (1 << i); j++ { + // 让出 CPU,允许其他 goroutine 执行 + runtime.Gosched() + } + } + } + + // 慢路径:多次尝试失败或空间不足时使用 + return b.getSliceSlow(size) +} + +// getSliceSlow 是 GetSlice 的慢路径实现 +// 使用互斥锁保护重置操作,减少竞争 +func (b *BytesSlicePool) getSliceSlow(size int) []byte { + b.mu.Lock() + defer b.mu.Unlock() + + if int(b.offset)+size > b.size { + b.bytes = make([]byte, b.size) + b.offset = 0 + } + start := b.offset + b.offset += int32(size) + + return b.bytes[start:b.offset] +} diff --git a/pprof/bench_pprof.sh b/pprof/bench_pprof.sh new file mode 100644 index 0000000..9324637 --- /dev/null +++ b/pprof/bench_pprof.sh @@ -0,0 +1,32 @@ + +# 运行解码基准测试 +go test -bench=Decode -benchmem -cpuprofile=cpu_decode.prof -memprofile=mem_decode.prof -trace=trace_decode.out + +# 运行编码基准测试 +go test -bench=Encode -benchmem -cpuprofile=cpu_encode.prof -memprofile=mem_encode.prof -trace=trace_encode.out + +# 查看基准测试结果 +go tool pprof -http=:8080 cpu_decode.prof +go tool pprof -http=:8080 mem_decode.prof +go tool trace trace_decode.out + +go tool pprof -http=:8080 cpu_encode.prof +go tool pprof -http=:8080 mem_encode.prof +go tool trace trace_encode.out + +# 转换为svg +go tool pprof -svg cpu_decode.prof > cpu_decode.svg +go tool pprof -svg mem_decode.prof > mem_decode.svg +go tool pprof -svg cpu_encode.prof > cpu_encode.svg +go tool pprof -svg mem_encode.prof > mem_encode.svg + + +# 查看trace +go tool trace trace_decode.out +go tool trace trace_encode.out + +# 查看 text +go tool pprof -text cpu_decode.prof +go tool pprof -text mem_decode.prof +go tool pprof -text cpu_encode.prof +go tool pprof -text mem_encode.prof diff --git a/pprof/cpu_decode.svg b/pprof/cpu_decode.svg new file mode 100644 index 0000000..d66767f --- /dev/null +++ b/pprof/cpu_decode.svg @@ -0,0 +1,2824 @@ + + + + + + +struc.test.exe + + +cluster_L + + + + +File: struc.test.exe + + +File: struc.test.exe +Build ID: B:\Temp\go-build495253450\b001\struc.test.exe2025-01-19 09:51:38 +0800 CST +Type: cpu +Time: Jan 19, 2025 at 9:51am (CST) +Duration: 8.95s, Total samples = 9.48s (105.95%) +Showing nodes accounting for 8.09s, 85.34% of 9.48s total +Dropped 173 nodes (cum <= 0.05s) +Showing top 80 nodes out of 118 +See https://git.io/JfYMW for how to read the graph + + + + + +N1 + + +testing +(*B) +runN +0 of 8.62s (90.93%) + + + + + +N13 + + +struc +BenchmarkStdlibDecode +0.03s (0.32%) +of 1.39s (14.66%) + + + + + +N1->N13 + + + + + + + 1.39s + + + + + +N16 + + +struc +BenchmarkManualDecode +0.28s (2.95%) +of 1.15s (12.13%) + + + + + +N1->N16 + + + + + + + 1.15s + + + + + +N27 + + +struc +BenchmarkSliceDecode +0.02s (0.21%) +of 1.66s (17.51%) + + + + + +N1->N27 + + + + + + + 1.66s + + + + + +N28 + + +struc +BenchmarkDecode +0.01s (0.11%) +of 1.59s (16.77%) + + + + + +N1->N28 + + + + + + + 1.59s + + + + + +N38 + + +struc +BenchmarkArrayDecode +0 of 1.60s (16.88%) + + + + + +N1->N38 + + + + + + + 1.60s + + + + + +N78 + + +runtime +newstack +0 of 0.13s (1.37%) + + + + + +N1->N78 + + + + + + + 0.01s + + + + + +N2 + + +struc +Unpack +0 of 5.75s (60.65%) + + + + + +N8 + + +struc +UnpackWithOptions +0.09s (0.95%) +of 5.75s (60.65%) + + + + + +N2->N8 + + + + + + + 5.75s + + + + + +N3 + + +struc +Fields +Unpack +0.32s (3.38%) +of 5.05s (53.27%) + + + + + +N6 + + +struc +Fields +unpackBasicType +0.21s (2.22%) +of 2.19s (23.10%) + + + + + +N3->N6 + + + + + + + 2.19s + + + + + +N26 + + +reflect +Value +Field +0.17s (1.79%) +of 0.31s (3.27%) + + + + + +N3->N26 + + + + + + + 0.23s + + + + + +N33 + + +struc +Fields +unpackStruct +0.01s (0.11%) +of 2.46s (25.95%) + + + + + +N3->N33 + + + + + + + 2.46s + + + + + +N49 + + +reflect +Value +Kind +0.05s (0.53%) +of 0.11s (1.16%) + + + + + +N3->N49 + + + + + + + 0.07s + (inline) + + + + + +N4 + + +testing +(*B) +launch +0 of 8.61s (90.82%) + + + + + +N4->N1 + + + + + + + 8.61s + + + + + +N5 + + +runtime +mallocgc +0.66s (6.96%) +of 1.73s (18.25%) + + + + + +N34 + + +runtime +nextFreeFast +0.27s (2.85%) + + + + + +N5->N34 + + + + + + + 0.27s + (inline) + + + + + +N51 + + +runtime +memclrNoHeapPointers +0.12s (1.27%) + + + + + +N5->N51 + + + + + + + 0.12s + + + + + +N54 + + +runtime +add +0.09s (0.95%) + + + + + +N5->N54 + + + + + + + 0.01s + (inline) + + + + + +N58 + + +runtime +acquirem +0.10s (1.05%) + + + + + +N5->N58 + + + + + + + 0.10s + (inline) + + + + + +N63 + + +runtime +getMCache +0.08s (0.84%) + + + + + +N5->N63 + + + + + + + 0.08s + (inline) + + + + + +N70 + + +runtime +(*mspan) +writeHeapBitsSmall +0.06s (0.63%) +of 0.08s (0.84%) + + + + + +N5->N70 + + + + + + + 0.08s + + + + + +N74 + + +runtime +deductAssistCredit +0.06s (0.63%) +of 0.08s (0.84%) + + + + + +N5->N74 + + + + + + + 0.08s + + + + + +N14 + + +struc +(*BytesSlicePool) +GetSlice +0.53s (5.59%) +of 0.97s (10.23%) + + + + + +N6->N14 + + + + + + + 0.97s + + + + + +N19 + + +struc +(*Field) +Unpack +0.10s (1.05%) +of 0.71s (7.49%) + + + + + +N6->N19 + + + + + + + 0.71s + + + + + +N41 + + +struc +Type +Resolve +0.10s (1.05%) + + + + + +N6->N41 + + + + + + + 0.04s + + + + + +N59 + + +io +ReadFull +0 of 0.31s (3.27%) + + + + + +N6->N59 + + + + + + + 0.26s + (inline) + + + + + +N7 + + +struc +Fields +unpackStructSlice +0.16s (1.69%) +of 2.39s (25.21%) + + + + + +N7->N3 + + + + + + + 0.26s + + + + + +N12 + + +struc +parseFields +0.02s (0.21%) +of 1.94s (20.46%) + + + + + +N7->N12 + + + + + + + 1.63s + + + + + +N21 + + +reflect +Value +Index +0.22s (2.32%) +of 0.33s (3.48%) + + + + + +N7->N21 + + + + + + + 0.09s + + + + + +N46 + + +reflect +MakeSlice +0.04s (0.42%) +of 0.19s (2.00%) + + + + + +N7->N46 + + + + + + + 0.19s + + + + + +N8->N3 + + + + + + + 5.05s + + + + + +N23 + + +struc +prepareValueForPacking +0.03s (0.32%) +of 0.57s (6.01%) + + + + + +N8->N23 + + + + + + + 0.57s + + + + + +N9 + + +runtime +mapaccess2 +0.83s (8.76%) +of 1.50s (15.82%) + + + + + +N32 + + +runtime +nilinterhash +0.15s (1.58%) +of 0.44s (4.64%) + + + + + +N9->N32 + + + + + + + 0.44s + + + + + +N50 + + +runtime +nilinterequal +0.10s (1.05%) +of 0.15s (1.58%) + + + + + +N9->N50 + + + + + + + 0.15s + + + + + +N9->N54 + + + + + + + 0.08s + (inline) + + + + + +N10 + + +sync +(*Map) +Load +0.11s (1.16%) +of 1.79s (18.88%) + + + + + +N10->N9 + + + + + + + 1.50s + + + + + +N45 + + +sync +(*entry) +load +0.17s (1.79%) + + + + + +N10->N45 + + + + + + + 0.17s + (inline) + + + + + +N11 + + +binary +(*decoder) +value +0.25s (2.64%) +of 0.74s (7.81%) + + + + + +N11->N21 + + + + + + + 0.16s + + + + + +N11->N26 + + + + + + + 0.08s + + + + + +N11->N49 + + + + + + + 0.04s + (inline) + + + + + +N57 + + +reflect +Value +SetUint +0.07s (0.74%) + + + + + +N11->N57 + + + + + + + 0.05s + + + + + +N67 + + +reflect +Value +SetInt +0.05s (0.53%) + + + + + +N11->N67 + + + + + + + 0.03s + + + + + +N76 + + +binary +(*decoder) +uint16 +0.06s (0.63%) + + + + + +N11->N76 + + + + + + + 0.06s + (inline) + + + + + +N22 + + +struc +fieldCacheLookup +0.16s (1.69%) +of 1.85s (19.51%) + + + + + +N12->N22 + + + + + + + 1.85s + (inline) + + + + + +N69 + + +reflect +Value +Type +0.07s (0.74%) + + + + + +N12->N69 + + + + + + + 0.07s + (inline) + + + + + +N15 + + +runtime +makeslice +0.08s (0.84%) +of 1.03s (10.86%) + + + + + +N13->N15 + + + + + + + 0.10s + + + + + +N18 + + +binary +Read +0 of 1.05s (11.08%) + + + + + +N13->N18 + + + + + + + 1.05s + + + + + +N44 + + +bytes +NewReader +0.01s (0.11%) +of 0.28s (2.95%) + + + + + +N13->N44 + + + + + + + 0.17s + (inline) + + + + + +N48 + + +bytes +(*Reader) +Read +0.06s (0.63%) +of 0.13s (1.37%) + + + + + +N13->N48 + + + + + + + 0.04s + (inline) + + + + + +N14->N15 + + + + + + + 0.04s + + + + + +N24 + + +sync +(*Mutex) +Unlock +0.40s (4.22%) + + + + + +N14->N24 + + + + + + + 0.40s + (inline) + + + + + +N15->N5 + + + + + + + 0.95s + + + + + +N16->N15 + + + + + + + 0.79s + + + + + +N36 + + +runtime +memmove +0.14s (1.48%) + + + + + +N16->N36 + + + + + + + 0.05s + + + + + +N17 + + +runtime +systemstack +0 of 0.59s (6.22%) + + + + + +N80 + + +runtime +gcBgMarkWorker +func2 +0 of 0.36s (3.80%) + + + + + +N17->N80 + + + + + + + 0.36s + + + + + +N18->N11 + + + + + + + 0.74s + + + + + +N18->N15 + + + + + + + 0.10s + + + + + +N18->N59 + + + + + + + 0.05s + (inline) + + + + + +N72 + + +binary +dataSize +0.03s (0.32%) +of 0.13s (1.37%) + + + + + +N18->N72 + + + + + + + 0.13s + + + + + +N20 + + +struc +(*Field) +unpackSingleValue +0.13s (1.37%) +of 0.41s (4.32%) + + + + + +N19->N20 + + + + + + + 0.27s + + + + + +N35 + + +struc +(*Field) +unpackSliceValue +0.05s (0.53%) +of 0.30s (3.16%) + + + + + +N19->N35 + + + + + + + 0.30s + + + + + +N19->N41 + + + + + + + 0.03s + + + + + +N20->N41 + + + + + + + 0.03s + + + + + +N42 + + +struc +(*Field) +unpackIntegerValue +0.13s (1.37%) +of 0.22s (2.32%) + + + + + +N20->N42 + + + + + + + 0.22s + + + + + +N29 + + +abi +(*Type) +Kind +0.19s (2.00%) + + + + + +N21->N29 + + + + + + + 0.05s + (inline) + + + + + +N22->N10 + + + + + + + 1.69s + + + + + +N23->N5 + + + + + + + 0.20s + + + + + +N23->N12 + + + + + + + 0.26s + + + + + +N23->N29 + + + + + + + 0.03s + (inline) + + + + + +N25 + + +runtime +newobject +0.01s (0.11%) +of 0.51s (5.38%) + + + + + +N25->N5 + + + + + + + 0.50s + + + + + +N26->N29 + + + + + + + 0.11s + (inline) + + + + + +N27->N2 + + + + + + + 1.55s + (inline) + + + + + +N53 + + +bytes +NewBuffer +0 of 0.18s (1.90%) + + + + + +N27->N53 + + + + + + + 0.09s + (inline) + + + + + +N28->N2 + + + + + + + 1.47s + (inline) + + + + + +N28->N44 + + + + + + + 0.11s + (inline) + + + + + +N30 + + +io +ReadAtLeast +0.14s (1.48%) +of 0.31s (3.27%) + + + + + +N30->N48 + + + + + + + 0.09s + + + + + +N73 + + +bytes +(*Buffer) +Read +0.06s (0.63%) +of 0.08s (0.84%) + + + + + +N30->N73 + + + + + + + 0.08s + + + + + +N31 + + +runtime +gcDrain +0 of 0.36s (3.80%) + + + + + +N52 + + +runtime +scanobject +0.07s (0.74%) +of 0.14s (1.48%) + + + + + +N31->N52 + + + + + + + 0.13s + + + + + +N56 + + +runtime +markroot +0 of 0.15s (1.58%) + + + + + +N31->N56 + + + + + + + 0.13s + + + + + +N61 + + +runtime +preemptone +0 of 0.17s (1.79%) + + + + + +N31->N61 + + + + + + + 0.10s + + + + + +N43 + + +runtime +typehash +0.09s (0.95%) +of 0.29s (3.06%) + + + + + +N32->N43 + + + + + + + 0.29s + + + + + +N33->N7 + + + + + + + 2.39s + + + + + +N35->N20 + + + + + + + 0.14s + + + + + +N35->N21 + + + + + + + 0.08s + + + + + +N37 + + +runtime +stdcall2 +0.17s (1.79%) + + + + + +N38->N2 + + + + + + + 1.52s + (inline) + + + + + +N38->N53 + + + + + + + 0.08s + (inline) + + + + + +N39 + + +runtime +gcBgMarkWorker +0.02s (0.21%) +of 0.29s (3.06%) + + + + + +N39->N17 + + + + + + + 0.27s + + + + + +N40 + + +runtime +memhash64 +0.20s (2.11%) + + + + + +N42->N57 + + + + + + + 0.02s + + + + + +N42->N67 + + + + + + + 0.02s + + + + + +N43->N40 + + + + + + + 0.20s + + + + + +N44->N25 + + + + + + + 0.27s + + + + + +N46->N25 + + + + + + + 0.07s + + + + + +N47 + + +runtime +schedule +0 of 0.21s (2.22%) + + + + + +N55 + + +runtime +stdcall1 +0.06s (0.63%) + + + + + +N47->N55 + + + + + + + 0.02s + + + + + +N65 + + +runtime +findRunnable +0.01s (0.11%) +of 0.18s (1.90%) + + + + + +N47->N65 + + + + + + + 0.18s + + + + + +N48->N36 + + + + + + + 0.07s + + + + + +N77 + + +reflect +flag +kind +0.06s (0.63%) + + + + + +N49->N77 + + + + + + + 0.06s + (inline) + + + + + +N68 + + +runtime +findObject +0.05s (0.53%) + + + + + +N52->N68 + + + + + + + 0.02s + + + + + +N53->N25 + + + + + + + 0.17s + + + + + +N56->N52 + + + + + + + 0.01s + + + + + +N62 + + +runtime +scanblock +0.06s (0.63%) +of 0.11s (1.16%) + + + + + +N56->N62 + + + + + + + 0.11s + + + + + +N75 + + +runtime +(*mheap) +allocSpan +0.01s (0.11%) +of 0.07s (0.74%) + + + + + +N56->N75 + + + + + + + 0.01s + + + + + +N59->N30 + + + + + + + 0.31s + + + + + +N60 + + +runtime +preemptM +0 of 0.17s (1.79%) + + + + + +N60->N37 + + + + + + + 0.13s + + + + + +N60->N55 + + + + + + + 0.02s + + + + + +N66 + + +runtime +lock +0 of 0.10s (1.05%) + + + + + +N60->N66 + + + + + + + 0.02s + (inline) + + + + + +N61->N60 + + + + + + + 0.17s + + + + + +N62->N68 + + + + + + + 0.03s + + + + + +N64 + + +runtime +mcall +0 of 0.16s (1.69%) + + + + + +N64->N47 + + + + + + + 0.16s + + + + + +N65->N66 + + + + + + + 0.03s + (inline) + + + + + +N79 + + +runtime +lock2 +0.01s (0.11%) +of 0.10s (1.05%) + + + + + +N66->N79 + + + + + + + 0.10s + + + + + +N71 + + +runtime +goschedImpl +0 of 0.09s (0.95%) + + + + + +N71->N47 + + + + + + + 0.02s + + + + + +N71->N55 + + + + + + + 0.02s + + + + + +N71->N66 + + + + + + + 0.04s + (inline) + + + + + +N72->N10 + + + + + + + 0.10s + + + + + +N73->N36 + + + + + + + 0.02s + + + + + +N74->N17 + + + + + + + 0.02s + + + + + +N78->N47 + + + + + + + 0.03s + + + + + +N78->N71 + + + + + + + 0.09s + + + + + +N79->N37 + + + + + + + 0.04s + + + + + +N80->N31 + + + + + + + 0.36s + + + + + diff --git a/pprof/cpu_encode.svg b/pprof/cpu_encode.svg new file mode 100644 index 0000000..0ee0655 --- /dev/null +++ b/pprof/cpu_encode.svg @@ -0,0 +1,2800 @@ + + + + + + +struc.test.exe + + +cluster_L + + + + +File: struc.test.exe + + +File: struc.test.exe +Build ID: D:\progames\GolandProjects\struc\struc.test.exe2025-01-19 09:36:36 +0800 CST +Type: cpu +Time: Jan 19, 2025 at 9:39am (CST) +Duration: 9.21s, Total samples = 10.58s (114.92%) +Showing nodes accounting for 8.80s, 83.18% of 10.58s total +Dropped 179 nodes (cum <= 0.05s) +Dropped 14 edges (freq <= 0.01s) +Showing top 80 nodes out of 120 +See https://git.io/JfYMW for how to read the graph + + + + + +N1 + + +testing +(*B) +runN +0 of 8.79s (83.08%) + + + + + +N23 + + +struc +BenchmarkStdlibEncode +0.01s (0.095%) +of 1.36s (12.85%) + + + + + +N1->N23 + + + + + + + 1.36s + + + + + +N29 + + +struc +BenchmarkManualEncode +0.12s (1.13%) +of 1.60s (15.12%) + + + + + +N1->N29 + + + + + + + 1.60s + + + + + +N34 + + +struc +BenchmarkEncode +0.01s (0.095%) +of 1.65s (15.60%) + + + + + +N1->N34 + + + + + + + 1.65s + + + + + +N43 + + +struc +BenchmarkArrayEncode +0 of 1.53s (14.46%) + + + + + +N1->N43 + + + + + + + 1.53s + + + + + +N47 + + +struc +BenchmarkSliceEncode +0 of 1.58s (14.93%) + + + + + +N1->N47 + + + + + + + 1.58s + + + + + +N59 + + +struc +BenchmarkFullEncode +0 of 1.07s (10.11%) + + + + + +N1->N59 + + + + + + + 1.07s + + + + + +N2 + + +struc +Pack +0.01s (0.095%) +of 5.56s (52.55%) + + + + + +N4 + + +struc +PackWithOptions +0.05s (0.47%) +of 5.55s (52.46%) + + + + + +N2->N4 + + + + + + + 5.55s + + + + + +N3 + + +runtime +mallocgc +0.92s (8.70%) +of 2.43s (22.97%) + + + + + +N31 + + +runtime +nextFreeFast +0.43s (4.06%) + + + + + +N3->N31 + + + + + + + 0.43s + (inline) + + + + + +N42 + + +runtime +memclrNoHeapPointers +0.18s (1.70%) + + + + + +N3->N42 + + + + + + + 0.17s + + + + + +N56 + + +runtime +acquirem +0.11s (1.04%) + + + + + +N3->N56 + + + + + + + 0.11s + (inline) + + + + + +N66 + + +runtime +gcmarknewobject +0.04s (0.38%) +of 0.13s (1.23%) + + + + + +N3->N66 + + + + + + + 0.13s + + + + + +N67 + + +runtime +(*mcache) +refill +0.01s (0.095%) +of 0.12s (1.13%) + + + + + +N3->N67 + + + + + + + 0.12s + + + + + +N70 + + +runtime +heapSetType +0.03s (0.28%) +of 0.16s (1.51%) + + + + + +N3->N70 + + + + + + + 0.16s + + + + + +N74 + + +runtime +deductAssistCredit +0.06s (0.57%) +of 0.08s (0.76%) + + + + + +N3->N74 + + + + + + + 0.08s + + + + + +N75 + + +runtime +publicationBarrier +0.06s (0.57%) + + + + + +N3->N75 + + + + + + + 0.06s + + + + + +N6 + + +struc +Fields +Pack +0.42s (3.97%) +of 3.08s (29.11%) + + + + + +N4->N6 + + + + + + + 3.08s + + + + + +N7 + + +bytes +(*Buffer) +Write +0.22s (2.08%) +of 2.15s (20.32%) + + + + + +N4->N7 + + + + + + + 0.42s + + + + + +N14 + + +struc +Fields +Sizeof +0.24s (2.27%) +of 1.05s (9.92%) + + + + + +N4->N14 + + + + + + + 1.05s + + + + + +N16 + + +runtime +makeslice +0.09s (0.85%) +of 1.81s (17.11%) + + + + + +N4->N16 + + + + + + + 0.16s + + + + + +N21 + + +struc +prepareValueForPacking +0.05s (0.47%) +of 0.78s (7.37%) + + + + + +N4->N21 + + + + + + + 0.78s + + + + + +N5 + + +testing +(*B) +launch +0 of 8.79s (83.08%) + + + + + +N5->N1 + + + + + + + 8.79s + + + + + +N11 + + +struc +(*Field) +Pack +0.07s (0.66%) +of 2.42s (22.87%) + + + + + +N6->N11 + + + + + + + 2.42s + + + + + +N13 + + +reflect +Value +Field +0.31s (2.93%) +of 0.64s (6.05%) + + + + + +N6->N13 + + + + + + + 0.28s + + + + + +N77 + + +reflect +Value +FieldByIndex +0.02s (0.19%) +of 0.06s (0.57%) + + + + + +N6->N77 + + + + + + + 0.06s + + + + + +N22 + + +bytes +(*Buffer) +grow +0.08s (0.76%) +of 1.77s (16.73%) + + + + + +N7->N22 + + + + + + + 1.77s + + + + + +N38 + + +runtime +memmove +0.17s (1.61%) + + + + + +N7->N38 + + + + + + + 0.14s + + + + + +N8 + + +runtime +systemstack +0.01s (0.095%) +of 1.48s (13.99%) + + + + + +N76 + + +runtime +stdcall3 +0.06s (0.57%) + + + + + +N8->N76 + + + + + + + 0.06s + + + + + +N79 + + +runtime +(*sweepLocked) +sweep +(*mheap) +freeSpan +func3 +0 of 0.13s (1.23%) + + + + + +N8->N79 + + + + + + + 0.12s + + + + + +N80 + + +runtime +gcBgMarkWorker +func2 +0 of 0.97s (9.17%) + + + + + +N8->N80 + + + + + + + 0.97s + + + + + +N9 + + +struc +(*Field) +packSingleValue +0.36s (3.40%) +of 1.06s (10.02%) + + + + + +N9->N6 + + + + + + + 0.20s + + + + + +N27 + + +struc +Type +Resolve +0.24s (2.27%) + + + + + +N9->N27 + + + + + + + 0.10s + + + + + +N37 + + +struc +(*Field) +packIntegerValue +0.15s (1.42%) +of 0.34s (3.21%) + + + + + +N9->N37 + + + + + + + 0.34s + + + + + +N63 + + +struc +(*Field) +determineByteOrder +0.09s (0.85%) + + + + + +N9->N63 + + + + + + + 0.09s + (inline) + + + + + +N10 + + +struc +(*Field) +packGenericSlice +0.48s (4.54%) +of 1.62s (15.31%) + + + + + +N10->N9 + + + + + + + 0.63s + + + + + +N12 + + +reflect +Value +Index +0.53s (5.01%) +of 0.80s (7.56%) + + + + + +N10->N12 + + + + + + + 0.49s + + + + + +N10->N27 + + + + + + + 0.02s + + + + + +N11->N9 + + + + + + + 0.45s + + + + + +N19 + + +struc +(*Field) +packSliceValue +0.09s (0.85%) +of 1.83s (17.30%) + + + + + +N11->N19 + + + + + + + 1.83s + + + + + +N11->N27 + + + + + + + 0.09s + + + + + +N20 + + +abi +(*Type) +Kind +0.34s (3.21%) + + + + + +N12->N20 + + + + + + + 0.10s + (inline) + + + + + +N64 + + +reflect +add +0.09s (0.85%) + + + + + +N12->N64 + + + + + + + 0.09s + (inline) + + + + + +N71 + + +reflect +arrayAt +0.07s (0.66%) + + + + + +N12->N71 + + + + + + + 0.07s + (inline) + + + + + +N13->N20 + + + + + + + 0.22s + (inline) + + + + + +N58 + + +abi +Name +IsExported +0.10s (0.95%) + + + + + +N13->N58 + + + + + + + 0.10s + (inline) + + + + + +N14->N13 + + + + + + + 0.21s + + + + + +N24 + + +struc +(*Field) +Size +0.10s (0.95%) +of 0.77s (7.28%) + + + + + +N14->N24 + + + + + + + 0.77s + + + + + +N15 + + +binary +Write +0.02s (0.19%) +of 1.13s (10.68%) + + + + + +N15->N7 + + + + + + + 0.26s + + + + + +N15->N16 + + + + + + + 0.06s + + + + + +N18 + + +binary +(*encoder) +value +0.29s (2.74%) +of 0.70s (6.62%) + + + + + +N15->N18 + + + + + + + 0.70s + + + + + +N41 + + +sync +(*Map) +Load +0.02s (0.19%) +of 0.43s (4.06%) + + + + + +N15->N41 + + + + + + + 0.07s + + + + + +N16->N3 + + + + + + + 1.72s + + + + + +N17 + + +runtime +gcDrain +0.03s (0.28%) +of 0.97s (9.17%) + + + + + +N28 + + +runtime +scanobject +0.12s (1.13%) +of 0.46s (4.35%) + + + + + +N17->N28 + + + + + + + 0.46s + + + + + +N49 + + +runtime +preemptone +0 of 0.36s (3.40%) + + + + + +N17->N49 + + + + + + + 0.27s + + + + + +N78 + + +runtime +markroot +0 of 0.20s (1.89%) + + + + + +N17->N78 + + + + + + + 0.19s + + + + + +N18->N12 + + + + + + + 0.06s + + + + + +N18->N13 + + + + + + + 0.11s + + + + + +N45 + + +reflect +flag +kind +0.10s (0.95%) + + + + + +N18->N45 + + + + + + + 0.05s + (inline) + + + + + +N19->N10 + + + + + + + 1.62s + + + + + +N19->N27 + + + + + + + 0.02s + + + + + +N19->N45 + + + + + + + 0.03s + (inline) + + + + + +N61 + + +struc +parseFields +0.03s (0.28%) +of 0.42s (3.97%) + + + + + +N21->N61 + + + + + + + 0.42s + + + + + +N65 + + +runtime +convTslice +0.03s (0.28%) +of 0.24s (2.27%) + + + + + +N21->N65 + + + + + + + 0.24s + + + + + +N22->N16 + + + + + + + 1.59s + + + + + +N23->N7 + + + + + + + 0.02s + + + + + +N23->N15 + + + + + + + 1.13s + + + + + +N25 + + +runtime +newobject +0 of 0.46s (4.35%) + + + + + +N23->N25 + + + + + + + 0.20s + + + + + +N26 + + +struc +(*Field) +calculateStructSize +0.12s (1.13%) +of 0.54s (5.10%) + + + + + +N24->N26 + + + + + + + 0.54s + + + + + +N73 + + +struc +(*Field) +calculateBasicSize +0.06s (0.57%) +of 0.07s (0.66%) + + + + + +N24->N73 + + + + + + + 0.07s + + + + + +N25->N3 + + + + + + + 0.46s + + + + + +N26->N12 + + + + + + + 0.25s + + + + + +N26->N14 + + + + + + + 0.17s + + + + + +N33 + + +runtime +findObject +0.16s (1.51%) +of 0.20s (1.89%) + + + + + +N28->N33 + + + + + + + 0.12s + + + + + +N57 + + +runtime +typePointers +next +0.10s (0.95%) +of 0.11s (1.04%) + + + + + +N28->N57 + + + + + + + 0.11s + + + + + +N29->N7 + + + + + + + 1.45s + + + + + +N29->N38 + + + + + + + 0.02s + + + + + +N30 + + +runtime +gcBgMarkWorker +0.01s (0.095%) +of 0.79s (7.47%) + + + + + +N30->N8 + + + + + + + 0.76s + + + + + +N32 + + +runtime +stdcall2 +0.30s (2.84%) + + + + + +N34->N2 + + + + + + + 1.54s + (inline) + + + + + +N34->N25 + + + + + + + 0.10s + + + + + +N35 + + +runtime +mapaccess2 +0.23s (2.17%) +of 0.39s (3.69%) + + + + + +N36 + + +runtime +preemptM +0.01s (0.095%) +of 0.36s (3.40%) + + + + + +N36->N32 + + + + + + + 0.22s + + + + + +N39 + + +runtime +lock +0 of 0.21s (1.98%) + + + + + +N36->N39 + + + + + + + 0.06s + (inline) + + + + + +N46 + + +runtime +stdcall1 +0.11s (1.04%) + + + + + +N36->N46 + + + + + + + 0.05s + + + + + +N55 + + +struc +(*Field) +writeInteger +0.09s (0.85%) +of 0.13s (1.23%) + + + + + +N37->N55 + + + + + + + 0.13s + + + + + +N44 + + +runtime +lock2 +0.06s (0.57%) +of 0.21s (1.98%) + + + + + +N39->N44 + + + + + + + 0.21s + + + + + +N40 + + +runtime +(*sweepLocked) +sweep +0.03s (0.28%) +of 0.22s (2.08%) + + + + + +N40->N8 + + + + + + + 0.11s + + + + + +N68 + + +runtime +(*spanSet) +push +0.03s (0.28%) +of 0.06s (0.57%) + + + + + +N40->N68 + + + + + + + 0.03s + + + + + +N41->N35 + + + + + + + 0.39s + + + + + +N43->N2 + + + + + + + 1.46s + (inline) + + + + + +N43->N25 + + + + + + + 0.07s + + + + + +N44->N32 + + + + + + + 0.08s + + + + + +N72 + + +runtime +procyield +0.07s (0.66%) + + + + + +N44->N72 + + + + + + + 0.07s + + + + + +N47->N2 + + + + + + + 1.52s + (inline) + + + + + +N47->N25 + + + + + + + 0.06s + + + + + +N48 + + +runtime +bgsweep +0 of 0.27s (2.55%) + + + + + +N48->N40 + + + + + + + 0.21s + + + + + +N49->N36 + + + + + + + 0.36s + + + + + +N50 + + +runtime +scanblock +0.13s (1.23%) +of 0.18s (1.70%) + + + + + +N50->N33 + + + + + + + 0.05s + + + + + +N51 + + +runtime +goschedImpl +0 of 0.11s (1.04%) + + + + + +N51->N39 + + + + + + + 0.03s + (inline) + + + + + +N51->N46 + + + + + + + 0.05s + + + + + +N69 + + +runtime +schedule +0 of 0.19s (1.80%) + + + + + +N51->N69 + + + + + + + 0.02s + + + + + +N52 + + +runtime +findRunnable +0.01s (0.095%) +of 0.18s (1.70%) + + + + + +N52->N39 + + + + + + + 0.06s + (inline) + + + + + +N53 + + +runtime +(*mspan) +writeHeapBitsSmall +0.10s (0.95%) +of 0.13s (1.23%) + + + + + +N54 + + +runtime +(*mspan) +base +0.09s (0.85%) + + + + + +N59->N2 + + + + + + + 1.04s + (inline) + + + + + +N59->N25 + + + + + + + 0.03s + + + + + +N60 + + +runtime +mcall +0 of 0.19s (1.80%) + + + + + +N60->N69 + + + + + + + 0.17s + + + + + +N62 + + +struc +fieldCacheLookup +0.03s (0.28%) +of 0.39s (3.69%) + + + + + +N61->N62 + + + + + + + 0.39s + (inline) + + + + + +N62->N41 + + + + + + + 0.36s + + + + + +N65->N3 + + + + + + + 0.21s + + + + + +N66->N54 + + + + + + + 0.08s + (inline) + + + + + +N67->N8 + + + + + + + 0.07s + + + + + +N67->N68 + + + + + + + 0.03s + + + + + +N69->N52 + + + + + + + 0.18s + + + + + +N70->N53 + + + + + + + 0.13s + + + + + +N74->N8 + + + + + + + 0.02s + + + + + +N77->N13 + + + + + + + 0.04s + + + + + +N78->N50 + + + + + + + 0.18s + + + + + +N79->N39 + + + + + + + 0.05s + + + + + +N80->N17 + + + + + + + 0.97s + + + + + diff --git a/struc.go b/struc.go index d83d9f6..2c38036 100644 --- a/struc.go +++ b/struc.go @@ -1,122 +1,150 @@ +// Package struc 实现了 Go 结构体的二进制打包和解包功能。 +// 它提供了高效的序列化和反序列化方法,支持自定义类型、字节对齐和不同的字节序。 +// +// Features: +// - 支持基本类型和复杂结构体的序列化 +// - 支持自定义类型的序列化接口 +// - 支持字节对齐和字节序控制 +// - 提供高性能的内存管理 +// - 支持指针和嵌套结构 package struc import ( - "encoding/binary" "fmt" "io" "reflect" ) -type Options struct { - ByteAlign int - PtrSize int - Order binary.ByteOrder -} +// packingSlicePool 是用于打包和解包的切片池 +// 用于存储和重用字节切片, 提高性能 +var packingSlicePool = NewBytesSlicePool(0) -func (o *Options) Validate() error { - if o.PtrSize == 0 { - o.PtrSize = 32 - } else { - switch o.PtrSize { - case 8, 16, 32, 64: - default: - return fmt.Errorf("Invalid Options.PtrSize: %d. Must be in (8, 16, 32, 64)", o.PtrSize) - } - } - return nil +// Pack 使用默认选项将数据打包到写入器中 +// 这是一个便捷方法,内部调用 PackWithOptions +func Pack(writer io.Writer, data interface{}) error { + return PackWithOptions(writer, data, nil) } -var emptyOptions = &Options{} - -func init() { - // fill default values to avoid data race to be reported by race detector. - emptyOptions.Validate() -} - -func prep(data interface{}) (reflect.Value, Packer, error) { - value := reflect.ValueOf(data) - for value.Kind() == reflect.Ptr { - next := value.Elem().Kind() - if next == reflect.Struct || next == reflect.Ptr { - value = value.Elem() - } else { - break - } - } - switch value.Kind() { - case reflect.Struct: - fields, err := parseFields(value) - return value, fields, err - default: - if !value.IsValid() { - return reflect.Value{}, nil, fmt.Errorf("Invalid reflect.Value for %+v", data) - } - if c, ok := data.(Custom); ok { - return value, customFallback{c}, nil - } - return value, binaryFallback(value), nil - } -} - -func Pack(w io.Writer, data interface{}) error { - return PackWithOptions(w, data, nil) -} - -func PackWithOptions(w io.Writer, data interface{}, options *Options) error { +// PackWithOptions 使用指定的选项将数据打包到写入器中 +// 支持自定义选项,如字节对齐和字节序 +func PackWithOptions(writer io.Writer, data interface{}, options *Options) error { if options == nil { - options = emptyOptions + options = defaultPackingOptions } if err := options.Validate(); err != nil { - return err + return fmt.Errorf("invalid options: %w", err) } - val, packer, err := prep(data) + + value, packer, err := prepareValueForPacking(data) if err != nil { - return err + return fmt.Errorf("preparation failed: %w", err) + } + + if value.Type().Kind() == reflect.String { + value = value.Convert(reflect.TypeOf([]byte{})) } - if val.Type().Kind() == reflect.String { - val = val.Convert(reflect.TypeOf([]byte{})) + + bufferSize := packer.Sizeof(value, options) + buffer := packingSlicePool.GetSlice(bufferSize) + + if _, err := packer.Pack(buffer, value, options); err != nil { + return fmt.Errorf("packing failed: %w", err) } - size := packer.Sizeof(val, options) - buf := make([]byte, size) - if _, err := packer.Pack(buf, val, options); err != nil { - return err + + if _, err = writer.Write(buffer); err != nil { + return fmt.Errorf("writing failed: %w", err) } - _, err = w.Write(buf) - return err + + return nil } -func Unpack(r io.Reader, data interface{}) error { - return UnpackWithOptions(r, data, nil) +// Unpack 使用默认选项从读取器中解包数据 +// 这是一个便捷方法,内部调用 UnpackWithOptions +func Unpack(reader io.Reader, data interface{}) error { + return UnpackWithOptions(reader, data, nil) } -func UnpackWithOptions(r io.Reader, data interface{}, options *Options) error { +// UnpackWithOptions 使用指定的选项从读取器中解包数据 +// 支持自定义选项,如字节对齐和字节序 +func UnpackWithOptions(reader io.Reader, data interface{}, options *Options) error { if options == nil { - options = emptyOptions + options = defaultPackingOptions } if err := options.Validate(); err != nil { - return err + return fmt.Errorf("invalid options: %w", err) } - val, packer, err := prep(data) + + value, packer, err := prepareValueForPacking(data) if err != nil { - return err + return fmt.Errorf("preparation failed: %w", err) } - return packer.Unpack(r, val, options) + + return packer.Unpack(reader, value, options) } +// Sizeof 使用默认选项返回打包数据的大小 +// 这是一个便捷方法,内部调用 SizeofWithOptions func Sizeof(data interface{}) (int, error) { return SizeofWithOptions(data, nil) } +// SizeofWithOptions 使用指定的选项返回打包数据的大小 +// 支持自定义选项,如字节对齐和字节序 func SizeofWithOptions(data interface{}, options *Options) (int, error) { if options == nil { - options = emptyOptions + options = defaultPackingOptions } if err := options.Validate(); err != nil { - return 0, err + return 0, fmt.Errorf("invalid options: %w", err) } - val, packer, err := prep(data) + + value, packer, err := prepareValueForPacking(data) if err != nil { - return 0, err + return 0, fmt.Errorf("preparation failed: %w", err) + } + + return packer.Sizeof(value, options), nil +} + +// prepareValueForPacking 准备一个值用于打包或解包 +// 处理指针解引用、类型检查和打包器选择 +func prepareValueForPacking(data interface{}) (reflect.Value, Packer, error) { + if data == nil { + return reflect.Value{}, nil, fmt.Errorf("cannot pack/unpack nil data") } - return packer.Sizeof(val, options), nil + + value := reflect.ValueOf(data) + + // 解引用指针直到获取非指针类型 + for value.Kind() == reflect.Ptr { + next := value.Elem().Kind() + if next == reflect.Struct || next == reflect.Ptr { + value = value.Elem() + } else { + break + } + } + + var packer Packer + var err error + + switch value.Kind() { + case reflect.Struct: + if fields, err := parseFields(value); err != nil { + return reflect.Value{}, nil, fmt.Errorf("failed to parse fields: %w", err) + } else { + packer = fields + } + default: + if !value.IsValid() { + return reflect.Value{}, nil, fmt.Errorf("invalid reflect.Value for %+v", data) + } + if customPacker, ok := data.(CustomBinaryer); ok { + packer = customBinaryerFallback{customPacker} + } else { + packer = binaryFallback(value) + } + } + + return value, packer, err } diff --git a/struc_test.go b/struc_test.go index f590bb2..f90307c 100644 --- a/struc_test.go +++ b/struc_test.go @@ -74,7 +74,7 @@ type Example struct { CustomTypeSizeArr []byte // "ABCD" } -var five = 5 +var testFive = 5 type ExampleStructWithin struct { a uint8 @@ -90,7 +90,7 @@ type ExampleArray struct { Props [16]ExampleStructWithin `struc:"[16]ExampleStructWithin"` } -var arraySliceReferenceBytes = []byte{ +var testArraySliceBytes = []byte{ 16, 0, 0, 0, 1, 0, 0, 0, 1, @@ -111,51 +111,51 @@ var arraySliceReferenceBytes = []byte{ 0, 0, 0, 16, } -var arrayReference = &ExampleArray{ +var testArrayExample = &ExampleArray{ 16, [16]ExampleStructWithin{ - ExampleStructWithin{1}, - ExampleStructWithin{2}, - ExampleStructWithin{3}, - ExampleStructWithin{4}, - ExampleStructWithin{5}, - ExampleStructWithin{6}, - ExampleStructWithin{7}, - ExampleStructWithin{8}, - ExampleStructWithin{9}, - ExampleStructWithin{10}, - ExampleStructWithin{11}, - ExampleStructWithin{12}, - ExampleStructWithin{13}, - ExampleStructWithin{14}, - ExampleStructWithin{15}, - ExampleStructWithin{16}, + {1}, + {2}, + {3}, + {4}, + {5}, + {6}, + {7}, + {8}, + {9}, + {10}, + {11}, + {12}, + {13}, + {14}, + {15}, + {16}, }, } -var sliceReference = &ExampleSlice{ +var testSliceExample = &ExampleSlice{ 16, []ExampleStructWithin{ - ExampleStructWithin{1}, - ExampleStructWithin{2}, - ExampleStructWithin{3}, - ExampleStructWithin{4}, - ExampleStructWithin{5}, - ExampleStructWithin{6}, - ExampleStructWithin{7}, - ExampleStructWithin{8}, - ExampleStructWithin{9}, - ExampleStructWithin{10}, - ExampleStructWithin{11}, - ExampleStructWithin{12}, - ExampleStructWithin{13}, - ExampleStructWithin{14}, - ExampleStructWithin{15}, - ExampleStructWithin{16}, + {1}, + {2}, + {3}, + {4}, + {5}, + {6}, + {7}, + {8}, + {9}, + {10}, + {11}, + {12}, + {13}, + {14}, + {15}, + {16}, }, } -var reference = &Example{ +var testExample = &Example{ nil, 1, 2, 3, 4, 5, 6, 7, 8, 0, []byte{'a', 'b', 'c', 'd'}, 9, 10, 11, 12, 13, 14, 15, 16, true, false, [4]byte{'e', 'f', 'g', 'h'}, @@ -168,13 +168,13 @@ var reference = &Example{ 4, []byte("5678"), 7, "ijklmno", "pqrstuv", 4, []byte("5678"), - Nested{1}, &Nested{2}, &five, + Nested{1}, &Nested{2}, &testFive, 6, []Nested{{3}, {4}, {5}, {6}, {7}, {8}}, 0, Int3(4), []byte("ABCD"), } -var referenceBytes = []byte{ +var testExampleBytes = []byte{ 0, 0, 0, 0, 0, // pad(5) 1, 0, 2, 0, 0, 0, 3, 0, 0, 0, 0, 0, 0, 0, 4, // fake int8-int64(1-4) 5, 6, 0, 7, 0, 0, 0, 8, 0, 0, 0, 0, 0, 0, 0, // fake little-endian uint8-uint64(5-8) @@ -215,49 +215,49 @@ var referenceBytes = []byte{ func TestCodec(t *testing.T) { var buf bytes.Buffer - if err := Pack(&buf, reference); err != nil { + if err := Pack(&buf, testExample); err != nil { t.Fatal(err) } out := &Example{} if err := Unpack(&buf, out); err != nil { t.Fatal(err) } - if !reflect.DeepEqual(reference, out) { - fmt.Printf("got: %#v\nwant: %#v\n", out, reference) + if !reflect.DeepEqual(testExample, out) { + fmt.Printf("got: %#v\nwant: %#v\n", out, testExample) t.Fatal("encode/decode failed") } } func TestEncode(t *testing.T) { var buf bytes.Buffer - if err := Pack(&buf, reference); err != nil { + if err := Pack(&buf, testExample); err != nil { t.Fatal(err) } - if !bytes.Equal(buf.Bytes(), referenceBytes) { - fmt.Printf("got: %#v\nwant: %#v\n", buf.Bytes(), referenceBytes) + if !bytes.Equal(buf.Bytes(), testExampleBytes) { + fmt.Printf("got: %#v\nwant: %#v\n", buf.Bytes(), testExampleBytes) t.Fatal("encode failed") } } func TestDecode(t *testing.T) { - buf := bytes.NewReader(referenceBytes) + buf := bytes.NewReader(testExampleBytes) out := &Example{} if err := Unpack(buf, out); err != nil { t.Fatal(err) } - if !reflect.DeepEqual(reference, out) { - fmt.Printf("got: %#v\nwant: %#v\n", out, reference) + if !reflect.DeepEqual(testExample, out) { + fmt.Printf("got: %#v\nwant: %#v\n", out, testExample) t.Fatal("decode failed") } } func TestSizeof(t *testing.T) { - size, err := Sizeof(reference) + size, err := Sizeof(testExample) if err != nil { t.Fatal(err) } - if size != len(referenceBytes) { - t.Fatalf("sizeof failed; expected %d, got %d", len(referenceBytes), size) + if size != len(testExampleBytes) { + t.Fatalf("sizeof failed; expected %d, got %d", len(testExampleBytes), size) } } @@ -268,11 +268,11 @@ type ExampleEndian struct { func TestEndianSwap(t *testing.T) { var buf bytes.Buffer big := &ExampleEndian{1} - if err := PackWithOrder(&buf, big, binary.BigEndian); err != nil { + if err := PackWithOptions(&buf, big, &Options{Order: binary.BigEndian}); err != nil { t.Fatal(err) } little := &ExampleEndian{} - if err := UnpackWithOrder(&buf, little, binary.LittleEndian); err != nil { + if err := UnpackWithOptions(&buf, little, &Options{Order: binary.LittleEndian}); err != nil { t.Fatal(err) } if little.T != 256 { diff --git a/test_pack_init/pack_init_test.go b/test_pack_init/pack_init_test.go index b280aa3..89e960f 100644 --- a/test_pack_init/pack_init_test.go +++ b/test_pack_init/pack_init_test.go @@ -2,9 +2,10 @@ package test_pack_init import ( "bytes" - "github.com/lunixbochs/struc" "sync" "testing" + + "github.com/shengyanli1982/struc/v2" ) type Example struct { diff --git a/type_adapter.go b/type_adapter.go new file mode 100644 index 0000000..3fc7d0c --- /dev/null +++ b/type_adapter.go @@ -0,0 +1,89 @@ +package struc + +import ( + "reflect" +) + +// TypeAdapter 提供类型系统的适配层 +// 将现有的硬编码类型系统迁移到新的注册器系统 +type TypeAdapter struct { + registry *TypeRegistry +} + +// NewTypeAdapter 创建一个新的类型适配器 +func NewTypeAdapter() *TypeAdapter { + return &TypeAdapter{ + registry: GlobalTypeRegistry, + } +} + +// String 返回类型的字符串表示 +// 使用注册器查找类型名称 +func (a *TypeAdapter) String(t Type) string { + if name, exists := a.registry.LookupNameByType(t); exists { + return name + } + return "unknown" +} + +// ParseType 解析类型字符串 +// 使用注册器查找类型枚举 +func (a *TypeAdapter) ParseType(typeStr string) (Type, bool) { + return a.registry.LookupTypeByName(typeStr) +} + +// KindToType 将 reflect.Kind 转换为 Type +// 使用注册器查找类型映射 +func (a *TypeAdapter) KindToType(kind reflect.Kind) (Type, bool) { + return a.registry.LookupTypeByKind(kind) +} + +// RegisterType 注册新类型(便捷方法) +func (a *TypeAdapter) RegisterType(name string, typ Type) error { + return a.registry.RegisterType(name, typ) +} + +// RegisterKindMapping 注册类型映射(便捷方法) +func (a *TypeAdapter) RegisterKindMapping(kind reflect.Kind, typ Type) error { + return a.registry.RegisterKindMapping(kind, typ) +} + +// RegisterCustomType 注册自定义类型(便捷方法) +func (a *TypeAdapter) RegisterCustomType(typ reflect.Type, info CustomTypeInfo) error { + return a.registry.RegisterCustomType(typ, info) +} + +// GetRegisteredTypes 获取已注册的类型列表 +func (a *TypeAdapter) GetRegisteredTypes() []string { + return a.registry.GetRegisteredTypes() +} + +// GetRegisteredKinds 获取已注册的类型映射 +func (a *TypeAdapter) GetRegisteredKinds() map[reflect.Kind]Type { + return a.registry.GetRegisteredKinds() +} + +// GetRegisteredCustomTypes 获取已注册的自定义类型 +func (a *TypeAdapter) GetRegisteredCustomTypes() map[reflect.Type]CustomTypeInfo { + return a.registry.GetRegisteredCustomTypes() +} + +// ==================== 向后兼容的全局函数 ==================== + +// GlobalTypeAdapter 全局类型适配器实例 +var GlobalTypeAdapter = NewTypeAdapter() + +// TypeToString 全局类型到字符串转换函数 +func TypeToString(t Type) string { + return GlobalTypeAdapter.String(t) +} + +// ParseTypeString 全局类型字符串解析函数 +func ParseTypeString(typeStr string) (Type, bool) { + return GlobalTypeAdapter.ParseType(typeStr) +} + +// KindToTypeString 全局类型映射函数 +func KindToTypeString(kind reflect.Kind) (Type, bool) { + return GlobalTypeAdapter.KindToType(kind) +} \ No newline at end of file diff --git a/type_registry.go b/type_registry.go new file mode 100644 index 0000000..90188ef --- /dev/null +++ b/type_registry.go @@ -0,0 +1,234 @@ +package struc + +import ( + "fmt" + "reflect" + "sync" +) + +// TypeRegistry 是类型系统注册器 +// 提供动态注册和查找类型的功能,支持自定义类型扩展 +type TypeRegistry struct { + mu sync.RWMutex + + // 类型名称到 Type 枚举的映射 + nameToType map[string]Type + + // Type 枚举到类型名称的映射 + typeToName map[Type]string + + // reflect.Kind 到 Type 枚举的映射 + kindToType map[reflect.Kind]Type + + // 自定义类型注册表 + customTypes map[reflect.Type]CustomTypeInfo +} + +// CustomTypeInfo 包含自定义类型的注册信息 +type CustomTypeInfo struct { + Name string + SizeFunc func(options *Options) int + PackFunc func(value reflect.Value, buffer []byte, options *Options) (int, error) + UnpackFunc func(buffer []byte, value reflect.Value, options *Options) error +} + +// GlobalTypeRegistry 全局类型注册器实例 +var GlobalTypeRegistry = NewTypeRegistry() + +// NewTypeRegistry 创建一个新的类型注册器 +func NewTypeRegistry() *TypeRegistry { + registry := &TypeRegistry{ + nameToType: make(map[string]Type), + typeToName: make(map[Type]string), + kindToType: make(map[reflect.Kind]Type), + customTypes: make(map[reflect.Type]CustomTypeInfo), + } + + // 初始化内置类型 + registry.initBuiltinTypes() + + return registry +} + +// initBuiltinTypes 初始化内置类型 +func (r *TypeRegistry) initBuiltinTypes() { + // 注册内置类型名称映射 + builtinTypes := map[string]Type{ + "pad": Pad, + "bool": Bool, + "byte": Uint8, + "int8": Int8, + "uint8": Uint8, + "int16": Int16, + "uint16": Uint16, + "int32": Int32, + "uint32": Uint32, + "int64": Int64, + "uint64": Uint64, + "float32": Float32, + "float64": Float64, + "size_t": SizeType, + "off_t": OffType, + } + + for name, typ := range builtinTypes { + r.nameToType[name] = typ + r.typeToName[typ] = name + } + + // 注册内置类型字符串映射 + additionalTypeNames := map[Type]string{ + Invalid: "invalid", + String: "string", + Struct: "struct", + Ptr: "ptr", + CustomType: "custom", + } + + for typ, name := range additionalTypeNames { + r.typeToName[typ] = name + } + + // 注册 reflect.Kind 到 Type 的映射 + builtinKindMappings := map[reflect.Kind]Type{ + reflect.Bool: Bool, + reflect.Int8: Int8, + reflect.Int16: Int16, + reflect.Int: Int32, + reflect.Int32: Int32, + reflect.Int64: Int64, + reflect.Uint8: Uint8, + reflect.Uint16: Uint16, + reflect.Uint: Uint32, + reflect.Uint32: Uint32, + reflect.Uint64: Uint64, + reflect.Float32: Float32, + reflect.Float64: Float64, + reflect.String: String, + reflect.Struct: Struct, + reflect.Ptr: Ptr, + } + + for kind, typ := range builtinKindMappings { + r.kindToType[kind] = typ + } +} + +// RegisterType 注册一个新的类型 +func (r *TypeRegistry) RegisterType(name string, typ Type) error { + r.mu.Lock() + defer r.mu.Unlock() + + if existing, exists := r.nameToType[name]; exists { + return fmt.Errorf("type name '%s' already registered to type %v", name, existing) + } + + if existing, exists := r.typeToName[typ]; exists { + return fmt.Errorf("type %v already registered with name '%s'", typ, existing) + } + + r.nameToType[name] = typ + r.typeToName[typ] = name + + return nil +} + +// RegisterKindMapping 注册 reflect.Kind 到 Type 的映射 +func (r *TypeRegistry) RegisterKindMapping(kind reflect.Kind, typ Type) error { + r.mu.Lock() + defer r.mu.Unlock() + + if existing, exists := r.kindToType[kind]; exists { + return fmt.Errorf("kind %v already mapped to type %v", kind, existing) + } + + r.kindToType[kind] = typ + return nil +} + +// RegisterCustomType 注册自定义类型 +func (r *TypeRegistry) RegisterCustomType(typ reflect.Type, info CustomTypeInfo) error { + r.mu.Lock() + defer r.mu.Unlock() + + if _, exists := r.customTypes[typ]; exists { + return fmt.Errorf("custom type %v already registered", typ) + } + + r.customTypes[typ] = info + return nil +} + +// LookupTypeByName 根据类型名称查找 Type +func (r *TypeRegistry) LookupTypeByName(name string) (Type, bool) { + r.mu.RLock() + defer r.mu.RUnlock() + + typ, exists := r.nameToType[name] + return typ, exists +} + +// LookupNameByType 根据 Type 查找类型名称 +func (r *TypeRegistry) LookupNameByType(typ Type) (string, bool) { + r.mu.RLock() + defer r.mu.RUnlock() + + name, exists := r.typeToName[typ] + return name, exists +} + +// LookupTypeByKind 根据 reflect.Kind 查找 Type +func (r *TypeRegistry) LookupTypeByKind(kind reflect.Kind) (Type, bool) { + r.mu.RLock() + defer r.mu.RUnlock() + + typ, exists := r.kindToType[kind] + return typ, exists +} + +// LookupCustomType 查找自定义类型信息 +func (r *TypeRegistry) LookupCustomType(typ reflect.Type) (CustomTypeInfo, bool) { + r.mu.RLock() + defer r.mu.RUnlock() + + info, exists := r.customTypes[typ] + return info, exists +} + +// GetRegisteredTypes 返回所有已注册的类型名称 +func (r *TypeRegistry) GetRegisteredTypes() []string { + r.mu.RLock() + defer r.mu.RUnlock() + + names := make([]string, 0, len(r.nameToType)) + for name := range r.nameToType { + names = append(names, name) + } + return names +} + +// GetRegisteredKinds 返回所有已注册的 reflect.Kind 映射 +func (r *TypeRegistry) GetRegisteredKinds() map[reflect.Kind]Type { + r.mu.RLock() + defer r.mu.RUnlock() + + // 返回副本以避免并发访问问题 + result := make(map[reflect.Kind]Type) + for kind, typ := range r.kindToType { + result[kind] = typ + } + return result +} + +// GetRegisteredCustomTypes 返回所有已注册的自定义类型 +func (r *TypeRegistry) GetRegisteredCustomTypes() map[reflect.Type]CustomTypeInfo { + r.mu.RLock() + defer r.mu.RUnlock() + + // 返回副本以避免并发访问问题 + result := make(map[reflect.Type]CustomTypeInfo) + for typ, info := range r.customTypes { + result[typ] = info + } + return result +} \ No newline at end of file diff --git a/types.go b/types.go index 6ca97f4..465ce92 100644 --- a/types.go +++ b/types.go @@ -5,32 +5,34 @@ import ( "reflect" ) +// Type 定义了支持的数据类型枚举 type Type int const ( - Invalid Type = iota - Pad - Bool - Int - Int8 - Uint8 - Int16 - Uint16 - Int32 - Uint32 - Int64 - Uint64 - Float32 - Float64 - String - Struct - Ptr - - SizeType - OffType - CustomType + Invalid Type = iota // 无效类型 + Pad // 填充类型 + Bool // 布尔类型 + Int // 整数类型 + Int8 // 8位整数 + Uint8 // 8位无符号整数 + Int16 // 16位整数 + Uint16 // 16位无符号整数 + Int32 // 32位整数 + Uint32 // 32位无符号整数 + Int64 // 64位整数 + Uint64 // 64位无符号整数 + Float32 // 32位浮点数 + Float64 // 64位浮点数 + String // 字符串类型 + Struct // 结构体类型 + Ptr // 指针类型 + SizeType // size_t 类型 + OffType // off_t 类型 + CustomType // 自定义类型 ) +// Resolve 根据选项解析实际类型 +// 主要用于处理 SizeType 和 OffType 这样的平台相关类型 func (t Type) Resolve(options *Options) Type { switch t { case OffType: @@ -63,10 +65,12 @@ func (t Type) Resolve(options *Options) Type { return t } +// String 返回类型的字符串表示 func (t Type) String() string { - return typeNames[t] + return typeToString[t] } +// Size 返回类型的字节大小 func (t Type) Size() int { switch t { case SizeType, OffType: @@ -79,12 +83,28 @@ func (t Type) Size() int { return 4 case Int64, Uint64, Float64: return 8 + case Struct: + return 0 // 结构体大小需要通过字段计算 default: panic("Cannot resolve size of type:" + t.String()) } } -var typeLookup = map[string]Type{ +// IsBasicType 判断是否为基本类型 +// 基本类型包括:整数、浮点数、布尔值 +func (t Type) IsBasicType() bool { + switch t { + case Int8, Int16, Int32, Int64, + Uint8, Uint16, Uint32, Uint64, + Float32, Float64, Bool: + return true + default: + return false + } +} + +// typeStrToType 定义了字符串到类型的映射关系 +var typeStrToType = map[string]Type{ "pad": Pad, "bool": Bool, "byte": Uint8, @@ -103,20 +123,45 @@ var typeLookup = map[string]Type{ "off_t": OffType, } -var typeNames = map[Type]string{ - CustomType: "Custom", +// typeToString 定义了类型到字符串的映射关系 +var typeToString = map[Type]string{ + Invalid: "invalid", + Pad: "pad", + Bool: "bool", + Int8: "int8", + Int16: "int16", + Int32: "int32", + Int64: "int64", + Uint8: "uint8", + Uint16: "uint16", + Uint32: "uint32", + Uint64: "uint64", + Float32: "float32", + Float64: "float64", + String: "string", + Struct: "struct", + Ptr: "ptr", + SizeType: "size_t", + OffType: "off_t", + CustomType: "custom", } +// init 初始化类型到字符串的映射 func init() { - for name, enum := range typeLookup { - typeNames[enum] = name + for name, enum := range typeStrToType { + typeToString[enum] = name } } +// Size_t 是平台相关的无符号整数类型,用于表示大小 type Size_t uint64 + +// Off_t 是平台相关的有符号整数类型,用于表示偏移量 type Off_t int64 -var reflectTypeMap = map[reflect.Kind]Type{ +// typeKindToType 定义了 reflect.Kind 到 Type 的映射关系 +// 用于将 Go 的反射类型转换为 struc 包的类型系统 +var typeKindToType = map[reflect.Kind]Type{ reflect.Bool: Bool, reflect.Int8: Int8, reflect.Int16: Int16, diff --git a/unsafe.go b/unsafe.go new file mode 100644 index 0000000..fb69e5c --- /dev/null +++ b/unsafe.go @@ -0,0 +1,158 @@ +package struc + +import ( + "encoding/binary" + "math" + "reflect" + "unsafe" +) + +//go:linkname memclrNoHeapPointers runtime.memclrNoHeapPointers +func memclrNoHeapPointers(ptr unsafe.Pointer, n uintptr) + +// typedmemmove 是一个底层的内存移动函数 +// +//go:linkname typedmemmove runtime.typedmemmove +func typedmemmove(dst unsafe.Pointer, src unsafe.Pointer, size uintptr) + +// memclr 使用 runtime 的内存清零函数, 比循环清零更高效 +func memclr(b []byte) { + if len(b) == 0 { + return + } + memclrNoHeapPointers(unsafe.Pointer(&b[0]), uintptr(len(b))) +} + +// unsafeSliceHeader 是切片的内部表示 +type unsafeSliceHeader struct { + Data uintptr + Len int + Cap int +} + +// unsafeBytes2String 使用 unsafe 将字节切片转换为字符串, 避免内存拷贝 +func unsafeBytes2String(b []byte) string { + return *(*string)(unsafe.Pointer(&b)) +} + +// unsafeString2Bytes 使用 unsafe 将字符串转换为字节切片, 避免内存拷贝 +func unsafeString2Bytes(s string) []byte { + x := (*[2]uintptr)(unsafe.Pointer(&s)) + h := [3]uintptr{x[0], x[1], x[1]} + return *(*[]byte)(unsafe.Pointer(&h)) +} + +// unsafeSetSlice 使用 unsafe 直接设置切片的底层数据, 避免内存拷贝 +func unsafeSetSlice(fieldValue reflect.Value, buffer []byte, length int) { + sh := (*unsafeSliceHeader)(unsafe.Pointer(fieldValue.UnsafeAddr())) + sh.Data = uintptr(unsafe.Pointer(&buffer[0])) + sh.Len = length + sh.Cap = length +} + +// unsafeSetString 使用 unsafe 将字节切片转换为字符串并设置到字段, 避免内存拷贝 +func unsafeSetString(fieldValue reflect.Value, buffer []byte, length int) { + str := unsafeBytes2String(buffer[:length]) + fieldValue.SetString(str) +} + +// unsafeGetUint64 使用 unsafe 直接读取 uint64 值, 避免内存拷贝 +func unsafeGetUint64(buffer []byte, byteOrder binary.ByteOrder) uint64 { + if byteOrder == binary.LittleEndian { + return *(*uint64)(unsafe.Pointer(&buffer[0])) + } + // 大端序使用 binary 包,可能被编译器优化 + return binary.BigEndian.Uint64(buffer) +} + +// unsafeGetUint32 使用 unsafe 直接读取 uint32 值, 避免内存拷贝 +func unsafeGetUint32(buffer []byte, byteOrder binary.ByteOrder) uint32 { + if byteOrder == binary.LittleEndian { + return *(*uint32)(unsafe.Pointer(&buffer[0])) + } + // 大端序使用 binary 包,可能被编译器优化 + return binary.BigEndian.Uint32(buffer) +} + +// unsafeGetUint16 使用 unsafe 直接读取 uint16 值, 避免内存拷贝 +func unsafeGetUint16(buffer []byte, byteOrder binary.ByteOrder) uint16 { + if byteOrder == binary.LittleEndian { + return *(*uint16)(unsafe.Pointer(&buffer[0])) + } + // 大端序使用 binary 包,可能被编译器优化 + return binary.BigEndian.Uint16(buffer) +} + +// unsafePutUint64 使用 unsafe 直接写入 uint64 值, 避免内存拷贝 +func unsafePutUint64(buffer []byte, value uint64, byteOrder binary.ByteOrder) { + if byteOrder == binary.LittleEndian { + *(*uint64)(unsafe.Pointer(&buffer[0])) = value + return + } + // 大端序使用 binary 包,可能被编译器优化 + binary.BigEndian.PutUint64(buffer, value) +} + +// unsafePutUint32 使用 unsafe 直接写入 uint32 值, 避免内存拷贝 +func unsafePutUint32(buffer []byte, value uint32, byteOrder binary.ByteOrder) { + if byteOrder == binary.LittleEndian { + *(*uint32)(unsafe.Pointer(&buffer[0])) = value + return + } + // 大端序使用 binary 包,可能被编译器优化 + binary.BigEndian.PutUint32(buffer, value) +} + +// unsafePutUint16 使用 unsafe 直接写入 uint16 值, 避免内存拷贝 +func unsafePutUint16(buffer []byte, value uint16, byteOrder binary.ByteOrder) { + if byteOrder == binary.LittleEndian { + *(*uint16)(unsafe.Pointer(&buffer[0])) = value + return + } + // 大端序使用 binary 包,可能被编译器优化 + binary.BigEndian.PutUint16(buffer, value) +} + +// unsafeGetFloat64 使用 unsafe 直接读取 float64 值 +// 通过转换为 uint64 位模式实现 +func unsafeGetFloat64(buffer []byte, byteOrder binary.ByteOrder) float64 { + bits := unsafeGetUint64(buffer, byteOrder) + return math.Float64frombits(bits) +} + +// unsafeGetFloat32 使用 unsafe 直接读取 float32 值 +// 通过转换为 uint32 位模式实现 +func unsafeGetFloat32(buffer []byte, byteOrder binary.ByteOrder) float32 { + bits := unsafeGetUint32(buffer, byteOrder) + return math.Float32frombits(bits) +} + +// unsafePutFloat64 使用 unsafe 直接写入 float64 值 +// 通过转换为 uint64 位模式实现 +func unsafePutFloat64(buffer []byte, value float64, byteOrder binary.ByteOrder) { + bits := math.Float64bits(value) + unsafePutUint64(buffer, bits, byteOrder) +} + +// unsafePutFloat32 使用 unsafe 直接写入 float32 值 +// 通过转换为 uint32 位模式实现 +func unsafePutFloat32(buffer []byte, value float32, byteOrder binary.ByteOrder) { + bits := math.Float32bits(value) + unsafePutUint32(buffer, bits, byteOrder) +} + +// unsafeMoveSlice 使用 typedmemmove 移动切片数据 +// 直接操作切片的底层数据,避免内存拷贝 +func unsafeMoveSlice(dst, src reflect.Value) { + dstPtr := unsafe.Pointer(dst.Pointer()) + srcPtr := unsafe.Pointer(src.Pointer()) + + // The length of data to copy is the length of the source slice in bytes. + // Since src is always a []byte slice from reflect.ValueOf(buffer), + // src.Len() is the number of bytes. + dataLen := uintptr(src.Len()) + if dataLen == 0 { + return + } + typedmemmove(dstPtr, srcPtr, dataLen) +}