Golang语言系列-10-包

自定义包

package _0calc

import (
	"fmt"
)

/*
[Go语言的包]
在工程化的Go语言开发项目中,Go语言的源码复用是建立在包(package)基础之上的
本文介绍了Go语言中如何定义包、如何导出包的内容及如何导入其他包

[包介绍]
包(package)是多个Go源码的集合,是一种高级的代码复用方案,Go语言为我们提供了很多内置包 如fmt、os、io等

[定义包]
我们还可以根据自己的需要创建自己的包,一个包可以简单理解为一个存放 .go 文件的文件夹
该文件夹下面的所有go文件都要在代码的第一行添加如下代码,声明该文件归属的包
package 包名
注意事项:
	一个文件夹下面直接包含的文件只能归属一个package,同样一个package的文件不能在多个文件夹下
	包名可以不和文件夹的名字一样,包名不能包含 - 符号
	包名为main的包为应用程序的入口包,这种包编译后会得到一个可执行文件,而编译不包含main包的源代码则不会得到可执行文件

[可见性]
如果想在一个包中引用另外一个包里的标识符(如变量、常量、类型、函数等)时,该标识符必须是对外可见的(public)
在Go语言中只需要将标识符的首字母大写就可以让标识符对外可见了

[包的导入]
要在代码中引用其他包的内容,需要使用import关键字导入使用的包。具体语法如下:
	import "包的路径"
注意事项:
	import导入语句通常放在文件开头包声明语句的下面
	导入的包名需要使用双引号包裹起来
	包名是从$GOPATH/src/后开始计算的,使用/进行路径分隔
	Go语言中禁止循环导入包
单行导入
	import "包1"
	import "包2"
多行导入
	import (
    "包1"
    "包2"
	)

[自定义包名]
在导入包名的时候,我们还可以为导入的包设置别名。通常用于导入的包名太长或者导入的包名冲突的情况。具体语法格式如下:
import 别名 "包的路径"
import "fmt"
import m "github.com/Q1mi/studygo/pkg_test"
func main() {
	fmt.Println(m.Add(100, 200))
	fmt.Println(m.Mode)
}

[匿名导入包]
如果只希望导入包,而不使用包内部的数据时,可以使用匿名导入包。具体的格式如下:
import _ "包的路径"
匿名导入的包与其他方式导入的包一样都会被编译到可执行文件中。

[init()初始化函数]
在Go语言程序执行时导入包语句会自动触发包内部init()函数的调用
需要注意的是:
	init()函数没有参数也没有返回值。
	init()函数在程序运行时自动被调用执行,不能在代码中主动调用它

init()函数执行顺序
Go语言包会从main包开始检查其导入的所有包,每个包中又可能导入了其他的包。
Go编译器由此构建出一个树状的包引用关系,再根据引用顺序决定编译顺序,依次编译这些包的代码
在运行时,被最后导入的包会最先初始化并调用其init()函数,如图导入包init执行顺序.png
*/

// init函数
func init() {
	fmt.Println("被导入的时候,我是自动执行的...")
}

// 包中的标识符(变量名\函数名\结构体\接口等)如果首字母是小写的,表示私有的(只能在当前这个包使用)
// 首字母大写的标识符可以被外部的包调用
func Add(x, y int) int {
	return x + y
}

自定义包的案例

目录结构

├── 10calc
│   └── calc.go
├── 11import_demo
│   ├── main.go

calc.go文件

package _0calc

import (
	"fmt"
)

// init函数
func init() {
	fmt.Println("被导入的时候,我是自动执行的...")
}

func Add(x, y int) int {
	return x + y
}

main.go文件

package main

import (
	zhoulin "gostudy/day05/10calc"
	"fmt"
)

var x = 100

const pi = 3.14

func init() {
	fmt.Println("自动执行!")
	fmt.Println(x, pi)
}

func main() {
	ret := zhoulin.Add(10, 20)
	fmt.Println(ret)
}

执行过程和结果

// 先编译包,也可以直接执行main.go文件(go run main.go)
go build

// 在执行被编译的文件
./11import_demo

// 执行结果
被导入的时候,我是自动执行的...
自动执行!
100 3.14
30

包中init函数执行的时机

fmt包

package main

import "fmt"

