go unix 转化为time_Go语言基础(九)

3de0a2bb78ba6e14a2212eea7586f91a.png

Go语言基础(九)

一、Time包

二、输出日志

三、反射


一、Time包

1、time.Now()获取当前时间对象

func timeNow() {
	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.Minute())
	fmt.Println(now.Second())
}

bd1c930326f5fd7f5b009a9e051c55a6.png
输出结果

2、时间戳

时间戳是自1970年1月1日(08:00:00GMT)至当前时间的总毫秒数,它也成为了Unix时间戳。

func unixTimeStamp() {
	now := time.Now()
	timeStamp1 := now.Unix()
	timeStamp2 := now.UnixNano()
	fmt.Printf("current timestamp1:%vn", timeStamp1)
	fmt.Printf("current timestamp2:%vn", timeStamp2)

        // 使用time.Unix()函数可以将时间戳转换为时间格式。
	ret := time.Unix(timeStamp1, 0)
	fmt.Println(ret)
	fmt.Println(ret.Year())
	fmt.Println(ret.Month())
	fmt.Println(ret.Day())
}

使用time.Unix()函数可以将时间戳转换为时间格式。

3、时间间隔

Duration类型表示两个时间点之间经历的时间,以纳秒为单位,可表示的最大时间段约为290年,time包中自定义的时间间隔常量如下:

4da802fffbe003a1d0f3122f10923a8b.png

4、时间操作

a、Add

b、Sub

func timeOp() {
	now := time.Now()
	fmt.Println(now.Add(24 * time.Hour))
	// fmt.Println(now.Sub(time.Minute))
}

c、Equal,判断两个时间是否相同,考虑时区的影响。

d、Before

func (t Time) Before(u Time) bool

如果t代表的时间点在u之前,返回真,否则返回假。

e、After

func (t Time) After(u Time) bool

如果t代表的时间点在u之后,返回真,否则返回假。

5、定时器

使用time.Tick(时间间隔)来设置定时器,定时器本质上是一个通道channel。

func timeTick() {
	timer := time.Tick(time.Second)
	for t := range timer {
		fmt.Println(t)
	}
}

6、时间格式化

时间类型有一个自带的方法Format进行格式化,需要注意的是Go语言中格式化时间不是常见的Y-m-d H:M:S而是使用Go语言诞生时间2006年1月2号15点04分(记忆口诀2006 1 2 3 4)。

注意:如果想格式化为12小时制,需指定PM。

func formatTime() {
	now := time.Now()
	// 格式化的模板为Go语言诞生时间2006-01-02 15:04:05 Mon Jan
	// 24小时制
	fmt.Println(now.Format("2006-01-02 15:04:05.000 Mon Jan"))
	// 12小时制
	fmt.Println(now.Format("2006-01-02 03:04:05.000 Mon Jan"))
	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"))
}

7、时区

package main

import (
	"fmt"
	"time"
)

func f2()  {
	now := time.Now()
	fmt.Println(now)
	// 明天这个时间
	time.Parse("2006-01-02 15:04:05", "2019-08-04 21:16:00")
	// 按照东八区的时区和格式解析一个字符串格式的时间
	// 根据字符串加载时区
	loc, err := time.LoadLocation("Asia/Shanghai")
	if err != nil {
		fmt.Printf("location loc failed, err:%v", err)
		return
	}
	// 按照指定时区解析时间
	timeObj, err := time.ParseInLocation("2006-01-02 15:04:05", "2020-02-07 21:24:00", loc)
	if err != nil {
		fmt.Printf("parse time failed, err:%v", err)
		return
	}
	fmt.Println(timeObj)
	// 对象时间相减
	td := timeObj.Sub(now)
	fmt.Println(td)
}

func main()  {
	f2()
}

二、输出日志

简单示例:

package main

import (
	"fmt"
	"log"
	"os"
	"time"
)

func main() {
	fileObj, err := os.OpenFile("./xx.log", os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644)
	if err != nil {
		fmt.Printf("open file failed, err:%v", err)
		return
	}
	// log.SetOutPut(os.stdout)
	log.SetOutput(fileObj)
	for {
		log.Printf("这是一条普通日志")
		time.Sleep(time.Second * 3)
	}
}

1、日志库需求分析

a、支持往不同的地方输出日志

b、日志分级,Debug、Trace、Info、Warning、Error、Fatal

c、日志要支持开关控制

d、日志要有时间、行号、文件名、日志级别、日志信息

e、日志要切割

按文件大小切割,每次记录日志之前都判断一下当前写的这个文件大小

按日期分割,在日志结构体中设置一个字段记录上次切割的小时数

在写日志之前检查一下当前小时数和之前保存的是否一致,不一致就要切割

2、runtime.Caller()

package main

import (
	"fmt"
	"runtime"
)

