1、命令行参数包 flag
flag 包就是一个用来解析命令行参数的工具。
1.1、os.Args
import (
"fmt"
"os"
)
func main() {
if len(os.Args) > 0 {
for index, arg := range os.Args {
fmt.Printf("args[%d]=%v\n", index, arg)
}
}
}
运行结果:
osArgs 的本质是一个字符串切片,它的第一个索引 0 存储的是可执行文件的名称,之后的参数才是用户输入的参数。
1.2、flag 包的基本使用
flag 支持的的命令行参数主要有:数值类型、字符串和时间间隔(time.Duration)等。
- 对于 duration 类型,合法的单位有"ns"、“us” 、“µs”、“ms”、“s”、“m”、“h”。用的时候需要带上单位,比如 1h30m。
1.2.1、命令行参数的定义
命令行参数的定义有两种方式:一种是不带初始值的(flag.Type),一种是带初始值的(flag.TypeVar)。
flag.Type(flag名, 默认值, 帮助信息)
name := flag.String("name", "张三", "姓名")
age := flag.Int("age", 18, "年龄")
married := flag.Bool("married", false, "婚否")
delay := flag.Duration("delay", 0, "时间间隔")
通过查看源码我们可以发现,使用这种方式返回的是一个指针,而不是值:
所以在读取的时候需要使用 * 来取出指针的值。
flag.TypeVar(Type指针, flag名, 默认值, 帮助信息)
var name string
var age int
var married bool
var delay time.Duration
flag.StringVar(&name, "name", "张三", "姓名")
flag.IntVar(&age, "age", 18, "年龄")
flag.BoolVar(&married, "married", false, "婚否")
flag.DurationVar(&delay, "d", 0, "时间间隔")
1.2.2、解析命令行参数 flag.Parse()
定义好命令行参数之后,需要显式调用解析命令行参数方法(flag.Parse())才能生效,不然读取不到参数。
flag 支持的命令行参数格式有以下几种:
- -flag xxx (使用空格,一个-符号)
- --flag xxx (使用空格,两个-符号)
- -flag=xxx (使用等号,一个-符号)
- --flag=xxx (使用等号,两个-符号)
对于布尔类型的参数一般用等号来传递,不然解析不到布尔值之后的参数,并且会把布尔值及其之后的参数当做其它参数。
使用 go run ./flag.go 执行或者 go build 编译 go 文件再执行:
go build ./flag.go
1.2.3、其它参数
除了我们定义的参数之外,还可以有其它参数,但是必须跟在我们定义的最后一个参数后面,此外 flag 提供了一些方法来获取其它参数的属性:
//返回命令行参数后的其他参数
fmt.Println(flag.Args())
//返回命令行参数后的其他参数个数
fmt.Println(flag.NArg())
//返回使用的命令行参数个数
fmt.Println(flag.NFlag())
1.3、测试
package main
import (
"flag"
"fmt"
"time"
)
func main() {
var age int
var married bool
var delay time.Duration
name := flag.String("name", "张三", "姓名")
flag.IntVar(&age, "age", 18, "年龄")
flag.BoolVar(&married, "married", false, "婚否")
flag.DurationVar(&delay, "delay", 0, "延迟的时间间隔")
//解析命令行参数
flag.Parse()
fmt.Println(*name, age, married, delay)
//返回命令行参数后的其他参数
fmt.Println(flag.Args())
//返回命令行参数后的其他参数个数
fmt.Println(flag.NArg())
//返回使用的命令行参数个数
fmt.Println(flag.NFlag())
}
测试1(自定义参数 + 其它参数):
测试2(全为其它参数):
2、时间包 time
Go 语言中使用time.Time类型表示时间。我们可以通过time.Now函数获取当前的时间对象,然后从时间对象中可以获取到年、月、日、时、分、秒等信息。
func main() {
now := time.Now()
// 2024-05-06 19:44:56.6410767 +0800 CST m=+0.004404001
fmt.Println(now)
// 现在是2024年5月6日19时46分38秒
fmt.Printf("现在是%d年%d月%d日%d时%d分%d秒", now.Year(), now.Month(), now.Day(), now.Hour(), now.Minute(), now.Second())
}
2.1、Location和time zone
这个还是比较实用的,因为 time.LoadLocation 依赖系统的时区数据库,在不太确定程序运行环境的情况下建议先自定义时区偏移量(比如北京时间就是东八区时间,需要在UTC基础上+8个小时)然后使用time.FixedZone的方式指定时区。
// 时差(单位:s)
diffSeconds := int((8 * time.Hour).Seconds()) // float 转 int
// 北京时间
beijing := time.FixedZone("Beijing Time", diffSeconds) // 返回 *Location
2.2、Unix Time
Unix Time是自1970年1月1日 00:00:00 UTC 至当前时间经过的总秒数,我们可以通过 time 提供的方法获得当前的 Unix 秒/毫秒数(微秒、纳秒用不上):
func main() {
now := time.Now()
// 都是返回 int64 类型的整数
timestamp := now.Unix()
millisecond := now.UnixMilli()
// 1714996867s,1714996867336ms
fmt.Printf("%ds,%dms", timestamp, millisecond)
}
我们也可以把秒/毫秒数(int64)转为时间:
func main() {
now := time.Now()
// 都是返回 int64 类型的整数
second := now.Unix()
// 第2个参数为不足1秒的纳秒数
timeValue := time.Unix(int64(second), 22)
fmt.Println(timeValue) // 2024-05-06 20:01:07.000000022 +0800 CST
}
2.3、时间间隔
time 包中定义的时间间隔类型的常量如下:
const (
Nanosecond Duration = 1
Microsecond = 1000 * Nanosecond
Millisecond = 1000 * Microsecond
Second = 1000 * Millisecond
Minute = 60 * Second
Hour = 60 * Minute
)
用的时候直接用常量 * 单位即可,下面是 time.Duration 常用的一些方法:
func main() {
now := time.Now()
// 这里 Add 方法的参数为 time.Duration 类型
later := now.Add(3 * time.Hour)
// 三个小时以后是: 2024-05-06 23:07:28.3043137 +0800 CST m=+10800.004676401
fmt.Println("三个小时以后是: ", later)
// Sub 方法的返回值为 time.Duration 类型
fmt.Println(now.Sub(later)) // -3h0m0s
// now 是否在 later 之前
fmt.Println(now.Before(later)) // true
// now 是否在 later 之后
fmt.Println(now.After(later)) // false
// 加载东京所在的时区
tokyo, _ := time.LoadLocation("Asia/Tokyo")
// 加载上海所在的时区
shanghai, _ := time.LoadLocation("Asia/Shanghai")
tk := time.Date(now.Year(), now.Month(), now.Day(), now.Hour(), now.Minute(), now.Second(), now.Nanosecond(), tokyo)
sh := time.Date(now.Year(), now.Month(), now.Day(), now.Hour(), now.Minute(), now.Second(), now.Nanosecond(), shanghai)
// 判断两个时间是否相同,会考虑时区的影响
fmt.Println(now.Equal(tk)) // false
fmt.Println(now.Equal(sh)) // true
}
2.4、时间格式化
注意:Go 语言诞生于2006 年 1 月 2 日下午15 点 4 分 5 秒,它的时间格式化模板用的也正是这个时间!
func main() {
now := time.Now()
// 格式化模板:2006-01-02 15:04:05.000
fmt.Println(now.Format("2006-01-02 15:04:05")) // 2024-05-06 20:24:57
}
2.5、解析时间字符串
func main() {
now := time.Now()
// 格式化模板:2006-01-02 15:04:05.000
// 两个参数的长度必须对应上
t1, _ := time.Parse("2006-01-02 15:04:05", now.String()[0:19])
fmt.Println(t1) // 2024-05-06 20:34:59 +0000 UTC
}
在解析时,可额外指定时区信息:
func main() {
now := time.Now()
sh, _ := time.LoadLocation("Asia/Shanghai")
// 格式化模板:2006-01-02 15:04:05.000
// 按照指定时区和指定格式解析字符串时间
t1, _ := time.ParseInLocation("2006-01-02 15:04:05", now.String()[0:19], sh)
fmt.Println(t1) // 2024-05-06 20:40:24 +0800 CST
}
3、strconv
Go 语言中 strconv 包实现了基本数据类型和其字符串表示的相互转换,主要有以下常用函数: Atoi()、Itoa()、parse系列、format系列、append系列。
3.1、string 转 int(Atoi)
为什么是 Atoi 而不是 Atos 呢?这是因为C语言中没有string类型而是用字符数组(array)表示字符串。
func main() {
str := "100"
num, _ := strconv.Atoi(str)
fmt.Printf("%T,%v", num, num) // int,100
}
3.2、int 转 string(Itoa)
func main() {
num := 100
str := strconv.Itoa(num)
fmt.Printf("%T,%v", str, str) // string,100
}
3.3、Parse 系列
Parse类函数用于转换字符串为给定类型的值:ParseBool()、ParseFloat()、ParseInt()、ParseUint()。
b, _ := strconv.ParseBool("true")
f, _ := strconv.ParseFloat("3.1415", 64)
i, _ := strconv.ParseInt("-2", 10, 64)
u, _ := strconv.ParseUint("2", 10, 64)
3.4、Format 系列
Format系列函数实现了将给定类型数据格式化为string类型数据的功能。
s1 := strconv.FormatBool(true)
s2 := strconv.FormatFloat(3.1415, 'E', -1, 64)
s3 := strconv.FormatInt(-2, 16)
s4 := strconv.FormatUint(2, 16)
这里需要特别说明的是:
func FormatFloat(f float64, fmt byte, prec, bitSize int) string
-
fmt 表示格式:‘f’(-ddd.dddd)、‘b’(-ddddp±ddd,指数为二进制)、’e’(-d.dddde±dd,十进制指数)、‘E’(-d.ddddE±dd,十进制指数)、‘g’(指数很大时用’e’格式,否则’f’格式)、‘G’(指数很大时用’E’格式,否则’f’格式)。
-
prec 控制精度(排除指数部分):对’f’、’e’、‘E’,它表示小数点后的数字个数;对’g’、‘G’,它控制总的数字个数。如果prec 为-1,则代表使用最少数量的、但又必需的数字来表示f。
3.5、其它方法
func CanBackquote(s string) bool
表示返回字符串s是否可以不被修改的表示为一个单行的、没有空格和tab之外控制字符的反引号字符串。
4、文件操作
计算机中的文件是存储在外部介质(通常是磁盘)上的数据集合,文件分为文本文件和二进制文件(音频、视频)等。
4.1、文件的打开与关闭
// 返回文件对象指针和error对象
file, _ := os.Open("./main.go")
defer file.Close()
4.2、普通方式读取文件
读取文件的方法源码:
func (f *File) Read(b []byte) (n int, err error)
其中,b 是一个用于存放文件读取进来的字节,相当于是一个缓冲区,可以重复使用。
func main() {
file, _ := os.Open("./main.go")
defer file.Close()
// 开辟一个大小为128的字节切片用来存储文件
tmp := make([]byte, 128)
n, err := file.Read(tmp)
if err == io.EOF {
fmt.Println("读取完毕")
return
}
if err != nil {
fmt.Println("读取失败")
return
}
fmt.Printf("读取了%d字节的数据\n", n)
fmt.Println(string(tmp[:n]))
}
这种方式读取的缺点是我们并不能知道文件的大小,如果定义的缓冲区太大就浪费资源了,但是定义小了又读取不完整(因为只能读一次,不能循环利用),所以我们更多的是使用下面这种循环读取的方式:
func main() {
file, _ := os.Open("./main.go")
defer file.Close()
var content []byte
// 开辟一个大小为128的字节切片用来存储文件
tmp := make([]byte, 128)
for {
n, err := file.Read(tmp)
if err == io.EOF {
fmt.Println("读取完毕")
break
}
if err != nil {
fmt.Println("读取失败")
return
}
content = append(content, tmp[:n]...)
}
fmt.Println(string(content))
}
这种方法每次都会从文件读取 128 字节数据,Read 方法的返回值是一个切片,所以需要使用 append 函数来汇总到 content 切片当中;从下一次读取,又会把上一次的结果覆盖掉,
4.3、使用 bufio 读取文件
bufio 是在 file 的基础上封装了一层API,支持更多的功能:
func main() {
file, _ := os.Open("./main.go")
defer file.Close()
scanner := bufio.NewScanner(file)
for scanner.Scan() {
fmt.Println(scanner.Text())
}
if err := scanner.Err(); err != nil {
fmt.Println("读取失败", err)
}
}
4.4、使用 os.ReadFile 读取整个文件
os包(go1.16之前是 io.ioutil )的ReadFile方法能够读取完整的文件,只需要将文件名作为参数传入:
func main() {
content, err := os.ReadFile("./main.go")
if err != nil {
fmt.Println("读取失败")
return
}
fmt.Println(string(content))
}
4.5、文件写入操作
os.OpenFile()函数能够以指定模式打开文件,从而实现文件写入相关功能:
func OpenFile(name string, flag int, perm FileMode) (*File, error) {
...
}
其中 name:要打开的文件名 flag:打开文件的模式。 模式有以下几种:
模式 | 含义 |
---|---|
os.O_WRONLY | 只写 |
os.O_CREATE | 创建文件 |
os.O_RDONLY | 只读 |
os.O_RDWR | 读写 |
os.O_TRUNC | 清空 |
os.O_APPEND | 追加 |
4.5.1、Write 和 WriteString
func main() {
// 0666是文件的权限设置,表示文件所有者、所属组和其他用户都有读写权限
file, err := os.OpenFile("./test.txt", os.O_CREATE|os.O_TRUNC|os.O_WRONLY, 0666)
if err != nil {
fmt.Println("操作文件失败", err)
return
}
defer file.Close()
str := "Hello let's Go\n"
file.Write([]byte(str))
file.WriteString("Hello Big Data")
}
运行结果:
4.5.2、bufio.NewWriter
效果都是一样的,只不过是不同包下的方法:
func main() {
// 0666是文件的权限设置,表示文件所有者、所属组和其他用户都有读写权限
file, err := os.OpenFile("./test.txt", os.O_CREATE|os.O_TRUNC|os.O_WRONLY, 0666)
if err != nil {
fmt.Println("操作文件失败", err)
return
}
defer file.Close()
writer := bufio.NewWriter(file)
for i := 0; i < 10; i++ {
writer.WriteString("Hello let's Go\n") //将数据先写入缓存
}
writer.Flush() // 将缓存中的内容写入文件
}
4.5.3、os.WriteFile
os 包(go1.16之前是 io.ioutil )下的 WriteFile 可以一次将一个byte切片内的数据全部写入文件当中。只不过不能指定写入模式,比如追加等。
func main() {
str:="Hello let's Go"
err := os.WriteFile("./test.txt", []byte(str), 0666)
if err != nil {
fmt.Println("写入失败")
return
}
}