/*
fmt包主要分为 向外输出内容 和 获取输入内容 两大部分
向外输出内容:
	终端输出:Print系列 Print、Printf、Println
	文件输出:Fprint系列 Fprint、Fprintf、Fprintln函数会将内容输出到一个io.Writer接口类型的变量中,
	我们通常用这个函数往文件中写入内容

	Sprint系列函数会把传入的数据生成并返回一个字符串,拼接字符串
	Errorf函数根据format参数生成格式化字符串并返回一个包含该字符串的错误

获取输入内容
	Go语言fmt包下有fmt.Scan、fmt.Scanf、fmt.Scanln三个函数,可以在程序运行过程中从标准输入获取用户的输入
	fmt.Scan
		Scan从标准输入扫描文本,读取由空白符分隔的值保存到传递给本函数的参数中,换行符视为空白符。
	fmt.Scanf

	fmt.Scanln
		fmt.Scanln遇到回车就结束扫描了,这个比较常用

有时候我们想完整获取输入的内容,而输入的内容可能包含空格,这种情况下可以使用bufio包来实现
func bufioDemo() {
	reader := bufio.NewReader(os.Stdin) // 从标准输入生成读对象
	fmt.Print("请输入内容:")
	text, _ := reader.ReadString('\n') // 读到换行
	text = strings.TrimSpace(text)
	fmt.Printf("%#v\n", text)
}
*/

func main() {
	//fmt.Print("南山")
	//fmt.Print("alnk")
	//fmt.Println("------------")
	//fmt.Println("南山")
	//fmt.Println("alnk")
	//Printf("格式化字符串", 值)
	// %T :查看类型
	// %d :十进制数
	// %b :二进制数
	// %o :八进制数
	// %x :十六进制数
	// %c : 字符
	// %s :字符串
	// %p: 指针
	// %v: 值
	// %f:浮点数
	// %t :布尔值

	//var m1 = make(map[string]int, 1)
	//m1["lixiang"] = 100
	//fmt.Printf("%v\n", m1)  //map[lixiang:100]
	//fmt.Printf("%#v\n", m1) //map[string]int{"lixiang":100}

	//printBaifenbi(10)

	//fmt.Printf("%v\n", 100)
	////整数 -> 字符
	//fmt.Printf("%q\n", 65) // 'A'
	////浮点数 -> 复数
	//fmt.Printf("%b\n", 3.1415926)
	////字符串
	//fmt.Printf("%q\n", "理想有理想")
	//fmt.Printf("%7.3s\n", "abcdefghijk")

	//获取用户输入
	//var s string
	//fmt.Print("请输入内容:")
	//fmt.Scan(&s)
	//fmt.Println("用户输入的内容是:", s)

	//var (
	//	name  string
	//	age   int
	//	class string
	//)
	////fmt.Scanf("%s %d %s\n", &name, &age, &class) //获取用户输入,同一行
	////fmt.Println(name, age, class)
	////
	//fmt.Scanln(&name, &age, &class)
	//fmt.Println(name, age, class)

	//fmt.Printf("%b\n", 1024) //10000000000
}

func printBaifenbi(num int) {
	fmt.Printf("%d%%\n", num)
}

示例

package main

import (
	"bufio"
	"fmt"
	"os"
)

// 获取用户输入时如果有空格

// Scanln 如果有空格会报错
func useScan() {
	var s string
	fmt.Print("请输入内容:")
	fmt.Scanln(&s)
	fmt.Printf("你输入的内容是:%s\n", s)
}

// useBufio 能处理有空格的输入
func useBufio() {
	var s string
	reader := bufio.NewReader(os.Stdin)
	fmt.Printf("请输入内容:")
	s, _ = reader.ReadString('\n')
	fmt.Printf("你输入的内容是:%s", s)
}

// Fprintln 往终端输入、往文件输出
func useFprintln() {
	fmt.Fprintln(os.Stdout, "这是一条日志") //往终端屏幕写

	fileObj, _ := os.OpenFile("./test.log", os.O_CREATE|os.O_APPEND|os.O_WRONLY, 0644)
	defer fileObj.Close()

	fmt.Fprintln(fileObj, "这是一条日志记录!") //往文件写
}

func main() {
	//useScan()
	//useBufio()
	useFprintln()
}

文件读写操作包

读取文件

package main

import (
	"bufio"
	"fmt"
	"io"
	"io/ioutil"
	"os"
)

