递归函数需显式处理空节点以避免panic,统一用if node.children != nil判断;建议指针传参收集结果并预分配容量;json反序列化后须重建指针关系;深度过大时应加depth guard或改用显式栈。

递归函数必须显式处理空节点,否则 panic
Go 没有内置的树结构,你得自己定义节点类型。常见错误是 Node.Children 声明为 []*Node,但初始化时没做 nil 判断,一碰到空 slice 或 nil 切片就直接遍历,导致 nil pointer dereference。实际解析时,父节点可能有 Children 字段但值为 nil,也可能是个空 slice —— 这俩在 Go 里语义不同,但都该被安全跳过。
实操建议:
- 统一用
if node.Children != nil判断,不要只判len(node.Children) > 0,因为 nil slice 的 len 是 0,但遍历时会 panic - 构造测试数据时,主动包含
Children: nil和Children: []*Node{}两种情况 - 递归入口函数里先检查入参是否为
nil,避免第一层就崩
用指针传参 + 闭包收集结果,避免反复分配切片
如果每次递归都返回新 slice(比如 func(node *Node) []string),底层会不断 append、扩容、复制,深度大时 GC 压力明显。更稳的做法是把结果容器(如 []string)作为参数传入,用指针或闭包捕获,让所有递归层级往同一个底层数组追加。
实操示例:
func traverse(node *Node, result *[]string) {
if node == nil {
return
}
*result = append(*result, node.Name)
for _, child := range node.Children {
traverse(child, result)
}
}
// 调用:
var names []string
traverse(root, &names)
注意:*result 是指针,append 会修改原 slice 的底层数组;若担心并发或复用问题,可在调用前预分配容量:names := make([]string, 0, estimateMaxDepth)。
JSON 反序列化后需手动重建指针关系,不能直接递归
从 HTTP 或文件读到的 JSON 多是扁平结构(如带 ParentID 的数组),json.Unmarshal 后得到的是独立对象切片,Children 字段仍是 nil。这时候直接拿 raw slice 里某个元素去递归,根本走不出子树 —— 因为父子链接没建立。
Go语言(Golang)1.26.0版本提供 Go 官方 Windows amd64 MSI 安装包下载入口,版本号 1.26.0,可用于旧项目维护、兼容性测试和指定版本开发环境配置。
必须先建 map 做 ID 索引,再遍历一次补指针:
- 遍历原始 slice,用
map[int]*Node缓存所有节点 - 再遍历一次,对每个节点的
ParentID查 map,找到父节点后把当前节点 append 到其Children中 - 最后找出
ParentID == 0或nil的节点作为 root,再开始递归
漏掉这步,node.Children 永远是 nil,递归只会跑一层。
深度过大时栈溢出风险比 Python 高,要留退路
Go 默认 goroutine 栈初始只有 2KB,虽然会自动扩容,但极端嵌套(比如上万级深的树)仍可能触发 runtime: goroutine stack exceeds 1000000000-byte limit。Python 有递归限制可设,Go 没等价机制。
能做的只有两件事:
- 业务侧加深度 guard:递归函数里传一个
depth int参数,每次 +1,超过阈值(如 1000)就 return 并记录 warning - 真遇到超深结构,改用显式栈(
stack []*Node)模拟递归,while 循环 + pop/push,完全规避栈限制
别指望 runtime 调优解决,Go 的栈管理不对外暴露接口,深度逻辑必须自己兜底。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!










