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

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包中自定义的时间间隔常量如下:

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类型提供的获取原始值的方法如下:

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中与结构体成员相关方法如下:

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、反射的性能地下,基于反射实现的代码通常比正常代码运行慢一到二个数量级。
我是尾巴~
每日一句毒鸡汤:只要思想不滑坡,方法总比困难多。饲养员竟然让母猪笑话了。尽量补齐自己的短板!
本次推荐:
Diffusediffuse.sourceforge.net继续加油~!