/*
[Go语言文件操作] -- 读取文件

文件是什么?
计算机中的文件是存储在外部介质(通常是磁盘)上的数据集合,文件分为文本文件和二进制文件

[打开和关闭文件]
os.Open()函数能够打开一个文件,返回一个*File和一个err。对得到的文件实例调用close()方法能够关闭文件

[读取文件的三种方法]
1.Read方法定义如下:
	func (f *File) Read(b []byte) (n int, err error)
    它接收一个字节切片,返回读取的字节数和可能的具体错误,读到文件末尾时会返回0和io.EOF

2.bufio读取文件

3.ioutil读取整个文件,如果文件很大,使用这种方法是否会导致内存飙升?
*/

// 1.第一种读取文件的方法 os.Open() file.Read()
func readFromFile1() {
	fileObj, err := os.Open("./main.go") //底层调用的其实就是OpenFile函数,只不过Open函数更简单
	//fileObj, err := os.OpenFile("./main.go", os.O_RDONLY, 0)
	if err != nil {
		fmt.Printf("open file failed, err:%v\n", err)
		return
	}

	// 记得关闭文件
	defer fileObj.Close()

	// 读文件
	//var tmp = make([]byte, 128) //指定读的长度 切片
	var tmp [128]byte // 数组

	for {
		n, err := fileObj.Read(tmp[:])
		if err == io.EOF {
			fmt.Println("读完了")
			return
		}
		if err != nil {
			fmt.Printf("read from file failed, err:%v\n", err)
			return
		}
		//fmt.Printf("读了%d个字节\n", n)
		fmt.Printf("%s", string(tmp[:n])) //为了保持文本输出的格式,这里建议用Printf

		if n < 128 {
			return
		}
	}
}

// 2.bufio
// bufio是在file的基础上封装了一层API,支持更多的功能
func readFromFilebyBufio() {
	// 1.打开文件
	fileObj, err := os.Open("./main.go")
	if err != nil {
		fmt.Printf("open file failed, err:%v\n", err)
		return
	}

	// 2.关闭文件
	defer fileObj.Close()

	// 3.创建一个用来从文件中读内容的对象
	reader := bufio.NewReader(fileObj)

	// 4.循环读取文件内容
	for {
		line, err := reader.ReadString('\n') //这里可能会产生bug,如果一行的结尾不是\n的话
		if err == io.EOF {
			//fmt.Println("文件读完了!!!")
			return
		}
		if err != nil {
			fmt.Printf("read line failed, err:%v\n", err)
			return
		}
		fmt.Print(line)
	}
}

// 3.ioutil读取整个文件
func readFromFileByIoutil() {
	ret, err := ioutil.ReadFile("./main.go")
	if err != nil {
		fmt.Printf("read file failed, err:%v\n", err)
		return
	}

	fmt.Println(string(ret))
}

func main() {
	//readFromFile1()
	//readFromFilebyBufio()
	readFromFileByIoutil()
}

写入文件

package main

import (
	"bufio"
	"fmt"
	"io/ioutil"
	"os"
)

/*
[文件写入操作]
os.OpenFile()函数能够以指定模式打开文件,从而实现文件写入相关功能
func OpenFile(name string, flag int, perm FileMode) (*File, error) {}
其中:
	name:要打开的文件名
	flag:打开文件的模式
	perm:权限

模式有以下几种:
	os.O_WRONLY	只写
	os.O_CREATE	创建文件
	os.O_RDONLY	只读
	os.O_RDWR	读写
	os.O_TRUNC	清空
	os.O_APPEND	追加

Write和WriteString
	Write:写入字节切片数据
	WriteString:直接写入字符串数据
*/

// 1.os.OpenFile() 打开文件写内容
func writeDemo1() {
	// 1.打开文件
	fileObj, err := os.OpenFile("./test1.txt", os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0644) //只写,如果文件不存在则创建,如果存在则清空
	if err != nil {
		fmt.Printf("open file failed, err:%v\n", err)
		return
	}
	// 2.关闭文件
	defer fileObj.Close()

	// 3.write 方法写入
	n, err := fileObj.Write([]byte("zhoulin mengbi le 嘛!\n"))
	if err != nil {
		fmt.Printf("write file failed, err:%v\n", err)
		return
	}
	fmt.Println("n:", n) //写入了多少个字节

	// 4.writeString 方法写入
	n, err = fileObj.WriteString("周林解释不了!\n")
	if err != nil {
		fmt.Printf("write file failed, err:%v\n", err)
		return
	}
	fmt.Println("n:", n) //写入了多少个字节
}

