what
|
6c6159f342
|
fix: Scope() 返回快实现, 不再把后续操作丢回逐次反射
Scope() 原来是 tv.slow().Scope(), 返回的是 *refx —— 基于 reflect 的慢实现。
功能完全正常, 只是它之后的每一次 Get/Set 都退回逐次反射, 这次重写的收益
在 Scope 之后全部消失。
Scope("A") 之后再 Get 144.6ns / 7 allocs -> 48.7ns / 2 allocs 2.97x
对照: 不经 Scope 直接 Get 59.0ns / 2 allocs
改为自己走 walk 定位、克隆、再包成 *rfx。克隆复用 cloneForValueInput,
与 New(值) 同一套逻辑: 含引用成分才递归深拷贝, 纯值类型逐字节复制。
这类问题很容易在重构里悄悄失守 —— 交回慢实现不会有任何功能异常, 只是慢 3 倍,
测试不看动态类型就发现不了。所以 TestScopeReturnsFastImpl 直接断言返回的是
*rfx, 并验证过有效: 改回 &refx{} 后该用例失败。
顺带确认了其余 slow() 调用点都不会把慢实现泄漏给调用方:
Append/Delete 返回 v 自身, Keys/StringMap*/Slice* 返回的是具体 Go 类型。
另加 Scope 的语义测试(含引用子树 / 纯值子树 / map 子树 / 无参数整体克隆,
都断言原数据未被改写)与 78 个对拍用例。
|
2026-08-31 14:29:06 +08:00 |
|
what
|
ca2a8f911a
|
perf: map 分段走 reflect, 离开后切回快路径
walk 逐跳调用 step, 而 step 的 map 分支每次都要 boxCopy —— map 元素不可寻址,
rfx 的表示又需要一个地址, 只能 reflect.New 复制一份。层级越深累积越多,
导致纯 map 路径比改造前的旧实现还慢, 是整个重写里唯一的性能回归。
改法要两头兼顾:
- 一路 reflect 走到底能省掉逐跳装箱, 但 map 后面的 struct 跳会丢掉偏移量
快路径、退回 FieldByName —— 实测 map→struct→struct 反而比逐跳装箱慢 5.8%
- 所以只在**连续的 map 段**内用 reflect, 一离开就装箱一次切回快路径
map→map→struct→struct 303.2ns/16a -> 205.6ns/10a 1.47x
map→struct→struct→map 309.0ns/16a -> 246.3ns/11a 1.25x
map→struct→struct 224.6ns/11a -> 140.3ns/ 6a 1.60x
纯 map 三层 300.9ns/20a -> 260.8ns/15a 1.15x (原 0.72x)
纯 map 单跳 120.0ns/ 8a -> 126.8ns/ 7a 0.95x
struct 三层(对照) 200.4ns/ 9a -> 59.6ns/ 2a 3.36x
单跳 map 仍慢 5%: 只有一跳时没有 boxCopy 可省, 而 valueAt 那次 reflect.NewAt
省不掉。其余形态全部转正。
两个实现上的坑, 都是测试抓出来的:
1. walkMapRun 发现已离开 map 段时, 那一跳已经从迭代器取走但还没用, 必须交还
给调用方。最初用 *it = save 回退, 结果**零分配快路径全线退化成 1 次分配** ——
通过指针写回会让逃逸分析认为 it 指向的内容可能逃逸, 进而判定 Get 的可变参数
切片逃逸, 波及所有路径, 连纯 struct 的路径都跟着中枪。改成把待处理的段
作为返回值交出去就好了。
2. 若干处清理: normalize 从 step 提到 walk(step 只有 walk 一个调用方);
移除 step 里已不可达的 Map 分支。
测试 252 个对拍用例覆盖 map 之后的各种后继类型与失败形态, 另加 19 个交界用例
专压"离开 map 段"那一跳。已验证有效: 把交还 pending 的那行改成丢弃后,
53 个用例失败。
|
2026-08-31 14:15:39 +08:00 |
|
what
|
ba0fd4b3bc
|
perf: Keys() 原生实现 map 分支, 字符串键不再绕 fmt.Sprint
Keys() 的 struct 分支早就是原生的了, map 分支一直委托给 refx, 而那边用的是
fmt.Sprint(k.Interface()) —— 要先把 key 装箱成 interface 再走一遍格式化。
对字符串键完全没必要。
Keys() 3 个字符串键的 map 193.3ns / 9 allocs -> 119.7ns / 5 allocs 1.61x
非字符串键仍旧走 fmt.Sprint: k.String() 对非字符串 Value 返回的是
"<int Value>" 这种占位串而不是键本身, 无脑替换会静默给出错误的键名。
按 m.Type().Key().Kind() 分支处理。
顺带把 normalize() 提到开头, 这样经指针或 interface 拿到的 map/struct 也能
走原生分支, 而不是掉进 slow()。
测试期间发现并修掉一处自己引入的差异: nil map 上旧实现返回 []string{},
我最初加的 IsNil 提前返回给出的是 nil。两者在 reflect.DeepEqual 和 JSON
序列化(`[]` vs `null`)上不同。MapKeys() 对 nil map 本来就返回空切片而不是
panic, 那个提前返回是多余的。
测试覆盖 11 种输入与旧实现对拍(string/具名 string/int/int64/float/bool 键、
空 map、nil map、struct、切片、嵌套 map), 外加经路径取到的 map、
interface 包着的 map、指针指向的 map, 以及非字符串键必须是真实键值的断言。
map 迭代顺序随机, 比较前排序。
|
2026-08-31 11:14:24 +08:00 |
|
what
|
5c9f8bd6e7
|
perf: 用类型布局缓存 + 指针偏移寻址重写热路径
将实验目录 experiment/fastx 的实现合入主实现,替换原来逐次反射的做法。
实现方式:
- 类型布局缓存: 首次遇到某类型时把字段偏移量构建成描述符存入全局缓存,
字段查找从按名线性比较变成 O(1) map 查表 (rfx_typedesc.go)
- 指针偏移寻址: 取字段时用 基址+偏移量 直接算地址, 不再构造中间 reflect.Value
(unsafeptr.go, 全部 unsafe 代码集中在这一个文件)
- 零分配路径解析: 路径按需切片遍历, 不再为每次 Get 分配临时切片 (path.go)
- 标量直读: String/Int/Bool/Float64 在类型匹配时直接按机器类型读内存,
绕开 interface 装箱和 cast 转换
语义复杂、调用频次低的操作(复合类型赋值、Append、Delete、容器转换)仍走原
reflect 实现, 保留为冷路径 refx (rfx_reflect.go), 避免重写引入行为偏差。
新增包级泛型函数 Get[T](r, path...), 按路径直接取出目标类型, 零内存分配。
语义与 r.Get(path...).Xxx() 严格等价, 不改动任何现有接口。
性能对比(同进程同数据, -benchmem -count=6 中位数):
- Get 嵌套 + String 150.6ns/7allocs -> 50.7ns/2allocs 2.97x
- Get 4 层深路径 234.1ns/10allocs -> 68.9ns/2allocs 3.40x
- Set 单层 65.8ns/2allocs -> 18.1ns/0allocs 3.64x
- Set 嵌套 122.1ns/3allocs -> 35.5ns/0allocs 3.44x
- Exists 127.4ns/5allocs -> 37.2ns/1alloc 3.42x
- 访问器 String 15.0ns/1alloc -> 1.3ns/0allocs 11.8x
- Get[string] 泛型直取 -> 29.9ns/0allocs 5.04x
两处不快: map 键访问 1.25x(map 无稳定布局, 仍走 reflect);
New 构造 0.67x(多一次描述符缓存查找, 一次性成本)。
内存安全: 未导出字段在读和写两处显式拦截 —— reflect.NewAt 构造的 Value
不带只读标记, 语言层面的导出规则保护在 unsafe 路径上失效, 必须自己拦。
不变式与评审要点见 unsafeptr.go 顶部注释。
测试: 新增 703 个用例全部通过, 含 go test -race。
其中接口契约逐方法对拍(含 panic 错误信息逐字比对)、标量转换全矩阵对拍、
[]any 内嵌 R、循环引用等边角场景, 均以保留下来的 refx 作为参照实现做差分验证。
合并过程中据此发现并修复 11 处行为偏差。
|
2026-08-28 16:37:04 +08:00 |
|
what
|
1f48635da3
|
fix: New()/Array() 正确处理 Nil 及已经是 R 的元素,不再 panic/重复包装
New(nil) 之前会 panic,现在返回 Nil(新增的 R 空实现单例,所有操作都是
安全的空操作/零值,不用调用方每次都判空)。New([]R{...}) 和 Array() 遇到
元素本身已经是 R(包括 Nil)时直接透传,不再重新拿 reflect.Value 包一层,
避免丢失原有的空值语义或类型信息。
|
2026-08-20 17:28:57 +08:00 |
|
what
|
3915bee7b8
|
feat: Slice方法支持JSON字符串自动解析
当Slice()方法遇到字符串类型且内容以'['开头时,会自动尝试JSON解析为切片。
这使得处理JSON格式的数组字符串更加便捷。
|
2026-01-12 15:04:42 +08:00 |
|
what
|
39da1d55dd
|
feat: 添加 Lookuper 接口和 MustLookup 方法
- 新增 Lookuper 接口,提供 Lookup 和 MustLookup 两种路径查找方式
- Accessor 接口继承 Lookuper,保持向后兼容
- MustLookup 在路径不存在时返回 Nil 访问器,简化调用代码
- 更新 fieldx.Schema 使用 Lookuper 接口,支持更灵活的数据源
- 添加 Required 字段选项,控制字段不存在时的行为
|
2026-01-05 16:59:31 +08:00 |
|
what
|
d233fc319b
|
feat: 支持嵌套 R 和 valuex.Accessor 对象的路径访问
- 添加 unwrapAccessor 统一处理 Accessor 解引用
- 添加 deref/derefWithAccessor/derefValue 封装重复的解引用逻辑
- getValueByPath 支持穿透 R/Accessor 对象访问嵌套路径
- setNestedValue 支持在 R/Accessor 对象中设置嵌套值
- CloneValue 和 cloneElement 正确克隆 Accessor 底层值
- 支持在 map/struct/slice 中嵌套 R 对象并通过路径访问
- 添加完整测试覆盖 map/struct/slice/深度嵌套等场景
- 减少 38 行重复代码,提高代码可维护性
|
2025-12-24 16:37:12 +08:00 |
|
what
|
7d17293df3
|
feat: 增强错误处理与示例
|
2025-12-09 20:06:56 +08:00 |
|
what
|
72d670be0b
|
feat: 增强 map 大小写不敏感支持和函数重命名
- 新增 tryMapFieldKey 函数,返回 map 中实际存在的键(支持大小写转换)
- 优化 setFieldValue 方法,使用 tryMapFieldKey 查找已存在字段并更新
- 优化 setNestedValue 方法,修复嵌套 map 大小写处理和 interface 包装问题
- 重命名函数以提高代码清晰度:
- tryStructField → tryStructFieldValue
- tryMapField → tryMapFieldValue
- tryMapFieldWithKey → tryMapFieldKey
- 新增 rfx_map_case_test.go 包含 16 个测试用例,覆盖基本、嵌套、边界等场景
|
2025-12-09 16:55:13 +08:00 |
|
what
|
fbba6f9a30
|
feat: 增强 nil 值处理能力
主要改进:
- 简化 setValue 方法中的 nil 值处理逻辑,统一设置为目标类型的零值
- 修复 setFieldValue 方法中 map 类型设置 nil 值时的问题
- 新增完整的 nil 值设置测试,覆盖所有 Go 基础类型和复合类型
测试覆盖:
- 指针、接口、切片、map、channel、func 等可空类型设置为 nil
- int/uint 系列、float 系列、bool、string 等基础类型设置为零值
- 嵌套结构体和 map 中的 nil 值设置
|
2025-12-09 15:11:18 +08:00 |
|
what
|
6e3593f188
|
feat: 优化 setValue 方法支持 nil 值处理
改进 setValue 方法在处理 nil 值时的行为:
- 对于可空类型(指针、接口、切片、map 等)设置为零值而非失败
- 对于基础类型(int、string、bool 等)继续执行 cast 转换逻辑
- 修复 val 无效时调用 val.Type() 的潜在空指针问题
|
2025-12-09 14:56:37 +08:00 |
|
what
|
bacec92841
|
feat: Append 方法支持 R 接口包装的指针类型
增强 setValue 函数,支持将 R 接口包装的指针值追加到非指针类型的切片中。当源值是指针但目标不是指针时,自动解引用后再赋值,使得 New(&item) 可以追加到 []Item 类型的切片。同时统一使用 Raw() 方法替代 Value() 方法名。
|
2025-12-08 17:24:45 +08:00 |
|
what
|
f5f261e541
|
feat: R 接口支持 JSON 序列化和反序列化
实现功能:
- R 接口继承 json.Marshaler 和 json.Unmarshaler 接口
- MarshalJSON(): 将 R 实例序列化为 JSON 字节数组
- UnmarshalJSON(): 从 JSON 字节数组反序列化到 R 实例
核心特性:
- 支持 struct、map、slice 等所有类型的 JSON 序列化
- 支持嵌套结构的序列化和反序列化
- 自动智能类型转换(如 JSON 数字 float64 -> int/int64)
- 可以对嵌套字段单独序列化(如 rfx.Get("Address"))
测试覆盖:
- TestJSONMarshal: 测试各种类型的序列化
- TestJSONUnmarshal: 测试各种类型的反序列化
- TestJSONRoundTrip: 测试序列化和反序列化的往返一致性
文档更新:
- 在 README 特性列表中添加 JSON 序列化说明
- 新增"JSON 序列化和反序列化"章节
- 包含完整的使用示例和最佳实践
- 说明使用场景:API通信、配置持久化、数据传输、缓存、消息队列等
|
2025-12-08 17:22:05 +08:00 |
|
what
|
a6a936c07c
|
feat: 增强 map 类型转换的容错能力
优化了所有 map 类型转换方法,采用三层转换策略:
1. 优先使用 cast 库进行类型转换
2. cast 失败后,通过 Keys() 和 Get() 进行自定义转换
3. 转换失败时提供完整的错误信息(包含 cast 错误和自定义转换错误)
优化的方法:
- StringMapString()
- StringMapStringSlice()
- StringMapBool()
- StringMapInt()
- StringMapInt64()
- StringMap()
这使得 struct 类型也能成功转换为 map 类型,增强了类型转换的灵活性。
|
2025-12-08 11:16:44 +08:00 |
|
what
|
9e1bc55a8c
|
feat: 增强类型转换能力和代码重构
核心改进:
1. 新增 normalizeInputValue 函数
- 统一处理输入值的规范化,支持 R 接口、[]R 切片、reflect.Value
- 避免重复封装,提升性能和类型安全性
2. 重构 getValueByPath 为独立函数
- 从 rfx 方法提取为独立函数,提高代码复用性
- 更好的职责分离,便于维护和测试
3. 显著增强 setValue 方法的类型转换能力
- 支持切片类型转换:[]any -> []T,自动转换每个元素
- 支持结构体类型转换:map -> struct 或 struct -> struct,按字段名匹配
- 保持指针切片的引用语义,避免不必要的对象复制
4. 新增 tryMapField 函数
- 支持 Map 键名的大小写不敏感访问
- 首字母大写的键会自动尝试小写版本(如 "Host" -> "host")
5. 新增 lowercaseFirst 辅助函数
- 用于首字母小写转换,配合 Map 键名查找
6. 更新测试用例
- 新增指针切片的使用示例
- 展示 []any 包含指针元素的场景
7. 文档全面更新
- 新增"高级类型转换"章节,详细说明切片、结构体、指针切片等转换
- 更新特性列表,突出增强的类型转换能力和 R 接口集成
- 补充 Map 大小写不敏感访问的说明
影响范围:
- reflux.go: 使用 normalizeInputValue 统一输入处理
- rfx.go: 重构并增强 setValue、getValueByPath 等核心方法
- util.go: 新增多个辅助函数,代码行数增加 160 行
- rfx_example_test.go: 新增指针切片测试用例
向后兼容:完全兼容现有 API,仅增强内部实现和类型转换能力
|
2025-12-05 11:12:11 +08:00 |
|
what
|
243d1ffac8
|
feat: 在 rfx.String() 中,先尝试用 cast.ToStringE 将当前值转为字符串,如果失败则增加了一个兜底逻辑:尝试使用 json.Marshal 将当前值序列化为 JSON 字符串并返回
|
2025-12-04 14:09:23 +08:00 |
|
what
|
b373dde7f7
|
feat: 支持切片追加及 -1 头部插入
|
2025-12-04 11:28:18 +08:00 |
|
what
|
cbe079ddcd
|
feat: 支持反射设置指针类型字段
- 如果目标字段是指针类型,它会尝试直接赋值(如果类型兼容),或创建一个新指针并递归设置其指向的值。
- `rfx_example_test.go` 中新增了测试用例,以验证对 `*string` 等指针字段的设置功能。
|
2025-12-04 11:25:44 +08:00 |
|
what
|
d9f178020a
|
first commit
|
2025-12-02 19:52:29 +08:00 |
|