Go 基础之time库、自定义日志库、反射、配置文件解析器
Go 7
包
包的定义——>package
关键字,包名通常是和目录名一致,不能包含-
- 一个文件夹就是一个包
- 文件夹里面放的都是
.go
文件
包的导入——>import
- 包导入的路径是从
$GOPATH/src
后面的路径开始写起 - 单行导入
- 多行导入
- 给导入的包起别名
- 匿名导入
- Go不支持循环导入
包中标识符
(变量、函数、结构体、接口名)可见性——>标识符首字母大写表示对外可见
init()
- 包导入的时候会自动执行
- 一个包里面只要一个
init()
init()
没有参数也没提返回值也不能调用它- 多个包的
init()
执行顺序 - 一般用于做一些初始化操作...
接口
接口是一种类型,一种抽象的类型。
接口的定义
type mover interface{
move()
// 方法的前面(参数)(返回值)
}
接口的实现
实现了接口的所有方法就实现了这个接口。
实现了接口就可以当成这个接口类型的变量
接口变量
实现了一个万能的变量,可以保存所有实现了这个接口的类型的值
通常是作为函数的参数出现
空接口
interface{}
:空接口
接口中没有定义任何方法,也就是说任意类型都实现了空接口——>任何类型都可以存到空接口变量中
作为函数参数——>fmt.Println()
map[string]interface{}
——>表示字符串的键可以对应任意变量的值
package main
import "fmt"
func main() {
m1 := make(map[interface{}]interface{})
fmt.Println(m1)
m1["sss"] = "sss"
m1[111] = 111
m1[[...]int{1, 2, 3}] = [...]int{1, 2, 3}
m1[nil] = nil
fmt.Println(m1)
}
接口底层
- 动态类型
- 动态值
类型断言(判断变量是什么类型的)
做类型断言的前提一定要是一个借口类型的变量
x.(T)
使用switch
来做类型断言
package main
import "fmt"
// 类型断言
func main() {
var a interface{} // 定义一个空接口变量a
a = 100
// 如何判断a保存的值得具体类型是什么?
// 类型断言
// x.(T)
if v, ok := a.(int8); ok {
fmt.Println("猜对了", v)
return
} else {
fmt.Println("猜错了,不是int8")
}
// switch
switch vv := a.(type) {
case int8:
fmt.Println("int8", vv)
case int16:
fmt.Println("int16")
case string:
fmt.Println("string")
case int:
fmt.Println("int")
default:
fmt.Println("滚")
}
}
文件操作
打开文件和关闭文件
// 读文件
func readfile() {
fileobj, err := os.Open("./main.go")
if err != nil {
fmt.Println("open file failed! err:", err)
return
}
defer fileobj.Close()
}
读文件
fileobj.Read()
bufio
ioutil
写文件
os.OpenFile()
fileobj.write/fileobj.WriteString
bufio
ioutil
time标准库
func timeknow() {
now := time.Now()
fmt.Println(now)
fmt.Println(now.Year())
fmt.Println(now.Month())
fmt.Println(now.Day())
fmt.Println(now.Date())
fmt.Println(now.Hour())
// 时间对象转时间戳
fmt.Println(now.Unix())
fmt.Println(now.UnixNano())
// 时间戳转时间对象
ret := time.Unix(1620097458, 0)
fmt.Println(ret)
// 时间间隔
fmt.Println(time.Second)
// 时间操作
// now + 1小时
fmt.Println(now.Add(time.Hour * 24))
// sub 时间相减
fmt.Println(now.Sub(now.Add(time.Hour * 24)))
/*
判断时间先后
func (t Time) Equal(u Time) bool
unc (t Time) Before(u Time) bool
func (t Time) After(u Time) bool
*/
// 定时器
// timer := time.Tick(time.Second)
// for t := range timer {
// fmt.Println(t) // 1s中执行一次
// }
// 格式化时间:对象转字符串
fmt.Println(now.Format("2006-01-02 15:04:05"))
fmt.Println(now.Format("2006-01-02 03:04:05 PM"))
// 按照对应的格式解析字符串类型的时间
timeobj, err := time.Parse("2006-01-02", "2000-01-22")
if err != nil {
fmt.Printf("failed, err:%v\n", err)
return
}
fmt.Println(timeobj.UnixNano())
tobj, err := time.ParseInLocation("2006-01-02 15:04:05", "2021-05-04 11:32:00", time.Local)
if err != nil {
fmt.Printf("failed, err:%v\n", err)
return
}
fmt.Println(tobj)
// time.Sleep
// n := 5
// time.Sleep(time.Duration(n) * time.Second)
time.Sleep(5 * time.Second)
fmt.Println("睡5s")
}
获取指定时区的时间
// 时区
func f1() {
now := time.Now() // 本地时间
fmt.Println(now)
// 明天的这个时间
t2, _ := time.Parse("2006-01-02 15:04:05", "2021-05-05 11:40:22")
fmt.Println(t2)
// 按照指定时区解析时间
t1, _ := time.ParseInLocation("2006-01-02 15:04:05", "2021-05-05 11:40:22", time.Local)
fmt.Println(t1)
loc, _ := time.LoadLocation("Asia/Shanghai")
t3, _ := time.ParseInLocation("2006-01-02 15:04:05", "2021-05-05 11:40:22", loc)
fmt.Println(t3)
fmt.Println(t1.Sub(now))
fmt.Println(t2.Sub(now))
fmt.Println(t3.Sub(now))
}
日志库
package main
import (
"fmt"
"log"
"os"
"time"
)
// log 日志库
func main() {
fileobj, err := os.OpenFile("./xx.log", os.O_CREATE|os.O_APPEND|os.O_WRONLY, 0644)
if err != nil {
fmt.Printf("failed, err:%v\n", err)
return
}
log.SetOutput(fileobj) // 设置输出位置,就是文件句柄
for {
log.Printf("测试日志")
time.Sleep(5 * time.Second)
}
}
需求分析
- 支持往不同的地方输出日志
- 日志分级别
- Debug
- Trace
- Info
- Warning
- Error
- Fatal
- 日志要支持开关控制,比如开发的时候什么级别都能输出,但是上线之后只要INFO级别之后的才能输出
- 完整的日志记录要有时间、行号、日志级别、日志信息
- 日志文件要切割
- 按文件大小切割
- 每次记录日志之前都判断一下当前写的这个文件的文件大小
- 按日期切割
- 在日志结构体中设置一个字段记录上次切割的小时数
- 在写日志之前检查一下当前的时间的小时数和之前保存的是否一致,不一致就切割
- 按文件大小切割
main.go
package main
import (
"time"
"go.study.com/hina/day01/day07/mylogger"
)
var log mylogger.Loggerjk // 声明一个全局的接口变量
func main() {
// log = mylogger.NewConsoleLogger("Info") // 终端日志实例
log = mylogger.NewFileLogger("Info", "./", "hina.log", 10*1024*1024) // 文件日志实例
for {
log.Debug("这是一条Debug日志")
time.Sleep(2 * time.Second)
log.Trace("这是一条Trace日志")
time.Sleep(2 * time.Second)
log.Info("这是一条Info日志")
time.Sleep(2 * time.Second)
id := 100
name := "hina"
log.Warning("这是一条Warning日志,id:%d, name:%s", id, name)
time.Sleep(2 * time.Second)
log.Error("这是一条Error日志")
time.Sleep(2 * time.Second)
log.Fatal("这是一条Fatal日志")
time.Sleep(2 * time.Second)
}
}
mylogger.go
package mylogger
import (
"errors"
"path"
"runtime"
"strings"
)
// 自定义一个日志库
// 定义一个LogLevel类型 用来比较DEBUG INFO...的等级
type LogLevel uint16
// Logger接口
type Loggerjk interface {
Debug(format string, a ...interface{})
Trace(format string, a ...interface{})
Info(format string, a ...interface{})
Warning(format string, a ...interface{})
Error(format string, a ...interface{})
Fatal(format string, a ...interface{})
}
const (
// 定义日志级别
UNKNOW LogLevel = iota
DEBUG
TRACE
INFO
WARNING
ERROR
FATAL
)
// 将字符串转化为LogLevel类型、用来比较传入的level和log的等级大小
func parseLogLevel(s string) (LogLevel, error) {
s = strings.ToLower(s)
switch s {
case "debug":
return DEBUG, nil
case "trace":
return TRACE, nil
case "info":
return INFO, nil
case "warning":
return WARNING, nil
case "error":
return ERROR, nil
case "fatal":
return FATAL, nil
default:
err := errors.New("无效日志级别")
return UNKNOW, err
}
}
// 将LogLevel类型转化字符串,用来输出日志等级
func getlogstring(lv LogLevel) string {
switch lv {
case DEBUG:
return "DEBUG"
case TRACE:
return "TRACE"
case INFO:
return "INFO"
case WARNING:
return "WARNING"
case ERROR:
return "ERROR"
case FATAL:
return "FATAL"
default:
return "DEBUG"
}
}
// 用来获取的执行的文件名称、执行的函数名、执行的行号等信息
func getInfo(skip int) (funcname, filename string, lineno int) {
pc, file, lineno, ok := runtime.Caller(skip)
if !ok {
return
}
funcname = runtime.FuncForPC(pc).Name()
filename = path.Base(file)
funcname = strings.Split(funcname, ".")[1]
return
}
console.go
package mylogger
import (
"fmt"
"time"
)
// 在终端输出相关内容
// Logger结构体 里面有log的等级
type ConsoleLogger struct {
Level LogLevel
}
// 构造一个返回log对象的函数,参数是main中传入的日志级别的字符串格式
func NewConsoleLogger(levelstr string) ConsoleLogger {
level, err := parseLogLevel(levelstr)
if err != nil {
panic(err)
}
return ConsoleLogger{
Level: level,
}
}
// 用来判断当前等级是否比传入等级高
func (c ConsoleLogger) enable(loglevel LogLevel) bool {
return loglevel >= c.Level
}
// DEBUG级别日志,可以接受多个参数并字符串格式化
func (c ConsoleLogger) Debug(format string, a ...interface{}) {
c.log(DEBUG, format, a...)
}
// TRACE级别日志,可以接受多个参数并字符串格式化
func (c ConsoleLogger) Trace(format string, a ...interface{}) {
c.log(TRACE, format, a...)
}
// INFO级别日志,可以接受多个参数并字符串格式化
func (c ConsoleLogger) Info(format string, a ...interface{}) {
c.log(INFO, format, a...)
}
// WARNING级别日志,可以接受多个参数并字符串格式化
func (c ConsoleLogger) Warning(format string, a ...interface{}) {
c.log(WARNING, format, a...)
}
// ERROR级别日志,可以接受多个参数并字符串格式化
func (c ConsoleLogger) Error(format string, a ...interface{}) {
c.log(ERROR, format, a...)
}
// FATAL级别日志,可以接受多个参数并字符串格式化
func (c ConsoleLogger) Fatal(format string, a ...interface{}) {
c.log(FATAL, format, a...)
}
// 将日志信息打印到终端
func (c ConsoleLogger) log(lv LogLevel, format string, a ...interface{}) {
if c.enable(lv) {
msg := fmt.Sprintf(format, a...)
now := time.Now()
funcname, filename, lineno := getInfo(3) // 3表示runtime.Caller(skip)的外面第三层调用的函数
ts := now.Format("2006-01-02 15:04:05")
fmt.Printf("[%s] [%s] [%s:%s:%d] %s\n", ts, getlogstring(lv), filename, funcname, lineno, msg)
}
}
file.go
package mylogger
import (
"fmt"
"os"
"path"
"time"
)
// 往文件里写日志相关代码
// 文件日志结构体
type Filelogger struct {
Level LogLevel
filePath string // 日志文件保存的路径
fileName string // 日志文件保存的文件名
fileobj *os.File
errfileobj *os.File
maxFileSize int64 // 文件大小
}
// 构造函数
func NewFileLogger(levelstr, fp, fn string, maxsize int64) *Filelogger {
LogLevel, err := parseLogLevel(levelstr)
if err != nil {
panic(err)
}
fl := &Filelogger{
Level: LogLevel,
filePath: fp,
fileName: fn,
maxFileSize: maxsize,
}
err = fl.initFile() // 按照文件路径和文件名将文件打开
if err != nil {
panic(err)
}
return fl
}
// 根据指定的日志文件路径和文件名打开日志文件
func (f *Filelogger) initFile() error {
fullpathname := path.Join(f.filePath, f.fileName)
fileobj, err := os.OpenFile(fullpathname, os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0644)
if err != nil {
fmt.Printf("open log file failed, err:%v\n", err)
return err
}
errfileobj, err := os.OpenFile(fullpathname+".err", os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0644)
if err != nil {
fmt.Printf("open err log file failed, err:%v\n", err)
return err
}
// 日志文件都已经打开
f.fileobj = fileobj
f.errfileobj = errfileobj
return nil
}
// 用来判断是否需要记录该级别的日志
func (f *Filelogger) enable(loglevel LogLevel) bool {
return loglevel >= f.Level
}
// DEBUG级别日志,可以接受多个参数并字符串格式化
func (f *Filelogger) Debug(format string, a ...interface{}) {
f.log(DEBUG, format, a...)
}
// TRACE级别日志,可以接受多个参数并字符串格式化
func (f *Filelogger) Trace(format string, a ...interface{}) {
f.log(TRACE, format, a...)
}
// INFO级别日志,可以接受多个参数并字符串格式化
func (f *Filelogger) Info(format string, a ...interface{}) {
f.log(TRACE, format, a...)
}
// WARNING级别日志,可以接受多个参数并字符串格式化
func (f *Filelogger) Warning(format string, a ...interface{}) {
f.log(WARNING, format, a...)
}
// ERROR级别日志,可以接受多个参数并字符串格式化
func (f *Filelogger) Error(format string, a ...interface{}) {
f.log(ERROR, format, a...)
}
// FATAL级别日志,可以接受多个参数并字符串格式化
func (f *Filelogger) Fatal(format string, a ...interface{}) {
f.log(FATAL, format, a...)
}
// 将日志信息写到日志文件
func (f *Filelogger) log(lv LogLevel, format string, a ...interface{}) {
if f.enable(lv) {
msg := fmt.Sprintf(format, a...)
now := time.Now()
funcname, filename, lineno := getInfo(3) // 3表示runtime.Caller(skip)的外面第三层调用的函数
ts := now.Format("2006-01-02 15:04:05")
if f.checkSize(f.fileobj) {
newfile, err := f.splitfile(f.fileobj)
if err != nil {
return
}
f.fileobj = newfile
}
fmt.Fprintf(f.fileobj, "[%s] [%s] [%s:%s:%d] %s\n", ts, getlogstring(lv), filename, funcname, lineno, msg)
if lv >= ERROR {
if f.checkSize(f.errfileobj) {
newfile, err := f.splitfile(f.errfileobj)
if err != nil {
return
}
f.errfileobj = newfile
}
// 如果要记录的日志等级大于等于ERROR级别,还要写入err日志文件中写入
fmt.Fprintf(f.errfileobj, "[%s] [%s] [%s:%s:%d] %s\n", ts, getlogstring(lv), filename, funcname, lineno, msg)
}
}
}
// 切割文件
func (f *Filelogger) splitfile(file *os.File) (*os.File, error) {
// 需要切割日志文件
nowStr := time.Now().Format("20060102150405000")
fileinfo, err := file.Stat()
if err != nil {
fmt.Printf("failed, err:%v\n", err)
return nil, err
}
oldlogname := path.Join(f.filePath, fileinfo.Name()) // 拿到当前的日志文件完整路径
newlogname := fmt.Sprintf("%s.bak%s", oldlogname, nowStr) // 拼接一个日志文件备份的名字
// 1.关闭当前日志文件
file.Close()
// 2.rename备份一下 xx.log——> xx.log.bak200001011111
os.Rename(oldlogname, newlogname)
// 3.打开一个新的日志文件
fileobj, err := os.OpenFile(oldlogname, os.O_CREATE|os.O_APPEND|os.O_WRONLY, 0644)
if err != nil {
fmt.Printf("open new log failed, err:%v\n", err)
return nil, err
}
// // 4.将打开的新的日志文件对象赋值给 f.fileobj
// f.fileobj = fileobj
return fileobj, nil
}
// 判断文件是否需要切割
func (f *Filelogger) checkSize(file *os.File) bool {
fileinfo, err := file.Stat()
if err != nil {
fmt.Printf("failed, err:%v\n", err)
return false
}
return fileinfo.Size() >= f.maxFileSize
}
反射
reflect
reflect.TypeOf()
``type.Name()、
type.Kind()`
func reflectType(x interface{}) {
t := reflect.TypeOf(x)
fmt.Printf("type:%v kind:%v\n", t.Name(), t.Kind())
}
reflect.ValueOf()
v := reflect.ValueOf(x)
k := v.Kind() // 值的类型的种类
通过反射设置变量的值
Elem()
func reflectSetValue1(x interface{}) {
v := reflect.ValueOf(x)
if v.Kind() == reflect.Int64 {
v.SetInt(200) //修改的是副本,reflect包会引发panic
}
}
func reflectSetValue2(x interface{}) {
v := reflect.ValueOf(x)
// 反射中使用 Elem()方法获取指针对应的值
if v.Elem().Kind() == reflect.Int64 {
v.Elem().SetInt(200)
}
}
func main() {
var a int64 = 100
// reflectSetValue1(a) //panic: reflect: reflect.Value.SetInt using unaddressable value
reflectSetValue2(&a)
fmt.Println(a)
}
结构体反射
任意值通过reflect.TypeOf()
获得反射对象信息后,如果它的类型是结构体,可以通过反射值对象(reflect.Type
)的NumField()
和Field()
方法获得结构体成员的详细信息。
reflect.Type
中与获取结构体成员相关的的方法如下表所示。
方法 | 说明 |
---|---|
Field(i int) StructField | 根据索引,返回索引对应的结构体字段的信息。 |
NumField() int | 返回结构体成员字段数量。 |
FieldByName(name string) (StructField, bool) | 根据给定字符串返回字符串对应的结构体字段的信息。 |
FieldByIndex(index []int) StructField | 多层成员访问时,根据 []int 提供的每个结构体的字段索引,返回字段的信息。 |
FieldByNameFunc(match func(string) bool) (StructField,bool) | 根据传入的匹配函数匹配需要的字段。 |
NumMethod() int | 返回该类型的方法集中方法的数目 |
Method(int) Method | 返回该类型方法集中的第i个方法 |
MethodByName(string)(Method, bool) | 根据方法名返回该类型方法集中的方法 |
type student struct {
Name string `json:"name"`
Score int `json:"score"`
}
func main() {
stu1 := student{
Name: "小王子",
Score: 90,
}
t := reflect.TypeOf(stu1)
fmt.Println(t.Name(), t.Kind()) // student struct
// 通过for循环遍历结构体的所有字段信息
for i := 0; i < t.NumField(); i++ {
field := t.Field(i)
fmt.Printf("name:%s index:%d type:%v json tag:%v\n", field.Name, field.Index, field.Type, field.Tag.Get("json"))
}
// 通过字段名获取指定结构体字段信息
if scoreField, ok := t.FieldByName("Score"); ok {
fmt.Printf("name:%s index:%d type:%v json tag:%v\n", scoreField.Name, scoreField.Index, scoreField.Type, scoreField.Tag.Get("json"))
}
}
接下来编写一个函数printMethod(s interface{})
来遍历打印s包含的方法。
// 给student添加两个方法 Study和Sleep(注意首字母大写)
func (s student) Study() string {
msg := "好好学习,天天向上。"
fmt.Println(msg)
return msg
}
func (s student) Sleep() string {
msg := "好好睡觉,快快长大。"
fmt.Println(msg)
return msg
}
func printMethod(x interface{}) {
t := reflect.TypeOf(x)
v := reflect.ValueOf(x)
fmt.Println(t.NumMethod())
for i := 0; i < v.NumMethod(); i++ {
methodType := v.Method(i).Type()
fmt.Printf("method name:%s\n", t.Method(i).Name)
fmt.Printf("method:%s\n", methodType)
// 通过反射调用方法传递的参数必须是 []reflect.Value 类型
var args = []reflect.Value{}
v.Method(i).Call(args)
}
}
反射是把双刃剑
反射是一个强大并富有表现力的工具,能让我们写出更灵活的代码。但是反射不应该被滥用,原因有以下三个。
- 基于反射的代码是极其脆弱的,反射中的类型错误会在真正运行的时候才会引发panic,那很可能是在代码写完的很长时间之后。
- 大量使用反射的代码通常难以理解。
- 反射的性能低下,基于反射实现的代码通常比正常代码运行速度慢一到两个数量级。
ini配置文件解析器
利用反射实现一个ini配置文件解析器
package main
import (
"fmt"
"reflect"
)
// ini配置文件解析器
// MysqlConfig
type MysqlConfig struct {
Address string `ini:"address"`
Port int `ini:"port"`
Username string `ini:"username"`
Password string `ini:"password"`
}
func loadini(x interface{}) {
t := reflect.TypeOf(x)
v:=reflect.ValueOf(x)
}
func main() {
var mysql MysqlConfig
loadini(&mysql)
fmt.Println(mysql)
}