// 2.bufio.NewWriter() 写入文件
func writeDemo2() {
	// 1.打开文件
	fileObj, err := os.OpenFile("./test2.txt", os.O_WRONLY|os.O_CREATE|os.O_APPEND, 0644) //追加写入,不存在则创建
	if err != nil {
		fmt.Printf("open file failed, err:%v\n", err)
		return
	}

	// 2.关闭文件
	defer fileObj.Close() //延迟关闭文件

	// 3.写入文件
	wr := bufio.NewWriter(fileObj) //创建一个写的对象
	wr.WriteString("hello沙河\n")    //写到缓存中 WriteString
	wr.Write([]byte("难受啊\n"))      //写到缓存中 Write
	wr.Flush()                     //将缓存中的内容写入文件
}

// 3.ioutil.WriteFile 写入文件
func writeDemo3() {
	str := "hello沙河\n"
	err := ioutil.WriteFile("./test3.txt", []byte(str), 0644) //这种写入方式会清空被写入文件之前的数据
	if err != nil {
		fmt.Printf("write file failed, err:%v\n", err)
		return
	}
}

func main() {
	writeDemo1()
	writeDemo2()
	writeDemo3()
}

往文件中插入内容

package main

import (
	"fmt"
	"io"
	"os"
)

// 往文件中插入内容

func f2() {
	// 1.打开要操作的文件
	f, err := os.OpenFile("./test.txt", os.O_RDWR, 0644)
	if err != nil {
		fmt.Printf("open file failed, err:%v\n", err)
		return
	}
	defer f.Close()

	// 2.因为没有办法直接在文件中插入内容,所以要借助一个临时文件
	tmpFile, err := os.OpenFile("./test.tmp", os.O_CREATE|os.O_TRUNC|os.O_WRONLY, 0644)
	if err != nil {
		fmt.Printf("create tmp file failed, err:%v\n", err)
		return
	}
	defer tmpFile.Close()

	// 3.读取源文件部分内容写入临时文件
	var ret [2]byte //读取了2个字节
	n, err := f.Read(ret[:])
	if err != nil {
		fmt.Printf("read from file failed, err:%v\n", err)
		return
	}

	// 4.读取的部分源文件内容写入临时文件
	tmpFile.Write(ret[:n])

	// 5.写入要插入的内容到临时文件
	var s []byte
	s = []byte{'a', 'b', 'c'}
	tmpFile.Write(s)

	// 6.紧接着把源文件后续的所有内容写入临时文件
	var x [1024]byte
	for {
		n, err := f.Read(x[:])
		if err == io.EOF {
			tmpFile.Write(x[:n])
			break
		}
		if err != nil {
			fmt.Printf("read from file failed, err:%v\n", err)
			return
		}
		tmpFile.Write(x[:n])
	}

	// 7.把临时文件改名为源文件
	os.Rename("./test.tmp", "./test.txt")

}

func main() {
	f2()
}

获取文件基本信息

package main

import (
	"fmt"
	"os"
)

// 获取一个文件的基本信息 如文件大小、文件名称

// f1 获取文件详细信息,例如文件大小、名字等
func f1() {
	// 1.打开文件
	fileObj, err := os.Open("main.go")
	if err != nil {
		fmt.Printf("open file failed. err:%s\n", err)
		return
	}
	fmt.Printf("%T\n", fileObj) //文件对象的类型 *os.File指针

	// 2.获取文件对象的详细信息
	fileInfo, err := fileObj.Stat()
	if err != nil {
		fmt.Printf("get file info failed, err:%s\n", err)
		return
	}
	fmt.Printf("文件大小是:[%d]B\n", fileInfo.Size()) //文件大小是:[601]B
	fmt.Printf("文件名称是:[%s]\n", fileInfo.Name())  //文件名称是:[main.go] 只会获取文件名称,不会获取文件路径
}

func main() {
	f1()
}

案例

package main

import (
	"fmt"
	"io"
	"os"
)

// 借助io.copy() 实现一个拷贝文件函数

