0、flag 库
0.1 os.Args
func fn(){
// 这里关键是要 知道 os.Args 会把参数变成一个个字符串,然后是个切片
if len(os.Args)>0 {
for index, arg := range os.Args {
fmt.Printf("args[%d]=%v \n", index,arg)
}
}
}
0.2 flag 参数类型
flag包支持几乎所有的go基础数据类型
0.3 flag参数的定义方法
如何定义命令行flag 参数?
方法一:
func fn(){
// 返回的都是 指针
name:=flag.String("name","unknown","人名")
age := flag.Int("age",0, "age")
// 这个 parse() 不能少,容易漏掉
flag.Parse()
fmt.Println(*name,*age)
}
方法二:
func fn(){
var name string
var age int
flag.StringVar(&name, "name","unknown","名字")
flag.IntVar(&age,"age",0,"年纪")
flag.Parse()
fmt.Println(name,age)
}
0.4 flag.parse
在定义好命令行的flag参数后,还需要flag.parse()
来解析命令行参数。flag库支持常见的的参数格式:
-flag xxx
-- flag xxx
flag xxx
-flag=xxx
一、time标准库
1.1 时间类型
采用公历时间.
先要获取时间对象,然后获取年月日 时分秒
v := time.Now()
v.Year()
//...
v.Second()
1.2 时间戳
由时间获取时间戳:
t:=time.Now()
s := t.Unix() // unix 时间戳
fmt.Println(s)
由时间戳获取时间:
u:=time.Unix(s,0)
fmt.Println(u)
1.3 时间间隔
time.Duration是time包定义的一个类型,它代表两个时间点之间经过的时间间隔,以纳秒为单位。
fmt.Println(time.Second) // 1s
fmt.Println(time.Minute) // 1 min
1.4 时间操作
- add
- sub
- equal
- before
- after
1.5 定时器
定时器本质是个channel
。可以实现定期做事。倒是有点像java的Timer
。
t:= time.Tick(time.Second)
for i := range t {
fmt.Println(i)
}
1.6 时间格式化
这个很有意思,一般的语言都是用时间模板如:yyyy-mm-dd ,但是go中的语言模板是使用 go 诞生的那个时间点 2006 1 2 3 4
。技术大佬的“皮” (浪漫)是别具一格的
举个栗子:
func fn(){
t:=time.Now()
fmt.Println(t.Format("2006-01-02 15:04:05"))
//2021-01-30 20:22:16
}
1.7 字符串–> 时间
t := time.Now()
fmt.Println(t)
// 加入时区
loc, err := time.LoadLocation("Asia/Shanghai")
if err != nil {
fmt.Println(err)
return
}
parsed, err := time.ParseInLocation("2006/01/02 15:04:05", "2010/08/04 01:00:00", loc)
if err !=nil {
fmt.Println(err)
return
}
fmt.Println(parsed)
fmt.Println(parsed.Sub(t))
1.8 examples
需求:编写程序统计一段代码的执行耗时时间,单位精确到微秒。
1、第一个场景
func fn(){
f:=func () {
time.Sleep(time.Second)
}
d:=calculateDuration(f)
fmt.Println("duration=", d)
}
func calculateDuration(f func()) int64{
t:=time.Now().Unix()
f()
d:= time.Now().Unix() - t
return d
}
2、第二种场景
// f 是一个匿名函数
f:=func (a int,b int) int {
fmt.Println("......")
time.Sleep(time.Second * 2)
return a+b
}
//不能直接把 f 作为参数传给 calculateDuration;
// 所以做了一个适配。这个地方比较绕,要仔细看看
fn := func (foo func(a,b int) int, a int, b int) func() {
return func() {
foo(a,b)
}
}
d := calculateDuration(fn(f,1,2))
fmt.Println("duration=", d)
}
func calculateDuration(f func()) int64{
t:=time.Now().Unix()
f()
d:= time.Now().Unix() - t
return d
}
二、fmt标准库
2.1 输入
2.1.1 print
太基础了,不说了
2.1.2 Fprint
将内容输出到一个io.writer
接口类型的变量中。一般用来向文件中写入内容。
func fn3(){
// 打开一个文件句柄
file,ex := os.OpenFile("./test.txt", os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0644)
fmt.Printf("%T \n",file)
if ex !=nil {
fmt.Println(ex)
return
}
// 向打开的文件句柄中写入内容
var s = "this is a test"
fmt.Fprint(file,s)
}
2.1.3 Sprint
Sprint系列函数会把传入的数据生成并返回一个字符串。示例略
2.1.4 Errorf
Errorf函数根据format参数生成格式化字符串并返回一个包含该字符串的错误。
2.1.5 格式化占位符
最常用也就这几个:
- %T --> 打印变量类型
- %v --> 值的默认格式表示
- %s --> 字符串占位,或者 []byte 占位
- %t --> 布尔型占位
- %#v --> 值的Go语法表示
- %c --> Unicode 码值表示(其实就是 字符)
- %b --> 二进制
- %o --> 八进制
- %ox --> 十六进制
- 整型 、浮点数、复数,等等 N多,
%v
一般也就够,不需要记那么多。
2.2 输出
2.2.1 fmt.Scan
示例略,见api doc即可
2.2.2 fmt.Scanf
示例略,见api doc即可
2.2.3 fmt.Scanln
示例略,见api doc即可
三、go 操作文件
3.1 打开、读取文件
举个栗子,循环读取文件内容,最终输出:
func fn(){
f,e:=os.Open("./main.go")
if e != nil {
fmt.Println("failed to open file",e)
return
}
defer f.Close()
var buffer = make([]byte, 128)
var content = make([]byte,0)
for{
// 读取的时候后一次会不断覆盖前一次,
// 所以没有必要每次都 新make一个 buffer
num,err:=f.Read(buffer)
if err == io.EOF {
fmt.Println("read done")
break
}
if err !=nil {
fmt.Println("failed to read..")
return
}
content=append(content, buffer[:num]...)
}
fmt.Println(string(content))
}
3.2 包装类 bufio
bufio
是在file
基础上又封装了一层,功能更加强大
func fn(){
f,e:=os.Open("./main.go")
if e !=nil {
fmt.Println("failed to read",e)
return
}
defer f.Close()
r:=bufio.NewReader(f)
for{
//按行去读。注意这里的 '\n' 是单引号,表示字符, ‘\n’ 是换行符
line,ex := r.ReadString('\n')
if ex == io.EOF {
// 这个地方 不能少...读下 doc 就知道,假如出现了 异常,
// readString会在返回 error前先返回数据
if len(line) !=0 {
fmt.Println("line:", line)
}
fmt.Println("read done")
break
}
if ex != nil {
fmt.Println("failed to read:", ex)
return
}
fmt.Print(line)
}
}
3.3 ioutil
ioutil
包提供了更加完备的API,比如:
ioutil.ReadFile()
能够读取整个文件
3.4 文件写入操作
3.4.1 Write和WriteString
func fn(){
// file 可创建、清空、写入
file,err:= os.OpenFile("./test.txt",os.O_CREATE|os.O_TRUNC|os.O_WRONLY,0666)
if err!=nil {
fmt.Print("failed to open file", err)
}
defer file.Close()
file.Write([]byte("i love cn")) //写入字节切片
file.WriteString("i hate us") //写入字符串
content,ex := ioutil.ReadFile("./test.txt")
if ex != nil {
fmt.Print("failed to open file for reading", ex)
return
}
fmt.Println(string(content))
}
3.4.2 bufio.NewWriter
func fn(){
file,err:= os.OpenFile("./test.txt",os.O_CREATE|os.O_TRUNC|os.O_WRONLY,0666)
if err!=nil {
fmt.Print("failed to open file", err)
}
defer file.Close()
w:=bufio.NewWriter(file)
// 虽然有返回值,但是没有取
w.WriteString("i love this world") // 本质是将 数据 写入缓冲,而不是直接刷盘
w.Flush() // 真正刷盘
}
3.4.3 ioutil.WriteFile
同样的,ioutil
提供了可直接写入文件的工具类,还是很方便的
3.4.4 examples
1、借助io.Copy()实现一个拷贝文件函数。
// 实现一个copy
func copy(from string, to string) (int,error) {
f,e:=os.OpenFile(from,os.O_RDONLY, 0666)
if e !=nil {
fmt.Println("failed to open 'from' file", e)
return 0,e
}
defer f.Close()
t, ex := os.OpenFile(to,os.O_CREATE|os.O_TRUNC|os.O_WRONLY, 0666)
if e!=nil {
fmt.Println("failed to open 'to' file",ex)
return 0,ex
}
written, err:=io.Copy(t,f)
defer t.Close()
return int(written), err
}
2、读写同一个文件要注意指针位置
func bar(){
f,e:=os.OpenFile("./conf.ini",os.O_RDWR|os.O_APPEND, 0777)
if e!=nil {
fmt.Println("Failed to open file", e)
return
}
_,ex:=f.WriteString("this is a test")
if ex!=nil {
fmt.Println("Failed to write in",e)
return
}
var content = make([]byte,10)
i,err:=f.Read(content)
if err !=nil {
// 文件已经移到了最后,所以,实际上是读不到内容的,这个地方要注意。
// 应该首先判断 EOF 【go 中的 EOF 是个 error !】
fmt.Println("Failed to read",err)
return
}
fmt.Println(i)
fmt.Println(string(content))
}
四、strconv 包
这个包用来处理 字符串和数字之间的转换。API太多了,不一一写了,主要记几个特别常用的;前面三个已经能解决大部分问题:
strconv.Atoi
strconv.Itoa
strconv.ParseBool
strconv.ParseInt
strconv.ParseFloat
strconv.ParseUint
strconv.FormatBool
strconv.FormatInt
- …
这里的函数命名是沿袭了C语言,所谓的 Atoi ,其实就是 alphabet to integer ,字面翻译就是 字符串 --> 数字。这种命名,真是程序员的浪漫。
五、Log库
5.1 logger
logger提供了格式化输出的方法:
- Fatal
- Panic
可以直接通过类似log.Print
来调用。这里有个有意思的地方:Fatal系列函数会在写入日志信息后调用os.Exit(1)。Panic系列函数会在写入日志信息后panic。换言之,日志行为实际上也影响了代码执行流程,不是单纯的一个记录操作。
5.2 配置logger
1、要查看和配置logger,可使用 Flags() setFlags()
函数。比如:
log.SetFlags(log.Llongfile | log.Lmicroseconds | log.Ldate)
其中:log.Llongfile --> 这是记录代码文件的目录
2、配置日志前缀:
log.SetPrefix("[little boy]")
3、配置日志输出位置
log.SetOutput(f)
来一个简单demo:
func main() {
f, e := os.OpenFile("./log.txt", os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0777)
if e != nil {
fmt.Println("create log file error", e)
return
}
log.SetFlags(log.Llongfile | log.Lmicroseconds | log.Ldate)
log.SetPrefix("[little boy]")
log.SetOutput(f)
for {
log.Println("hello")
time.Sleep(time.Second)
// 如果这里是 Fatal() ,则有可能日志输出不出来,
// Fatal 会导致进程退出,日志来不及刷盘(?)
log.Println("fatal error!")
}
}
六、自己实现一个日志库
自己搞一个日志库,要求支持:
1、往不同地方输出
2、支持日志级别
3、支持日志开关
4、输出时间、行号、文件名、级别、日志内容
5、支持切割