# 打印输出
import "fmt"
// Println 中间有空格 Print 没有
fmt.Println("go", "python", "php", "javascript") // go python php javascript
fmt.Print("go", "python", "php", "javascript") // gopythonphpjavascript
// Println 会自动换行,Print 不会
func main() {
fmt.Println("hello")
fmt.Println("world")
// hello
// world
fmt.Print("hello")
fmt.Print("world")
// helloworld
}
// Printf 是格式化输出
a := 10
b := 20
c := 30
fmt.Println("a=", a, ",b=", b, ",c=", c) // a= 10 ,b= 20 ,c= 30
fmt.Printf("a=%d,b=%d,c=%d", a, b, c) // a=10,b=20,c=30
// Sprintf 拼接字符串,默认转为字符串
host := "127.0.0.1"
port := 6379
addr := fmt.Sprintf("%s:%d", host, port)
fmt.Println(addr)
// Errorf 错误信息
err := fmt.Error("用户名格式不正确: %s", "aaaa")
if err != nil {
panic(err)
}
# %v 和 %s 的区别
- %v 是通用占位符,什么都能打,但格式不一定是你想要的(如字节切片会输出数字数组)
- %s 是字符串专用,用于字符串/字节切片,用在其他类型上会报错(或者产生 %!s(...) 形式的错误)
s := "hello"
b := []byte("world")
num := 123
fmt.Printf("%v\n", s) // 输出: hello
fmt.Printf("%s\n", s) // 输出: hello
fmt.Printf("%v\n", b) // 输出: [119 111 114 108 100]
fmt.Printf("%s\n", b) // 输出: world
fmt.Printf("%v\n", num) // 输出: 123
fmt.Printf("%s\n", num) // 输出: %!s(int=123) 错误!%s 不能用于 int
# 时间处理
- 当前时间
now := time.Now() // 获取当前时间
fmt.Printf("当前时间:%v\n", now)
fmt.Printf("年:%v\n", now.Year())
fmt.Printf("月:%v\n", now.Month())
fmt.Printf("日:%v\n", now.Day())
fmt.Printf("时:%v\n", now.Hour())
fmt.Printf("分:%02d\n", now.Minute())
fmt.Printf("秒:%02d\n", now.Second())
fmt.Printf("星期:%v\n", now.Weekday())
fmt.Printf("纳秒:%v\n", now.Nanosecond())
fmt.Printf("时间戳:%v\n", now.Unix())
- Format 方法格式化输出日期字符串
now := time.Now()
// 格式化的模板为 Go 的出生时间 2006 年 1 月 2 号 15 点 04 分Mon Jan
// 24 小时制
fmt.Println(now.Format("2006-01-02 15:04:05"))
// 12 小时制
fmt.Println(now.Format("2006-01-02 03:04:05"))
fmt.Println(now.Format("2006/01/02 15:04"))
fmt.Println(now.Format("15:04 2006/01/02"))
fmt.Println(now.Format("2006/01/02"))
fmt.Println(now.Format("06")) // 返回的是年份的后两位
- 时间戳转换为日期字符串
func unixToTime1(timestamp int64) {
timeObj := time.Unix(timestamp, 0) // 将时间戳转为时间格式
year := timeObj.Year() // 年
month := timeObj.Month() // 月
day := timeObj.Day() // 日
hour := timeObj.Hour() // 小时
minute := timeObj.Minute() // 分钟
second := timeObj.Second() // 秒
fmt.Printf("%d-%02d-%02d %02d:%02d:%02d\n", year, month, day, hour, minute, second)
}
func unixToTime2(timestamp int64) {
timeObj := time.Unix(timestamp, 0) // 将时间戳转为时间格式
fmt.Println(t.Format("2006-01-02 03:04:05")) // 日期格式化输出
}
func main() {
unixToTime1(1587880013)
unixToTime2(1587880013)
}
- 日期字符串转换成时间戳
t1 := "2019-01-08 13:50:30"
timeTemplate := "2006-01-02 15:04:05" // 常规类型
stamp, _ := time.ParseInLocation(timeTemplate , t1, time.Local)
fmt.Println(stamp.Unix())
- 时间操作函数
now := time.Now()
later := now.Add(time.Hour) // 当前时间加 1 小时后的时间
fmt.Println("1小时后的时间:", later)
difference := later.Sub(now)
fmt.Println("时间差:", difference.Hours())
later.Before(now) // 判断 later 是否在 now 之前
fmt.Println("later 是否在 now 之前:", later.Before(now))
- 定时器
// 使用 time.NewTicker(时间间隔)来设置定时器
ticker := time.NewTicker(time.Second) //定义一个 1 秒间隔的定时器
n := 0
for i := range ticker.C {
fmt.Println(i) //每秒都会执行的任务
n++
if n > 5 {
ticker.Stop()
return
}
}
// time.Sleep(time.Second) 来实现定时器
for {
time.Sleep(time.Second)
fmt.Println("我在定时执行任务")
}
# 文件操作
# 文件基本操作
- 打开和关闭文件
// os.Open()以只读方式打开一个文件,返回一个*File 和一个 err
// 操作完成文件对象以后一定要记得关闭文件
file, err := os.Open("./go.mod")
if err != nil {
fmt.Println("open file failed!, err:", err)
return
}
fmt.Println(file) // &{0xc000078780}
// 为了防止文件忘记关闭,我们通常使用 defer 注册文件关闭语句
defer file.Close()
- 创建文件
// 在当前目录创建(或清空)一个名为 "example.txt" 的文件
file, err := os.Create("example.txt")
if err != nil {
// 如果创建失败,例如父目录不存在或没有写入权限,则记录错误并退出
log.Fatal("创建文件失败: ", err)
}
// defer 语句确保在函数返回前文件被正确关闭,避免资源泄漏
defer file.Close()
log.Println("文件 example.txt 创建成功")
- 创建并写入文件
// 直接写入全部内容(会覆盖原有内容)
data := []byte("Hello, Go!\n第二行内容")
err := os.WriteFile("example.txt", data, 0644)
if err != nil {
panic(err)
}
// 以追加模式打开并写入
f, err := os.OpenFile("example.txt", os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644)
if err != nil {
panic(err)
}
defer f.Close()
_, err = f.WriteString("\n追加一行")
if err != nil {
panic(err)
}
- 读取文件
// 一次性读取整个文件
content, err := os.ReadFile("example.txt")
if err != nil {
panic(err)
}
fmt.Println(string(content))
// 按行读取(使用 bufio)
f, err := os.Open("example.txt")
if err != nil {
panic(err)
}
defer f.Close()
scanner := bufio.NewScanner(f)
for scanner.Scan() {
fmt.Println("行:", scanner.Text())
}
if err := scanner.Err(); err != nil {
panic(err)
}
- 删除文件
err := os.Remove("example.txt")
if err != nil {
panic(err)
}
- 重命名
err := os.Rename("old.txt", "new.txt")
if err != nil {
panic(err)
}
# 目录操作
- 创建目录
// 创建单层目录
err := os.Mkdir("mydir", 0755)
if err != nil {
panic(err)
}
// 创建多层目录
err = os.MkdirAll("path/to/deep/dir", 0755)
if err != nil {
panic(err)
}
- 删除目录
// 删除空目录
err := os.Remove("mydir")
if err != nil {
panic(err)
}
// 递归删除整个目录树
err = os.RemoveAll("path/to/deep")
if err != nil {
panic(err)
}
- 读取目录内容
entries, err := os.ReadDir(".")
if err != nil {
panic(err)
}
for _, entry := range entries {
fmt.Printf("名称: %s, 是否目录: %v\n", entry.Name(), entry.IsDir())
// 如需获取详细信息,可调用 entry.Info()
info, _ := entry.Info()
fmt.Printf(" 大小: %d, 修改时间: %v\n", info.Size(), info.ModTime())
}
- 遍历目录树(递归)
err := filepath.WalkDir(".", func(path string, d fs.DirEntry, err error) error {
if err != nil {
return err
}
fmt.Printf("路径: %s, 是否为目录: %v\n", path, d.IsDir())
return nil
})
if err != nil {
panic(err)
}
# 路径操作
- 路径拼接、拆分
// 平台无关的路径拼接
fullPath := filepath.Join("dir", "subdir", "file.txt")
fmt.Println(fullPath) // 输出: dir/subdir/file.txt (Windows 下为 dir\subdir\file.txt)
// 获取目录和文件名
dir, file := filepath.Split("dir/subdir/file.txt")
fmt.Printf("目录: %q, 文件: %q\n", dir, file) // 目录: "dir/subdir/", 文件: "file.txt"
// 获取绝对路径
abs, err := filepath.Abs(".")
if err != nil {
panic(err)
}
fmt.Println("绝对路径:", abs)
- 检查路径是否存在
func pathExists(path string) (bool, error) {
_, err := os.Stat(path)
if err == nil {
return true, nil
}
if os.IsNotExist(err) {
return false, nil
}
return false, err
}
# 其他I/O操作包
- bufio
# bufio 包实现了带缓冲区的读写,是对文件读写的封装
- ioutil
# ioutil库包含在io目录下,它的主要作用是作为一个工具包,里面有一些比较实用的函数
# 日志服务
日志级别:Debug < Info < Warn < Error < Fatal
import (
rotatelogs "github.com/lestrrat-go/file-rotatelogs"
"github.com/rifflock/lfshook"
"github.com/sirupsen/logrus"
)
type LogFormatter struct{}
// 格式详情
func (s *LogFormatter) Format(entry *logrus.Entry) ([]byte, error) {
timestamp := time.Now().Format("2006-01-02 15:04:05.000")
var file string
var len int
if entry.Caller != nil {
// 数提取路径的最后一个元素,即文件名,例如:F:/project/src/main.go → main.go
file = filepath.Base(entry.Caller.File)
// 获取调用日志函数时的代码行号
len = entry.Caller.Line
}
reqid := utils.GetRequestId() // [reqid:main-container]
msg := fmt.Sprintf("%s [reqid:%v][%s:%d][%s] %s\n",
timestamp, reqid, file, len,
strings.ToUpper(entry.Level.String()), entry.Message)
return []byte(msg), nil
}
// 日志自定义格式
type EmptyFormatter struct{}
// 格式详情
func (s *EmptyFormatter) Format(entry *logrus.Entry) ([]byte, error) {
return []byte{}, nil
}
var (
logFilePath = "./log" //文件存储路径
logFileName = "app.log"
Logger *logrus.Logger
)
func init() {
os.Mkdir("./log", 0755)
// 日志文件
fileName := path.Join(logFilePath, logFileName)
// 以创建、只写、追加模式打开文件 ./log/app.log,权限 0644。如果文件不存在则创建
file, err := os.OpenFile(fileName, os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0644)
if err != nil {
panic("打开/写入文件失败" + err.Error())
}
// 创建一个新的 logrus.Logger 实例,赋值给全局变量 Logger
Logger = logrus.New()
// 日志级别
Logger.SetLevel(logrus.DebugLevel)
// 设置输出目标为刚刚打开的 app.log 文件
// 即使设置了 Logger.Out = file
// 但由于使用了 EmptyFormatter,实际上不会向 file 写入任何内容
// 所有日志都通过 hook 机制写入轮转文件
Logger.Out = file
// 启用报告调用者信息,这样每条日志的 entry.Caller 才能获取文件名和行号
Logger.SetReportCaller(true)
// 设置日志格式为 EmptyFormatter,即通过 Logger.Out 输出时不会产生任何内容
// 这是为了避免直接写入 app.log 文件,转而通过下面的 hook 写入带轮转的日志文件
Logger.SetFormatter(&EmptyFormatter{})
// 用于日志文件的自动切割和轮转
logWriter, err := rotatelogs.New(
// 分割后的文件名称
fileName+".%Y%m%d.log",
// 生成软链,指向最新日志文件
// 让你可以用固定的文件名访问不断变化的最新日志文件,无需关心具体的日期后缀
rotatelogs.WithLinkName(fileName),
// 设置最大保存时间(7天)
rotatelogs.WithMaxAge(7*24*time.Hour), //以hour为单位的整数
// 设置日志切割时间间隔(1小时),实际是每天生成一个文件
rotatelogs.WithRotationTime(1*time.Hour),
)
if err != nil {
panic(err)
}
// 为每个日志级别指定写入目标。这里所有级别都使用同一个 logWriter
writerMap := lfshook.WriterMap{
logrus.InfoLevel: logWriter,
logrus.FatalLevel: logWriter,
logrus.DebugLevel: logWriter,
logrus.WarnLevel: logWriter,
logrus.ErrorLevel: logWriter,
logrus.PanicLevel: logWriter,
}
// 调用 Logger.Infof()及以上级别 就会触发所有已注册的 Hook
// fshook.NewHook() 是lfshook 提供的 Hook 实现,专门用于将不同级别的日志写入不同的文件
Logger.AddHook(lfshook.NewHook(writerMap, &LogFormatter{}))
}
log 包提供了一个预定义的 logger,可以通过调用函数Print系列(Print|Printf]PrintIn) 、Fatal系列(Fatal|Fatalf|Fatalln)、和Panic系列(Panic|Panicf|Panicln)来使用。
# print系列
单纯打印日志
// 2022/08/01 14:11:26 this is a log
log.Print("this is a log")
// 2022/08/01 14:11:26 this is a log: 100
log.Printf("this is a log: %d", 100)
name := "zhang san"
age := 20
// 2022/08/01 14:11:26 zhangsan 20
log.Println(name, " ", age)
# panic系列
打印日志,抛出panic异常
defer fmt.Println("发生了 panic误!")
log.Print("this is a log")
log.Panic("this is a panic log")
fmt.Println("运行结束...")
// 2022/08/01 14:12:13 this is a log
// 2022/08/01 14:12:13 this is a panic log
// 发生了panic错误!
// panic: this is a panic log
# fatal系列
打印日志,强制结束程序(os.Exit(1)),defer函数不会执行
defer fmt.Println("defer... ")
log.Print("this is a log")
log.Fatal("this is a fatal log")
fmt.Println("运行结束... ")
// 2022/08/01 14:12:45 this is a log
// 2022/08/01 14:12:45 this is a fatal log
# 网络请求
# 网址解析
// 这是 URL 标准规范(RFC 3986) 中定义的固定语法
https://username:password@host
└────┬─┘ └───┬───┘
用户名 密码
u, _ := url.Parse("https://username:password@host")
name := u.User.Username()
password, _ := u.User.Password()
# JSON 序列化 jsoniter
- json 的高性能替代品 jsoniter
// 可无缝替换 encoding/json 的性能升级包
// 它拥有完全兼容的 API,通过优化反射和内存管理,能显著提升序列化/反序列化速度
import (
jsoniter "github.com/json-iterator/go"
"github.com/json-iterator/go/extra"
)
func init() {
// 用于注册 jsoniter 的模糊解码器
// 默认情况下,JSON 反序列化要求类型严格匹配
// 会自动将字符串 "25" 转换为整数 25
extra.RegisterFuzzyDecoders()
}
// 使用兼容标准库的配置初始化
var json = jsoniter.ConfigCompatibleWithStandardLibrary
type User struct {
Name string `json:"name"`
Age int `json:"age"`
}
func main() {
jsonStr := `{"name":"Alice", "age":30}`
var user User
err := json.Unmarshal([]byte(jsonStr), &user)
// 或者不需要兼容标准库的配置初始化
// err := jsoniter.Unmarshal([]byte(jsonStr), &user)
if err != nil { panic(err) }
println(user.Name) // 输出 Alice
}
- 嵌套结构的可以使用 gjson
// go get -u github.com/tidwall/gjson
json := `{
"user":{
"first.name": "Janet",
"last.name": "Prichard"
}
}`
user := gjson.Get(json, "user")
fmt.Println(user.Get(gjson.Escape("first.name")))
fmt.Println(user.Get(gjson.Escape("last.name")))
# 表达式计算 govaluate
- 主要作用是将你提供的字符串表达式 expr 进行“编译”,转换成一个可以反复执行的“表达式对象”
- 可以把它理解为在 Go 程序中实现了一个轻量级的、类似于 eval 的功能
# 工作流程可以分为两步
- 调用NewEvaluableExpression()将表达式转为一个表达式对象
expression, err := govaluate.NewEvaluableExpression(expr)
- 调用表达式对象的Evaluate方法,传入参数,返回表达式的值
result, err := expression.Evaluate(params)
# 代码示例
- 对于没有参数的表达式,Evaluate 方法传入 nil
import (
"fmt"
"log"
"github.com/Knetic/govaluate"
)
func main() {
// 1. 解析并编译表达式
expr, err := govaluate.NewEvaluableExpression("10 > 0")
if err != nil {
log.Fatal("表达式语法错误: ", err)
}
// 2. 计算表达式的值
result, err := expr.Evaluate(nil)
if err != nil {
log.Fatal("计算表达式出错: ", err)
}
fmt.Println(result) // 输出: true
}
- 带参数和表达式复用
import (
"fmt"
"github.com/Knetic/govaluate"
)
func main() {
// 1. 定义一个带参数 a 和 b 的表达式
expr, _ := govaluate.NewEvaluableExpression("a * b")
// 2. 准备第一组参数并计算
params1 := map[string]interface{}{"a": 2, "b": 4}
result1, _ := expr.Evaluate(params1)
fmt.Println(result1) // 输出: 8
// 3. 复用同一个表达式对象,传入第二组参数计算
params2 := map[string]interface{}{"a": 5, "b": 6}
result2, _ := expr.Evaluate(params2)
fmt.Println(result2) // 输出: 30
}
← 基础知识