// CopyFile拷贝文件函数
func CopyFile(dstName, srcName string) (wrtten int64, err error) {
	// 以读方式打开
	src, err := os.Open(srcName)
	if err != nil {
		fmt.Printf("open %s failed, err:%v\n", srcName, err)
		return
	}
	defer src.Close()

	// 以写|创建的方式打开目标文件
	dst, err := os.OpenFile(dstName, os.O_WRONLY|os.O_CREATE, 0644) //没有os.O_APPEND 会清空之前的文件
	if err != nil {
		fmt.Printf("open %s failed, err:%v\n", dstName, err)
		return
	}
	defer dst.Close()

	return io.Copy(dst, src) //调用io.Copy()拷贝内容
}

func main() {
	_, err := CopyFile("./dst.txt", "./src.txt")
	if err != nil {
		fmt.Println("copy file failed, err:", err)
		return
	}

	fmt.Println("copy done.")
}

time包

package main

import (
	"fmt"
	"time"
)

// time时间包

func f1() {
	now := time.Now()         //获取当前时间
	fmt.Println(now)          //2020-08-19 14:37:25.136076 +0800 CST m=+0.000102652
	fmt.Println(now.Year())   //2020
	fmt.Println(now.Month())  //August
	fmt.Println(now.Day())    //19
	fmt.Println(now.Hour())   //14
	fmt.Println(now.Minute()) //39
	fmt.Println(now.Second()) //30
	fmt.Println(now.Date())   //日期 2020 August 19

	// 时间戳
	fmt.Println(now.Unix())     //1597819214 获取当前秒
	fmt.Println(now.UnixNano()) //1597819255919494000  纳秒时间戳

	// time.Unix() 将时间戳转为时间格式
	ret := time.Unix(1597819214, 0)
	fmt.Println(ret)                         //2020-08-19 14:40:14 +0800 CST
	ret1 := time.Unix(1597819214, 484199000) //精确到纳秒
	fmt.Println(ret1)                        //2020-08-19 14:40:14.484199 +0800 CST
	fmt.Println(ret1.Year())                 //2020
	fmt.Println(ret1.Month())                //August
	fmt.Println(ret1.Day())                  //19
	fmt.Println(ret1.Date())                 //日期 2020 August 19

	// 时间间隔
	fmt.Println(time.Second)      //1s
	fmt.Println(time.Second * 10) //10s

	// 定时器
	//timer := time.Tick(5 * time.Second) //间隔5s
	////timer := time.Tick(1 * time.Second) //间隔1s
	//for t := range timer {
	//	fmt.Println("定时器")
	//	fmt.Println(t) //1秒钟执行一次
	//}

	// 格式化时间:把语言中时间对象转换成字符串类型的时间
	// 时间类型有一个自带的方法Format进行格式化,需要注意的是Go语言中格式化时间模板不是常见的Y-m-d H:M:S
	// 而是使用Go的诞生时间2006年1月2号15点04分05秒(记忆口诀为2006 1 2 3 4 5)
	fmt.Println(now.Format("2006-01-02"))                 //2020-08-19
	fmt.Println(now.Format("2006/01/02 15:04:05"))        //2020/08/19 07:41:40
	fmt.Println(now.Format("2006/01/02 03:04:05 PM"))     //2020/08/19 07:43:07 AM
	fmt.Println(now.Format("2006/01/02 15:04:05.000"))    //2020/08/19 07:45:52.486
	fmt.Println(time.Now().Format("2006-01-02 15:04:05")) //2020-08-19 15:51:08
	fmt.Println(time.Now().Format("2006-01-02"))          //2020-08-19

	// 按照对应的格式解析字符串类型的时间
	timeObj, _ := time.Parse("2006-01-02", "2019-08-03")
	fmt.Println(timeObj)        //2019-08-03 00:00:00 +0000 UTC
	fmt.Println(timeObj.Unix()) //1564790400

	// sleep
	n := 5 //int
	fmt.Println("开始sleep了")
	time.Sleep(time.Duration(n) * time.Second)
	fmt.Println("5秒钟过去了")
	time.Sleep(5 * time.Second)
	fmt.Println("5秒又钟过去了...")
}

func main() {
	f1()
}

根据时区计算时间差

import (
	"fmt"
	"time"
)

// 根据时区计算时间差
func f2() {
	now := time.Now() //本地当前时间
	fmt.Println(now)  //2020-08-21 11:43:49.71391 +0800 CST m=+0.000093483
	
	// 按照东八区的时区和格式取解析一个字符串格式的时间
	// 根据字符串加载时区
	loc, err := time.LoadLocation("Asia/Shanghai")
	if err != nil {
		return
	}

	// 按照指定时区解析时间
	timeObj, err := time.ParseInLocation("2006-01-02 15:04:05", "2021-03-13 14:18:00", loc)
	if err != nil {
		fmt.Println("ERROR:", err)
		return
	}
	fmt.Println(timeObj)

	//时间对象相减
	td := now.Sub(timeObj)
	fmt.Println(td) //24h0m38.279607s
}

