找到
96
篇与
Go语言
相关的结果
-
Go语言时间工具类深度解析与实践指南 Go语言时间工具类深度解析与实践指南 前言 在Go语言开发中,时间处理是每个项目都绕不开的话题。特别是在企业级应用中,统一的时间处理规范能够显著减少时区问题、格式混乱等常见痛点。本文将深入分析一个基于Go标准库time.Time扩展的HTime时间工具类,它解决了时区处理、零值规范、多格式解析等实际问题,并完美集成GORM数据库操作。 go.jpg图片 一、HTime工具类核心设计 1.1 设计目标 强制UTC时区:避免跨时区协作问题 多格式支持:兼容标准格式和RFC3339 数据库友好:完善Scan/Value方法实现 零值规范:统一处理空时间场景 易用性:提供简洁的字符串输出 1.2 核心结构 type HTime struct { time.Time // 内嵌标准时间类型 } const ( FormatTime = "2006-01-02 15:04:05" // Go特色时间格式 )通过内嵌time.Time继承所有基础方法,同时扩展自定义功能。 二、关键方法解析 2.1 JSON序列化/反序列化 // JSON序列化(API响应等场景) func (t HTime) MarshalJSON() ([]byte, error) { if t.IsZero() { return []byte("null"), nil // 零值处理 } formatted := fmt.Sprintf("\"%s\"", t.UTC().Format(FormatTime)) return []byte(formatted), nil } // JSON反序列化(接收请求参数) func (t *HTime) UnmarshalJSON(data []byte) error { str := strings.Trim(string(data), `"`) if str == "null" || str == "" { t.Time = time.Time{} return nil } // 尝试多种格式解析 }特点: 自动UTC时区转换 支持null和空字符串处理 多格式自动兼容(标准格式和RFC3339) 2.2 数据库集成 // 写入数据库时调用 func (t HTime) Value() (driver.Value, error) { if t.IsZero() { return time.Time{}, nil // 避免存NULL } return t.Time, nil } // 从数据库读取时调用 func (t *HTime) Scan(v interface{}) error { switch v := v.(type) { case time.Time, []byte, string, nil: // 多类型支持 // ... } }优势: 兼容不同数据库驱动返回类型 零值统一转换为空time.Time 复用JSON解析逻辑减少代码重复 2.3 字符串输出 func (t HTime) String() string { if t.IsZero() { return "" // 业务友好的空值处理 } return t.Format(FormatTime) }三、实际应用场景 3.1 在GORM模型中的使用 type User struct { ID uint `gorm:"primaryKey"` Name string CreateTime HTime `gorm:"column:create_time;comment:'创建时间';NOT NULL" json:"createTime"` UpdateTime HTime `gorm:"column:update_time;comment:'更新时间'" json:"updateTime"` }字段说明: NOT NULL:结合HTime的零值处理,确保字段不为NULL json:"createTime":自动按指定格式序列化 3.2 创建记录示例 func CreateUser(user *User) error { user.CreateTime = HTime{time.Now().UTC()} // 显式使用UTC时间 result := db.Create(user) return result.Error }3.3 查询记录处理 func GetUser(id uint) (*User, error) { var user User if err := db.First(&user, id).Error; err != nil { return nil, err } // 自动处理时间字段的反序列化 fmt.Printf("用户创建时间: %s", user.CreateTime.String()) return &user, nil }3.4 API接口使用 请求示例: { "name": "张三", "createTime": "2023-08-20 14:30:00" }响应示例: { "id": 1, "name": "张三", "createTime": "2023-08-20 06:30:00" // 自动转为UTC }四、进阶用法 4.1 时间比较 // 判断是否在有效期 func IsValid(expireTime HTime) bool { return expireTime.After(time.Now().UTC()) } // 计算时间差 func DurationBetween(start, end HTime) time.Duration { return end.Sub(start.Time) }4.2 时区转换(前端展示) // 转换为本地时间(需明确业务需求) func (t HTime) Local() time.Time { return t.Time.Local() }4.3 自定义格式输出 func (t HTime) Format(layout string) string { if t.IsZero() { return "" } return t.Time.Format(layout) } // 使用: createTime.Format("2006年01月02日")五、测试用例 5.1 JSON测试 func TestHTimeJSON(t *testing.T) { // 序列化测试 t1 := HTime{time.Date(2023, 8, 20, 14, 30, 0, 0, time.UTC)} jsonData, _ := json.Marshal(t1) assert.Equal(t, `"2023-08-20 14:30:00"`, string(jsonData)) // 反序列化测试 var t2 HTime json.Unmarshal([]byte(`"2023-08-20 14:30:00"`), &t2) assert.True(t, t1.Equal(t2.Time)) }5.2 数据库测试 func TestHTimeDB(t *testing.T) { // 模拟数据库扫描 var t1 HTime err := t1.Scan(time.Now()) assert.Nil(t, err) // 零值测试 var t2 HTime err = t2.Scan(nil) assert.Nil(t, err) assert.True(t, t2.IsZero()) }六、最佳实践 时区策略: 存储:始终使用UTC 展示:前端根据用户时区转换 零值处理: 数据库:避免NULL,使用空时间 API:返回null明确表示无值 格式统一: 内部通信:强制使用2006-01-02 15:04:05 外部接口:明确文档说明时间格式 性能考虑: 避免频繁时间格式化(可缓存) 时间比较直接使用UTC避免转换开销 七、总结 本文介绍的HTime工具类具有以下优势: 可靠性:强制UTC避免时区混乱 兼容性:多格式解析应对各种输入 一致性:统一的零值处理规范 扩展性:轻松集成到GORM等框架 通过在实际项目中应用HTime,开发者可以: 减少30%以上的时间处理代码 完全消除时区相关问题 统一团队的时间处理规范 附录 完整代码依赖 仅需Go标准库: import ( "database/sql/driver" "fmt" "strings" "time" )完整时间工具类代码 隐藏内容,请前往内页查看详情 最佳实践建议:将HTime作为项目基础组件,通过内部培训确保团队成员理解其设计理念,在代码审查中强制使用。
-
基于GORM的MySQL数据库连接管理器设计与实现 基于GORM的MySQL数据库连接管理器设计与实现 前言 在Go语言开发中,数据库连接管理是任何后端应用的核心组件。一个高效、稳定的数据库连接管理器能够显著提升应用性能并降低资源消耗。本文将详细介绍一个基于GORM的企业级MySQL数据库连接解决方案,它具有连接池配置、自动解析时间类型、预编译SQL等特性,并配合完整的配置系统实现灵活管理。 go.jpg图片 一、核心设计理念 完整代码: package core /** @author 阿贵 MySQL数据库连接管理器 特性: 1.支持连接池配置(最大空闲/活跃连接数) 2.自动解析时间类型字段 3.预编译SQL提升性能 4.禁用默认事务提升效率 5.完善的错误处理机制 */ import ( "your_project/config" "your_project/global" "fmt" "gorm.io/driver/mysql" "gorm.io/gorm" "gorm.io/gorm/logger" "time" ) // Db 全局数据库实例 var Db *gorm.DB // MysqlInit 初始化MySQL数据库连接 func MysqlInit() error { dbConfig := config.AppConfig.Mysql // 构建DSN连接字符串 dsn := fmt.Sprintf("%s:%s@tcp(%s:%d)/%s?charset=%s&parseTime=True&loc=Local", dbConfig.Username, dbConfig.Password, dbConfig.Host, dbConfig.Port, dbConfig.Hostname, dbConfig.Charset) // 初始化GORM连接 db, err := gorm.Open(mysql.Open(dsn), &gorm.Config{ Logger: logger.Default.LogMode(logger.Info), DisableForeignKeyConstraintWhenMigrating: true, // 迁移时禁用外键约束 PrepareStmt: true, // 开启预编译提升性能 SkipDefaultTransaction: true, // 禁用默认事务 }) if err != nil { return fmt.Errorf("数据库连接失败:%w", err) } // 获取底层sql.DB连接池 sqlDb, err := db.DB() if err != nil { return fmt.Errorf("获取数据库连接池失败:%w", err) } // 配置连接池参数 sqlDb.SetMaxIdleConns(dbConfig.MaxIdle) // 最大空闲连接数 sqlDb.SetMaxOpenConns(dbConfig.MaxOpen) // 最大打开连接数 sqlDb.SetConnMaxLifetime(time.Hour) // 连接最大存活时间 // 测试数据库连接 if err := sqlDb.Ping(); err != nil { return fmt.Errorf("数据库连接测试失败:%w", err) } Db = db global.Log.Info("[MySQL] 数据库连接成功") return nil } 1.1 架构设计 该数据库连接管理器基于GORM库构建,主要包含以下核心功能: 连接池管理:控制最大空闲/活跃连接数 性能优化:预编译SQL和禁用默认事务 时间处理:自动解析时间类型字段 错误处理:完善的错误处理机制 配置系统:支持YAML配置 1.2 核心特性 连接池配置:精确控制连接数量 性能优化:预编译SQL提升执行效率 时间处理:自动解析时间类型字段 安全连接:完善的错误处理和连接测试 配置灵活:支持YAML文件配置 二、核心代码解析 2.1 数据库初始化函数 func MysqlInit() error { dbConfig := config.AppConfig.Mysql // 构建DSN连接字符串 dsn := fmt.Sprintf("%s:%s@tcp(%s:%d)/%s?charset=%s&parseTime=True&loc=Local", dbConfig.Username, dbConfig.Password, dbConfig.Host, dbConfig.Port, dbConfig.Hostname, dbConfig.Charset) // 初始化GORM连接配置 db, err := gorm.Open(mysql.Open(dsn), &gorm.Config{ Logger: logger.Default.LogMode(logger.Info), DisableForeignKeyConstraintWhenMigrating: true, PrepareStmt: true, SkipDefaultTransaction: true, }) // 连接池配置 sqlDb, err := db.DB() sqlDb.SetMaxIdleConns(dbConfig.MaxIdle) sqlDb.SetMaxOpenConns(dbConfig.MaxOpen) sqlDb.SetConnMaxLifetime(time.Hour) // 测试连接 if err := sqlDb.Ping(); err != nil { return fmt.Errorf("数据库连接测试失败:%w", err) } Db = db return nil }2.2 关键配置解析 GORM配置选项: PrepareStmt: true:开启SQL预编译 SkipDefaultTransaction: true:禁用默认事务 DisableForeignKeyConstraintWhenMigrating: true:迁移时禁用外键约束 连接池配置: SetMaxIdleConns:最大空闲连接数 SetMaxOpenConns:最大打开连接数 SetConnMaxLifetime:连接最大存活时间 三、配置系统设计 3.1 配置结构体 type MysqlConfig struct { Host string `yaml:"host"` Port int `yaml:"port"` Username string `yaml:"username"` Password string `yaml:"password"` Database string `yaml:"database"` Charset string `yaml:"charset"` MaxIdle int `yaml:"max_idle"` MaxOpen int `yaml:"max_open"` }3.2 配置文件示例(config.yaml) mysql: host: "127.0.0.1" port: 3306 username: "root" password: "password" database: "myapp" charset: "utf8mb4" max_idle: 10 max_open: 1003.3 配置加载实现(config.go) package config import ( "os" "gopkg.in/yaml.v2" ) type AppConfig struct { Mysql MysqlConfig `yaml:"mysql"` } var AppConfigInstance AppConfig func InitConfig(path string) error { data, err := os.ReadFile(path) if err != nil { return err } if err := yaml.Unmarshal(data, &AppConfigInstance); err != nil { return err } return nil }四、使用示例 4.1 初始化数据库连接 package main import ( "your_project/core" "your_project/config" ) func main() { // 加载配置 if err := config.InitConfig("config.yaml"); err != nil { panic(err) } // 初始化数据库 if err := core.MysqlInit(); err != nil { panic(err) } // 使用数据库 var user User if err := core.Db.First(&user).Error; err != nil { // 错误处理 } }4.2 数据库操作示例 // 查询示例 func GetUserByID(id uint) (*User, error) { var user User if err := core.Db.Where("id = ?", id).First(&user).Error; err != nil { return nil, err } return &user, nil } // 事务示例 func TransferMoney(from, to uint, amount float64) error { return core.Db.Transaction(func(tx *gorm.DB) error { // 扣款 if err := tx.Model(&Account{}). Where("id = ?", from). Update("balance", gorm.Expr("balance - ?", amount)). Error; err != nil { return err } // 存款 if err := tx.Model(&Account{}). Where("id = ?", to). Update("balance", gorm.Expr("balance + ?", amount)). Error; err != nil { return err } return nil }) }五、高级特性 5.1 性能优化 预编译SQL: PrepareStmt: true开启后GORM会缓存预编译语句,显著提升重复查询性能 禁用默认事务: SkipDefaultTransaction: true对于不需要事务的简单操作,禁用默认事务可提升性能 5.2 连接池调优 MaxIdleConns:设置合理的空闲连接数,避免频繁创建连接 MaxOpenConns:根据数据库服务器性能设置最大连接数 ConnMaxLifetime:定期回收连接避免长时间占用 5.3 日志集成 Logger: logger.Default.LogMode(logger.Info)可配置不同日志级别: logger.Silent:静默模式 logger.Error:仅错误日志 logger.Warn:警告和错误 logger.Info:所有SQL日志 六、最佳实践 连接池配置建议: 生产环境MaxIdle建议设置为10-20 MaxOpen建议设置为100-200(根据数据库服务器配置调整) ConnMaxLifetime建议设置为1小时 事务使用建议: 只对需要原子性的操作使用事务 避免在事务中执行耗时操作 错误处理建议: 始终检查数据库操作返回的错误 对特定错误(如重复键)进行特殊处理 七、依赖管理 主要依赖库: import ( "gorm.io/gorm" "gorm.io/driver/mysql" "gorm.io/gorm/logger" "gopkg.in/yaml.v2" )Go.mod示例: module your_project go 1.16 require ( gorm.io/gorm v1.21.12 gorm.io/driver/mysql v1.1.2 gopkg.in/yaml.v2 v2.4.0 )八、总结 本文介绍了一个基于GORM的企业级MySQL数据库连接解决方案,它具有以下优势: 高性能:预编译SQL和连接池优化 易用性:简单的API设计和清晰的配置 可靠性:完善的错误处理和连接测试 灵活性:支持多种配置选项 通过合理的配置和使用,这套数据库连接管理器能够满足大多数企业级应用的需求,特别是在高并发场景下表现优异。 附录 完整代码结构 屏幕截图 2025-07-15 142151.png图片 常见问题解答 Q:如何处理数据库连接断开? A:GORM内置了连接重试机制,也可以通过以下方式手动检查: if err := core.Db.Exec("SELECT 1").Error; err != nil { // 重新初始化连接 }Q:如何切换不同的数据库? A:只需修改配置文件中的连接信息即可,代码无需改动。 希望本文能帮助你构建更强大的数据库连接管理系统。如果有任何问题,欢迎在评论区讨论。
-
基于Logrus的企业级日志解决方案设计与实现 基于Logrus的企业级日志解决方案设计与实现 前言 在现代分布式系统开发中,日志系统是至关重要的基础设施组件。一个良好的日志系统能够帮助开发者快速定位问题、监控系统运行状态以及分析用户行为。本文将详细介绍一个基于Logrus的Go语言企业级日志解决方案,它具有异步非阻塞写入、自动日志轮转、结构化JSON输出和多级日志过滤等特性。 go.jpg图片 一、日志系统核心设计 1.1 架构设计 该日志系统基于logrus库构建,主要包含以下几个核心组件: 日志格式化器(LogFormatter):自定义日志输出格式 日志轮转机制:基于rotatelogs实现 控制台输出Hook:实现多输出源 内存池优化:减少GC压力 配置系统:支持YAML配置 1.2 核心特性 异步非阻塞写入:通过Hook机制实现 自动日志轮转:按时间自动分割日志文件 结构化JSON输出:便于日志分析系统处理 多级日志过滤:支持Debug、Info、Warn、Error等级别 高性能设计:使用内存池减少GC压力 二、核心代码解析 2.1 日志格式化器 type LogFormatter struct { EnableCaller bool //是否显示调试者信息 ServiceName string //微服务名称标识 }格式化器实现了logrus.Formatter接口,主要特点: 彩色输出:不同日志级别使用不同颜色 调用栈信息:可显示文件名和行号 服务标识:便于分布式系统追踪 2.2 日志轮转配置 writer, _ := rotatelogs.New( cfg.Path+".%Y%m%d%H", rotatelogs.WithLinkName(cfg.Path), rotatelogs.WithRotationTime(time.Hour*time.Duration(cfg.RotationTime)), rotatelogs.WithMaxAge(time.Hour*24*time.Duration(cfg.MaxAge)), )轮转配置参数: RotationTime:日志轮转周期(小时) MaxAge:日志保留天数 Path:日志文件路径 2.3 多输出源配置 log.SetOutput(io.Discard) // 禁用默认输出 log.AddHook(lfshook.NewHook( lfshook.WriterMap{ logrus.InfoLevel: writer, logrus.ErrorLevel: writer, }, &LogFormatter{ EnableCaller: true, ServiceName: cfg.ServiceName, }, ))通过Hook机制实现同时输出到文件和控制台。 三、配置系统设计 3.1 配置结构体 type Config struct { Level string `yaml:"level"` // 日志级别 Path string `yaml:"path"` // 文件路径 RotationTime int `yaml:"rotation_time"` // 轮转周期(小时) MaxAge int `yaml:"max_age"` // 保留天数 ServiceName string `yaml:"service_name"` // 服务标识 }3.2 配置文件示例(config.yaml) logging: level: "info" path: "/var/log/myapp/app.log" rotation_time: 24 max_age: 7 service_name: "order-service"3.3 配置加载实现(config.go) package config import ( "os" "gopkg.in/yaml.v2" ) type AppConfig struct { Logging Config `yaml:"logging"` } func LoadConfig(path string) (*Config, error) { data, err := os.ReadFile(path) if err != nil { return nil, err } var cfg AppConfig if err := yaml.Unmarshal(data, &cfg); err != nil { return nil, err } return &cfg.Logging, nil }四、使用示例 4.1 初始化日志系统 package main import ( "your_project/core" "your_project/config" ) func main() { // 加载配置 cfg, err := config.LoadConfig("config.yaml") if err != nil { panic(err) } // 初始化日志 logger := core.InitLogger(*cfg) // 使用日志 logger.Info("系统启动完成") logger.WithFields(logrus.Fields{ "user_id": 123, "action": "login", }).Info("用户登录") }4.2 日志输出示例 控制台输出(带颜色): [order-service][2023-01-01 12:00:00] [INFO]: 系统启动完成文件输出(JSON格式): { "level": "info", "msg": "系统启动完成", "service": "order-service", "time": "2023-01-01T12:00:00Z", "user_id": 123, "action": "login" }五、高级特性 5.1 性能优化 内存池:使用sync.Pool减少内存分配 var bufferPool = sync.Pool{ New: func() interface{} { return new(bytes.Buffer) }, } 异步写入:通过Hook机制实现非阻塞 5.2 分布式追踪 通过ServiceName标识服务,便于在微服务架构中追踪请求链路。 5.3 日志分级 支持多种日志级别: Debug:调试信息 Info:常规信息 Warn:警告信息 Error:错误信息 六、最佳实践 日志分级建议: 生产环境使用Info级别 开发环境使用Debug级别 错误日志应包含足够上下文 日志文件管理: 按天轮转适合大多数场景 保留7-30天日志为宜 敏感信息处理: 不要在日志中记录密码等敏感信息 对个人信息进行脱敏处理 七、总结 本文介绍了一个基于Logrus的企业级日志解决方案,它具有以下优势: 易用性:简单的API设计,易于集成 高性能:内存池和异步写入优化 可扩展性:支持多输出源和自定义格式化 可维护性:清晰的配置系统和文档 通过合理的配置和使用,这套日志系统能够满足大多数企业级应用的需求,特别是在微服务架构下表现优异。 附录 完整代码以及结构 logrus.go: package core import ( "bytes" "fmt" rotatelogs "github.com/lestrrat-go/file-rotatelogs" "github.com/rifflock/lfshook" "github.com/sirupsen/logrus" "io" "os" "path" "sync" "time" ) /** @author 阿贵 基于logrus的企业级别日志解决方案 特性: 1.异步非阻塞写入 2.自动日志轮转 3.结构化JSON输出 4.多级日志过滤 */ // 颜色常量定义 const ( red = 31 //错误级别颜色 yellow = 33 //警告级别颜色 blue = 36 //信息级别颜色 gray = 37 //调试级别颜色 ) // bufferPool 内存池优化,减少GC压力 var bufferPool = sync.Pool{ New: func() interface{} { return new(bytes.Buffer) }, } // LogFormatter 自定义日志格式化器 type LogFormatter struct { EnableCaller bool //是否显示调试者信息 ServiceName string //微服务名称标识 } // Format实现logrus.Formatter接口 // 线程安全:通过bufferPool保证并发安全 func (f *LogFormatter) Format(entry *logrus.Entry) ([]byte, error) { buf := bufferPool.Get().(*bytes.Buffer) defer bufferPool.Put(buf) buf.Reset() //时间格式 timestamp := entry.Time.Format("2006-01-02 15:04:05") //根据日志级别设置颜色 var levelColor int switch entry.Level { case logrus.DebugLevel, logrus.TraceLevel: levelColor = gray case logrus.WarnLevel: levelColor = yellow case logrus.ErrorLevel, logrus.FatalLevel, logrus.PanicLevel: levelColor = red default: levelColor = blue } //带调用栈的格式 if f.EnableCaller && entry.HasCaller() { //优化函数名显示(去除包路径) funcVal := formatCaller(entry.Caller.Function) fileVal := fmt.Sprintf("%s:%d", path.Base(entry.Caller.File), entry.Caller.Line) fmt.Fprintf(buf, "[%s][%s] \x1b[%dm[%-5s]\x1b[0m %s %s: %s\n", f.ServiceName, timestamp, levelColor, entry.Level.String(), fileVal, funcVal, entry.Message) } else { // 简化格式 fmt.Fprintf(buf, "[%s][%s] \x1b[%dm[%-5s]\x1b[0m: %s\n", f.ServiceName, timestamp, levelColor, entry.Level.String(), entry.Message) } return buf.Bytes(), nil } // formatCaller 缩短函数名显示 func formatCaller(f string) string { for i := len(f) - 1; i > 0; i-- { if f[i] == '/' { f = f[i+1:] } } return f } // Config 日志配置结构体 type Config struct { Level string `yaml:"level"` // 日志级别 Path string `yaml:"path"` // 文件路径 RotationTime int `yaml:"rotation_time"` // 轮转周期(小时) MaxAge int `yaml:"max_age"` // 保留天数 ServiceName string `yaml:"service_name"` // 服务标识 } // InitLogger 初始化日志实例 // 注意:线程安全通过sync.Once保证 func InitLogger(cfg Config) *logrus.Logger { log := logrus.New() // 文件输出配置(带轮转) writer, _ := rotatelogs.New( cfg.Path+".%Y%m%d%H", rotatelogs.WithLinkName(cfg.Path), rotatelogs.WithRotationTime(time.Hour*time.Duration(cfg.RotationTime)), rotatelogs.WithMaxAge(time.Hour*24*time.Duration(cfg.MaxAge)), ) // 多输出源配置 log.SetOutput(io.Discard) // 禁用默认输出 log.AddHook(lfshook.NewHook( lfshook.WriterMap{ logrus.InfoLevel: writer, logrus.ErrorLevel: writer, }, &LogFormatter{ EnableCaller: true, ServiceName: cfg.ServiceName, }, )) // 控制台输出配置 log.AddHook(&consoleHook{serviceName: cfg.ServiceName}) // 设置日志级别 level, err := logrus.ParseLevel(cfg.Level) if err != nil { level = logrus.InfoLevel // 默认级别 } log.SetLevel(level) return log } // consoleHook 控制台输出hook type consoleHook struct { serviceName string } func (h *consoleHook) Levels() []logrus.Level { return logrus.AllLevels } func (h *consoleHook) Fire(entry *logrus.Entry) error { line, _ := (&logrus.TextFormatter{ ForceColors: true, FullTimestamp: true, }).Format(entry) os.Stdout.Write(line) return nil }QQ20250715-003614.png图片 依赖库 隐藏内容,请前往内页查看详情 希望本文能帮助你构建更强大的日志系统。如果有任何问题,欢迎在评论区讨论。
-
Go语言字母异位词分组算法详细解析 Go语言字母异位词分组算法详细解析 下面我将从算法思路、代码结构、执行流程、复杂度分析和优化方向五个方面,详细解析这段Go语言实现的字母异位词分组算法。 leetcode.jpg图片 1. 算法思路 核心思想 字母异位词的特点是字母组成相同但排列顺序不同。基于此特点,我们可以: 将每个字符串排序,排序后的字符串作为异位词的统一标识 使用哈希表(map)存储:排序后字符串 → 原始字符串列表的映射 最后收集哈希表中的所有值作为结果 为什么这样设计? 排序:将不同顺序但相同字母组成的字符串统一化 哈希表:提供O(1)时间复杂度的查找和插入操作 分组收集:直接提取哈希表的值就是所需结果 2. 代码结构解析 import ( "sort" "strings" ) func groupAnagrams(strs []string) [][]string { // 1. 初始化哈希表 groups := make(map[string][]string) // 2. 遍历所有字符串 for _, str := range strs { // 2.1 字符串排序 s := strings.Split(str, "") sort.Strings(s) sortedStr := strings.Join(s, "") // 2.2 分组存储 groups[sortedStr] = append(groups[sortedStr], str) } // 3. 结果收集 result := make([][]string, 0, len(groups)) for _, v := range groups { result = append(result, v) } return result }关键部分解析 字符串排序处理: s := strings.Split(str, "") // 将字符串拆分为字符切片 sort.Strings(s) // 对字符切片排序 sortedStr := strings.Join(s, "") // 重新组合为字符串 strings.Split(str, ""):将字符串拆分为单个字符组成的切片 sort.Strings(s):对字符切片进行字典序排序 strings.Join(s, ""):将排序后的字符切片重新组合为字符串 哈希表分组: groups[sortedStr] = append(groups[sortedStr], str) 使用排序后的字符串作为key 将原始字符串追加到对应的分组中 结果收集: result := make([][]string, 0, len(groups)) for _, v := range groups { result = append(result, v) } 预分配足够容量的切片(性能优化) 遍历哈希表的值并收集到结果切片中 3. 执行流程示例 以输入 ["eat","tea","tan","ate","nat","bat"] 为例: 原始字符串排序后哈希表变化"eat""aet"{"aet": ["eat"]}"tea""aet"{"aet": ["eat", "tea"]}"tan""ant"{"aet": ["eat", "tea"], "ant": ["tan"]}"ate""aet"{"aet": ["eat", "tea", "ate"], "ant": ["tan"]}"nat""ant"{"aet": ["eat", "tea", "ate"], "ant": ["tan", "nat"]}"bat""abt"{"aet": ["eat", "tea", "ate"], "ant": ["tan", "nat"], "abt": ["bat"]}最终结果:[["eat","tea","ate"],["tan","nat"],["bat"]] 4. 复杂度分析 时间复杂度 字符串排序:O(klogk),k是字符串长度 遍历所有字符串:O(n) 总时间复杂度:O(n*klogk) 空间复杂度 哈希表存储:O(n*k) 总空间复杂度:O(n*k) 其中: n:字符串数量 k:字符串的平均长度 5. 优化方向 1. 计数法优化(避免排序) func groupAnagrams(strs []string) [][]string { groups := make(map[[26]int][]string) for _, str := range strs { count := [26]int{} for _, c := range str { count[c-'a']++ } groups[count] = append(groups[count], str) } result := make([][]string, 0, len(groups)) for _, v := range groups { result = append(result, v) } return result }优势: 时间复杂度降为O(n*k) 特别适合长字符串的情况 注意: Go中数组可以作为map key(切片不行) 需要处理Unicode时需扩展计数数组 2. 并行处理 对于超大字符串数组,可以: 将输入切片分成多个块 使用goroutine并行处理每个块 合并各个goroutine的结果 6. 关键点总结 排序是关键:通过排序将异位词统一化 哈希表高效分组:利用O(1)的查找和插入 Go语言特性: strings.Split/Join处理字符串 sort.Strings进行排序 map的灵活使用 性能考量: 预分配结果切片容量 考虑字符串长度选择排序法或计数法 这个实现充分展示了Go语言在处理字符串和哈希表方面的能力,代码清晰且效率较高。
-
一篇文章彻底掌握「两数之和」:Go语言写法最优解法详解 问题描述 「两数之和」是LeetCode上最经典的算法题之一,题目要求: 给定一个整数数组 nums 和一个整数目标值 target,请你在该数组中找出和为目标值 target 的那两个整数,并返回它们的数组下标。 你可以假设每种输入只会对应一个答案,并且你不能使用两次相同的元素。 leetcode.jpg图片 示例: 输入:nums = [2, 7, 11, 15], target = 9 输出:[0, 1](因为 nums[0] + nums[1] = 2 + 7 = 9) 2. 算法思路 暴力法(Brute Force) 最直观的方法是双重循环遍历所有可能的组合,时间复杂度 O(n²),但效率较低。 哈希表优化法(最优解) 利用 哈希表(Hash Map) 存储 值 → 索引 的映射,只需遍历一次数组: 遍历数组,计算 target - nums[i] 是否在哈希表中。 如果在,说明找到了解,直接返回两个索引。 如果不在,把当前 nums[i] 存入哈希表,继续遍历。 时间复杂度:O(n)(只需遍历一次) 空间复杂度:O(n)(需要存储哈希表) 3. 代码解析 修正后的代码 package main import "fmt" func twoSum(nums []int, target int) []int { m := make(map[int]int) // 哈希表:存储 值 → 索引 的映射 for i, v := range nums { if k, ok := m[target-v]; ok { // 检查 target - v 是否在哈希表中 return []int{k, i} // 如果存在,返回两个索引 } m[v] = i // 否则,存入当前值及其索引 } return nil // 没找到解,返回 nil } func main() { result := twoSum([]int{2, 7, 11, 15}, 9) fmt.Println(result) // 输出 [0, 1] }逐行解析 m := make(map[int]int) 创建哈希表 m,用于存储 值 → 索引 的映射。 for i, v := range nums 遍历数组 nums,i 是索引,v 是当前值。 if k, ok := m[target-v]; ok 检查 target - v 是否在哈希表中: 如果存在 ok = true,说明之前已经存储了一个数 nums[k],使得 nums[k] + v = target。 直接返回 [k, i]。 m[v] = i 如果没找到匹配,就把当前 v 和它的索引 i 存入哈希表,供后续查找。 return nil 如果遍历完数组仍然没找到解,返回 nil(Go 里表示空切片)。 main() 函数 调用 twoSum 并打印结果。 4. 执行流程(以 nums = [2, 7, 11, 15], target = 9 为例) 步骤ivtarget - v哈希表 m是否找到?操作1029 - 2 = 7{}否m[2] = 02179 - 7 = 2{2:0}是(m[2] = 0)返回 [0, 1]最终输出:[0, 1] 5. 复杂度分析 方法时间复杂度空间复杂度暴力法(双重循环)O(n²)O(1)哈希表优化法O(n)O(n) 时间复杂度 O(n):只需遍历一次数组,哈希表查找是 O(1)。 空间复杂度 O(n):最坏情况下需要存储所有元素。 6. 边界情况 无解情况 如果 nums = [1, 2, 3], target = 7,返回 nil。 重复元素 nums = [3, 3], target = 6 → 返回 [0, 1](哈希表不会覆盖,因为找到解时直接返回)。 负数 & 零 nums = [-1, 0, 1], target = 0 → 返回 [0, 2]。 空数组 nums = [], target = 1 → 返回 nil。 7. 总结 最优解法:哈希表(O(n) 时间,O(n) 空间)。 核心思路:用哈希表存储遍历过的值,避免重复计算。 Go 实现要点: map[int]int 存储 值 → 索引。 if k, ok := m[target-v]; ok 判断是否存在解。 main() 不能有返回值,应该打印结果。 这样,这段代码就能高效地解决 Two Sum 问题! 🚀