func f1() {
	// 把0分别改成1,2试试
	pc, file, line, ok := runtime.Caller(2)
	if !ok {
		fmt.Println("runtime callback failed")
		return
	}
	fmt.Println(pc)
	funcName := runtime.FuncForPC(pc).Name()
	fmt.Println(funcName)
	fmt.Println(file)
        fmt.Println(path.Base(file))
	fmt.Println(line)
}

func f() {
	f1()
}

func main() {
	f()
}

3、实现往终端输出日志

package mylogger

import (
	"errors"
	"fmt"
	"path"
	"runtime"
	"strings"
	"time"
)

// 日志级别
type LogLevel uint16

const (
	UNKNOWN LogLevel = iota
	DEBUG
	TRACE
	INFO
	WARNING
	ERROR
	FATAL
)

// 日志结构体
type Logger struct {
	Level 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"
	}
	return "DEBUG"
}

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 UNKNOWN, err
	}
}

func getInfo(skip int) (funcName, filename string, lineNo int) {
	pc, file, lineNo, ok := runtime.Caller(skip)
	if !ok {
		fmt.Println("runtime callback failed")
		return
	}
	funcName = runtime.FuncForPC(pc).Name()
	filename = path.Base(file)
	funcName = strings.Split(funcName, ".")[1]
	return
}

// 构造函数
func NewLog(LevelStr string) Logger {
	level, err := parseLogLevel(LevelStr)
	if err != nil {
		panic(err)
	}
	return Logger{
		Level: level,
	}
}

func (l Logger) enable(LogLevel LogLevel) bool {
	return l.Level > LogLevel
}

func (l Logger) log(lv LogLevel, format string, a ...interface{}) {
	if l.enable(lv) {
		msg := fmt.Sprintf(format, a...)
		now := time.Now()
		funcName, fileName, lineNo := getInfo(3)
		fmt.Printf("[%s] [%s] [%s:%s:%d] %s n", now.Format("2016-01-02 15:04:05"), getLogString(lv), fileName, funcName, lineNo, msg)
	}
}

func (l Logger) Debug(format string, a ...interface{}) {
	l.log(DEBUG, format, a)
}

func (l Logger) Info(format string, a ...interface{}) {
	l.log(INFO, format, a)
}

func (l Logger) Warning(format string, a ...interface{}) {
	l.log(WARNING, format, a)
}

func (l Logger) Error(format string, a ...interface{}) {
	l.log(ERROR, format, a)
}

func (l Logger) Fatal(format string, a ...interface{}) {
	l.log(FATAL, format, a)
}

4、往文件中输出日志

这个有点没懂,以后再说吧~

三、反射

反射是指在程序运行期间对程序本身进行访问和修改的能力。程序在编译时,变量被转换为内存地址,变量名不会被编译器写入可执行部分,在运行程序时,程序无法获取自身信息。

支持反射的语言可以在程序编译期将变量的反射信息,如字段名称、类型信息、结构体信息等整合到可执行文件中,并给程序提供接口访问反射信息,这样就可以在程序运行时获取类型的反射信息,并且有能力改变他们。

Go语言在程序运行期间使用refect包访问程序的反射信息。

package main

import (
	"fmt"
	"reflect"
)

func reflectType(x interface{}) {
	v := reflect.TypeOf(x)
	fmt.Printf("type:%vn", v)
}

func main() {
	var a float32 = 3.14
	reflectType(a)
	var b int64 = 100
	reflectType(b)
}

1、type name和type kind

在反射中关于类型分为两种:类型(Type)和类型(Kind)。因为在Go语言中使用Type关键字构造很多自定义类型,而kind就是指底层类型,但在反射中,当需要区分指针、结构体等大品种类型时,就会用到kind。

package main

import (
	"fmt"
	"reflect"
)

type Cat struct {
}

func reflectType(x interface{}) {
	v := reflect.TypeOf(x)
	fmt.Printf("type:%vn", v)
	fmt.Printf("type name:%s, type kind:%sn", v.Name(), v.Kind())
}

func main() {
	var a float32 = 3.14
	reflectType(a)
	var b int64 = 100
	reflectType(b)
	var c = Cat{}
	reflectType(c)
}

Go语言的反射中像数组、切片、map、指针等类型的变量,他们的.Name()都是返回空。

在reflect包中定义的Kind类型如下:

type Kind uint

const (
	Invalid Kind = iota
	Bool
	Int
	Int8
	Int16
	Int32
	Int64
	Uint
	Uint8
	Uint16
	Uint32
	Uint64
	Uintptr
	Float32
	Float64
	Complex64
	Complex128
	Array
	Chan
	Func
	Interface
	Map
	Ptr
	Slice
	String
	Struct
	UnsafePointer    // 底层指针
)

2、ValueOf

refect.Value类型提供的获取原始值的方法如下:

66dc0e447439a9e6adc6f16e9e1678a8.png
package main

import (
	"fmt"
	"reflect"
)