func main() {
	f2()
}

时间加减

// Add Sub 时间的加减
func f3() {
	// Add 时间相加
	now := time.Now() //获取当前时间
	fmt.Println(now)  //2020-08-21 10:31:43.955684 +0800 CST m=+0.000092436

	// 十分钟以前
	m, _ := time.ParseDuration("-10m")
	m1 := now.Add(m)
	fmt.Println(m1)
	
	// 8个小时以前
	h, _ := time.ParseDuration("-1h")
	h1 := now.Add(8 * h)
	fmt.Println(h1)
	
	// 一天以前
	d, _ := time.ParseDuration("-24h")
	d1 := now.Add(d)
	fmt.Println(d1)

	// 十分钟以后
	mm, _ := time.ParseDuration("10m")
	mm1 := now.Add(mm)
	fmt.Println(mm1)
	
	// 8个小时以后
	hh, _ := time.ParseDuration("1h")
	hh1 := now.Add(8 * hh)
	fmt.Println(hh1)
	
	// 一天以后
	dd, _ := time.ParseDuration("24h")
	dd1 := now.Add(dd)
	fmt.Println(dd1)

	// Sub 计算两个时间差
	subM := now.Sub(m1)
	fmt.Println(subM.Minutes()) //10分钟

	subH := now.Sub(h1)
	fmt.Println(subH) //8h0m0s

	subD := now.Sub(d1)
	fmt.Println(subD) //24h0m0s
}
// 计算两个string类型的时间差
func f4() {
	// 计算两个固定的string类型的时间差
	// 1.声明变量
	t1 := "2020-08-21 11:25:00"
	t2 := "2020-08-21 10:25:00"

	// 2.把string类型转化为time类型
	var baseTime = "2006-01-02 15:04:05"
	t1Time, _ := time.Parse(baseTime, t1)
	t2Time, _ := time.Parse(baseTime, t2)
	fmt.Println(t1Time) //2020-08-21 11:25:00 +0000 UTC
	fmt.Println(t2Time) //2020-08-21 10:25:00 +0000 UTC

	// 3.利用sub计算时间差
	sub1 := t1Time.Sub(t2Time)
	sub2 := t2Time.Sub(t1Time)
	fmt.Println(sub1)           //1h0m0s
	fmt.Println(sub2)           //-1h0m0s
	fmt.Println(sub1.Hours())   //1
	fmt.Println(sub1.Minutes()) //60
	fmt.Println(sub1.Seconds()) //3600

	// 计算某个时间(string类型)和当前时间的时间差
	// 注意要计算一个固定的时间串和本地时间间隔多少,解析时间字符串的时候需要把时区设置进去

	// 1.声明变量
	t3 := "2021-03-12 15:31:00"
	// 1.1设置时区
	loc, err := time.LoadLocation("Asia/Shanghai")
	if err != nil {
		return
	}
	// 1.2按照时区去解析时间
	t3Time, err := time.ParseInLocation("2006-01-02 15:04:05", t3, loc)
	if err != nil {
		return
	}
	fmt.Println(t3Time)

	fmt.Println(t3Time) //2020-09-08 18:03:00 +0000 UTC  //这里时区不对

	// 2.获取当前时间
	now := time.Now()
	fmt.Println(now) //2020-09-09 18:06:10.040535 +0800 CST m=+0.000185949

	// 3.相减
	sub3 := now.Sub(t3Time)

	// 4
	fmt.Println(sub3)           //24h0m24.746951s
	fmt.Println(sub3.Hours())   //24.006874153055556
	fmt.Println(sub3.Minutes()) //1440.4124491833334
	fmt.Println(sub3.Seconds()) //86424.746951
}

log包

package main

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

// 内置log包

func f1() {
	// 1.打开文件
	f, err := os.OpenFile("./test1.log", os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644) //追加写入
	if err != nil {
		fmt.Println("err:", err)
		return
	}

	// 2.延迟关闭文件
	defer f.Close()

	// 3.设置log输出位置,这里设置为文件,默认会输出到终端
	log.SetOutput(f) //往文件输出

	// 4.测试循环写入内容到日志文件
	for {
		log.Println("这是一条测试的日志f1") //不会打印到终端,会写入到文件
		time.Sleep(time.Second * 3)
	}
}

