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

image

写文件

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)
	}
}

需求分析

  1. 支持往不同的地方输出日志
  2. 日志分级别
    1. Debug
    2. Trace
    3. Info
    4. Warning
    5. Error
    6. Fatal
  3. 日志要支持开关控制,比如开发的时候什么级别都能输出,但是上线之后只要INFO级别之后的才能输出
  4. 完整的日志记录要有时间、行号、日志级别、日志信息
  5. 日志文件要切割
    1. 按文件大小切割
      1. 每次记录日志之前都判断一下当前写的这个文件的文件大小
    2. 按日期切割
      1. 在日志结构体中设置一个字段记录上次切割的小时数
      2. 在写日志之前检查一下当前的时间的小时数和之前保存的是否一致,不一致就切割

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)
	}
}

反射是把双刃剑

反射是一个强大并富有表现力的工具,能让我们写出更灵活的代码。但是反射不应该被滥用,原因有以下三个。

  1. 基于反射的代码是极其脆弱的,反射中的类型错误会在真正运行的时候才会引发panic,那很可能是在代码写完的很长时间之后。
  2. 大量使用反射的代码通常难以理解。
  3. 反射的性能低下,基于反射实现的代码通常比正常代码运行速度慢一到两个数量级。

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)
}

posted @ 2021-05-10 10:55  橘丶阳菜  阅读(92)  评论(0编辑  收藏  举报