func reflectValue(x interface{}) {
	v := reflect.ValueOf(x)
	k := v.Kind()
	switch k {
	case reflect.Int64:
		// v.Int()从反射中获取整型的原始值,然后通过int64()强制类型转换
		fmt.Printf("type is int64, value is %dn", int64(v.Int()))
	case reflect.Float32:
		fmt.Printf("type is float32, value is %fn", float32(v.Float()))
	case reflect.Float64:
		fmt.Printf("type is float64, value is %fn", float64(v.Float()))
	}
}

func main() {
	var a float32 = 3.14
	var b int64 = 100
	reflectValue(a)
	reflectValue(b)
	c := reflect.ValueOf(10)
	fmt.Printf("type c:%Tn", c)
}

3、通过反射设置变量的值

想要在函数中通过反射修改变量的值,需要注意函数参数传递是值拷贝,必须传递变量地址才能改变变量值,而反射中使用专有的Elem()方法获取指针对应的值。

package main

import (
	"fmt"
	"reflect"
)

func reflectSetValue(x interface{}) {
	v := reflect.ValueOf(x)
	if v.Elem().Kind() == reflect.Int64 {
		v.Elem().SetInt(200)
	}
}

func main() {
	var b int64 = 100
	reflectSetValue(&b)
	fmt.Println(b)
}

4、isNil()和isValid()

func (v Value) IsNil() bool

IsNil()报告v持有的值是否为nil,v持有的值的分类必须是通道,函数,接口,映射,指针,切片之一,否则函数isNil函数会导致panic。IsNil()常被用于判断指针为空。

func (v Value) IsValid() bool

IsValid()返回v是否持有一个值。如果v是Value零值会返回假,此时v除了IsValid,String,Kind之外的方法都会导致painc。IsValid()常被用于判定返回值是否有效。

package main

import (
	"fmt"
	"reflect"
)

func main() {
	// *int类型空指针
	var a *int
	fmt.Println("var a *int IsNil:", reflect.ValueOf(a).IsNil())
	// nil值
	fmt.Println("nil IsValid:", reflect.ValueOf(nil).IsValid())
	// 实例化一个匿名结构体
	b := struct{}{}
	// 尝试从结构体中查找"abc"字段
	fmt.Println("不存在的结构体成员:", reflect.ValueOf(b).FieldByName("abc").IsValid())
	fmt.Println("不存在的结构体方法:", reflect.ValueOf(b).MethodByName("abc").IsValid())
	// map
	c := map[string]int {}
	// 尝试从map中查找一个不存在的键
	fmt.Println("map中不存在的键:", reflect.ValueOf(c).MapIndex(reflect.ValueOf("娜扎")).IsValid())
}

5、结构体反射

与结构体相关方法,任意值通过reflect.TypeOf()获取反射对象信息后,如果它的类型是结构体,可以通过反射值对象(relect.Type)的NumFeild()和Field()方法获得结构体成员详细信息。reflect.Type中与结构体成员相关方法如下:

c9951d8fb987dffd81e23fa1b1aa4d43.png

StructField类型

用来描述结构体中的一个字段信息。StructField定义如下:

type StructField struct {
    // Name是字段的名字。PkgPath是非导出字段的包路径,对导出字段该字段为""。
    // 参见http://golang.org/ref/spec#Uniqueness_of_identifiers
    Name    string
    PkgPath string
    Type      Type      // 字段的类型
    Tag       StructTag // 字段的标签
    Offset    uintptr   // 字段在结构体中的字节偏移量
    Index     []int     // 用于Type.FieldByIndex时的索引切片
    Anonymous bool      // 是否匿名字段
}

举个例子:

package main

import (
	"fmt"
	"reflect"
)

type student struct {
	Name  string `json:"name"`
	Score int    `json:"age"`
}

func main() {
	stu1 := student{
		Name:  "小王子",
		Score: 90,
	}

	t := reflect.TypeOf(stu1)
	fmt.Println(t.Name(), t.Kind())
	// 通过for循环遍历结构体所有字段信息
	for i := 0; i < t.NumField(); i++ {
		field := t.Field(i)
		fmt.Printf("name:%s index:%d type:%v json tag:%vn", 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:%vn", scoreField.Name, scoreField.Index, scoreField.Type, scoreField.Tag.Get("json"))
	}
}

6、反射是一把双刃剑

反射是一个极其强大并富有表现力的工具,能够写出更灵活的代码,但是反射不应该滥用,原因如下:

a、基于反射的代码是极其脆弱的,反射中的类型错误会在真正运行的时候才会触发panic。

b、大量使用反射的代码通常难以理解

c、反射的性能地下,基于反射实现的代码通常比正常代码运行慢一到二个数量级。

我是尾巴~

每日一句毒鸡汤:只要思想不滑坡,方法总比困难多。饲养员竟然让母猪笑话了。尽量补齐自己的短板!

本次推荐:

Diffuse​diffuse.sourceforge.net

继续加油~!


版权声明:本文为weixin_29691667原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接和本声明。