func f2() {
	log.Println("这是一条很普通的日志") //2020/09/10 10:16:02 这是一条很普通的日志

	v := "很普通的"
	log.Printf("这是一条%s日志。\n", v) //2020/09/10 10:16:02 这是一条很普通的日志。

	// Fatal系列函数会在写入日志信息后调用os.Exit(1)
	log.Fatalln("这是一条会触发fatal的日志") //2020/09/10 10:16:28 这是一条会触发fatal的日志

	// Panic系列函数会在写入日志信息后panic。
	log.Panic("这是一条会触发Panic的日志")
}

func main() {
	//f1()
	f2()
}

runtime包

package main

import (
	"fmt"
	"path"
	"runtime"
)

// runtime.Caller(): 获取调用runtime.Caller所在函数的一些信息

func f(skip int) {
	pc, file, line, ok := runtime.Caller(skip)
	if !ok {
		return
	}

	funcName := runtime.FuncForPC(pc).Name()
	fmt.Println(funcName) //函数名

	fmt.Println(file) //文件名 全路径

	fmt.Println(line) //行号

	fmt.Println(path.Base(file)) //文件名,不是全路径
}

func f1(skip int) {
	f(1)
}

func main() {
	f(0) //注意这里的参数,0表示f这个函数本身调用
	/*
		输出结果:
		main.f  //函数名 main包下的f函数
		/Users/lichengguo/go/src/code.oldboyedu.com/gostudy/day06/06runtime_demo/main.go //文件名 全路径
		12 //行号
		main.go //文件名,不是全路径
	*/

	f(1) //注意这里的参数 1表示函数main调用了f函数
	/*
		main.main
		/Users/lichengguo/go/src/code.oldboyedu.com/gostudy/day06/06runtime_demo/main.go
		41
		main.go
	*/

	f1(2) //2 表示嵌套了2层函数才调用到f函数
	/*
		main.f1
		/Users/lichengguo/go/src/code.oldboyedu.com/gostudy/day06/06runtime_demo/main.go
		28
		main.go
	*/
}

json序列化和反序列化

package main

import (
	"encoding/json"
	"fmt"
)

// str json 和结构体转换
// 序列化 反序列化

type person struct {
	Name string `json:"name"`
	Age  int    `json:"age"`
}

func main() {
	// 反序列化 str-->struct
	s1 := `{"name":"周林", "age":9000}`
	fmt.Printf("%T\n", s1) //string
	var p person
	json.Unmarshal([]byte(s1), &p)
	fmt.Printf("%#v\n", p) //main.person{Name:"周林", Age:9000}

	// 序列化 strcut --> str
	p1 := person{
		Name: "保德路",
		Age:  22,
	}
	strJson, _ := json.Marshal(&p1)
	fmt.Printf("%#v\n", string(strJson)) //"{\"name\":\"保德路\",\"age\":22}"
}

strconv包

package main

import (
	"fmt"
	"strconv"
)

/*
Go语言内置包之strconv
Go语言中strconv包实现了基本数据类型和其字符串表示的相互转换
strconv包实现了基本数据类型与其字符串表示的转换,主要有以下常用函数: Atoi()、Itoa()、parse系列、format系列、append系列
*/

func main() {
	// 从字符串中解析出对应的数据
	str := "10000"
	ret1, err := strconv.ParseInt(str, 10, 64)
	if err != nil {
		return
	}
	fmt.Printf("%#v %T\n", ret1, ret1) //10000 int64

	// Atoi 字符串转换成int
	retInt, _ := strconv.Atoi(str)
	fmt.Printf("%#v %T\n", retInt, retInt) //10000 int

	// 从字符串中解析出布尔值
	boolStr := "true"
	boolValue, _ := strconv.ParseBool(boolStr)
	fmt.Printf("%#v %T\n", boolValue, boolValue) //true bool

	// 从字符串解析出浮点数
	floatStr := "1.234"
	floatValue, _ := strconv.ParseFloat(floatStr, 64)
	fmt.Printf("%#v %T\n", floatValue, floatValue) //1.234 float64

	// 把数字转换成字符串类型
	i := 97
	ret2 := string(i)
	fmt.Println(ret2) //a
	ret3 := fmt.Sprintf("%d", i)
	fmt.Printf("%#v\n", ret3) //"97"
	ret4 := strconv.Itoa(i)
	fmt.Printf("%#v %T\n", ret4, ret4) //"97" string
}

