请问什么时候用指针什么时候用实体,例如多层级的配置信息,配置结构体载入做成接收者可以吗?
这是一个非常经典的问题。在 Go 中,配置信息(Config)通常建议使用指针或在初始化后作为全局只读变量。
1. 结构体字段建议:使用实体还是指针?
结论:结构体内部字段建议用实体,但对外传递和全局变量建议用指针。
- 配置项(字段): 像
RedisConfig这种小型结构体,直接用实体(Value)更好。它在内存中是连续的,对垃圾回收(GC)更友好。 - 总配置(App): 建议将
var App Config改为var App *Config。
2. 核心优化方案:修改 Load 模式
你目前的 Load 方法是在接收器上调用并返回新对象,这在逻辑上有点绕。更高效、更惯用的做法是直接初始化并返回指针。
修改 pkg\config\config.go
package config
// 将全局变量设为指针
var App *Config
type Config struct {
Server ServerConfig
DB map[string]DbConfig
Redis RedisConfig
Logger LoggerConfig
}
// 改为函数,直接返回指针
func NewConfig() *Config {
// 直接初始化,避免多次拷贝
return &Config{
Server: LoadServerConfig(), // 假设你改成了普通函数
DB: map[string]DbConfig{
"biz": LoadDbConfig("biz"),
"report": LoadDbConfig("report"),
},
Redis: LoadRedisConfig(),
Logger: LoadLoggerConfig(),
}
}
修改 pkg\config\redis.go
// 建议改为普通函数,返回实体值即可
func LoadRedisConfig() RedisConfig {
return RedisConfig{
Host: getEnv("REDIS_HOST", "localhost"),
Port: GetEnvInt("REDIS_PORT", 6379),
Password: getEnv("REDIS_PASSWORD", "123qwe"),
DB: GetEnvInt("REDIS_DB", 1),
}
}
3. 为什么这样更高效?
- 减少拷贝: 你原来的
config.App = config.App.LoadAll()会在栈上创建临时对象,然后再赋值给全局变量,产生两次内存拷贝。使用指针(*Config)后,全局变量只存储一个 8 字节的地址。 - 避免副作用: 你的
Load方法使用了指针接收器(r *RedisConfig)但内部没用到r,这容易产生误导。改为普通的New或Load函数更清晰。 - 并发安全: 配置通常在
main启动时一次性加载。一旦加载完成,全局指针指向的内存就不再变动,多协程并发读取App.Redis.Host是非常高效且安全的。
4. 改进后的 main 函数
func main() {
_ = godotenv.Load()
// 初始化全局指针
config.App = config.NewConfig()
// 使用时很方便:config.App.Redis.Host
}