练习

目录结构

├── go.mod
├── logs
│   └── log.txt // 日志文件
├── main.go // 主文件
└── mylogs // 包
    └── mylogs.go // 包文件

mylogs.go

package mylogs

/*
日志包
1.可以同时往终端和日志文件输入日志
2.日志分为5种级别[trace debug warning info error]

用法 ex:
//声明成全局变量,好让所有的函数都能调用
var logger = mylogs.NewLogger("info", "./logs/", "log.txt", true)
logger.Trace("这是一条Trace日志f1()")
logger.Debug("这是一条debug日志f1()...")
logger.Warning("warning日志f1()...")
logger.Info("info日志f1()")
logger.Error("错误日志f1()...")
*/

import (
	"fmt"
	"os"
)

// Logger 定义一个日志结构体
type Logger struct {
	Level    string //日志等级[trace debug warning info error]
	FilePath string //日志存放路径
	FileName string //日志存放文件名称
	Tag      bool   //true:往屏幕打印日志
}

// NewLogger Logger结构体构造函数
// tag参数 是否往终端输出日志
func NewLogger(level string, filepath, filename string, tag bool) Logger {
	return Logger{
		Level:    level,
		FilePath: filepath,
		FileName: filename,
		Tag:      tag,
	}
}

//方法
//Trace
func (l *Logger) Trace(logContent string) {
	l.writeLogFile("trace", logContent)
}

//Debug
func (l *Logger) Debug(logContent string) {
	l.writeLogFile("debug", logContent)
}

//Warning
func (l *Logger) Warning(logContent string) {
	l.writeLogFile("warning", logContent)
}

//Info
func (l *Logger) Info(logContent string) {
	l.writeLogFile("info", logContent)
}

//Error
func (l *Logger) Error(logContent string) {
	l.writeLogFile("error", logContent)
}

//写入日志方法
func (l *Logger) writeLogFile(lv string, logContent string) {
	lvl, _ := parseLogLevel(lv)
	level, ok := parseLogLevel(l.Level)

	if !ok {
		fmt.Println("日志配置的等级不正确")
		os.Exit(1)
	}

	if level <= lvl {
		l.writeFile(lv, logContent)
	}
}

//把日志内容写入到文件方法
func (l *Logger) writeFile(lv string, logContent string) {
	filePath := l.FilePath + l.FileName                 //拼接文件日志路径和日志名称
	logContent = fmt.Sprintf("[%s] %s", lv, logContent) //拼接日志内容

	if l.Tag { //往屏幕输出日志内容
		fmt.Println(logContent)
	}

	fileObj, err := os.OpenFile(filePath, os.O_CREATE|os.O_APPEND|os.O_WRONLY, 0644)

	if err != nil {
		fmt.Println("log file not found")
		os.Exit(1)
	}

	defer fileObj.Close()

	fmt.Fprintln(fileObj, logContent)

}

//解析日志等级函数
func parseLogLevel(str1 string) (int, bool) {
	switch str1 {
	case "trace":
		return 1, true
	case "debug":
		return 2, true
	case "warning":
		return 3, true
	case "info":
		return 4, true
	case "error":
		return 5, true
	default:
		return 0, false
	}
}

main.go文件

package main

import (
	"mylogs"
	"time"
)

// 声明成全局变量,好让所有的函数都能调用
var logger = mylogs.NewLogger("info", "./logs/", "log.txt", true)

func f1() {
	logger.Trace("这是一条Trace日志f1()")
	logger.Debug("这是一条debug日志f1()...")
	logger.Warning("warning日志f1()...")
	logger.Info("info日志f1()")
	logger.Error("错误日志f1()...")
}

func f2() {
	logger.Trace("这是一条Trace日志f2()")
	logger.Debug("这是一条debug日志f2()...")
	logger.Warning("warning日志f2()...")
	logger.Info("info日志f2()")
	logger.Error("错误日志f2()...")
}

func main() {
	for {
		f1()
		f2()
		time.Sleep(2 * time.Second)
	}
}
posted @ 2021-03-12 16:55  李成果  阅读(81)  评论(0编辑  收藏  举报