Go全栈第10节:Go常用包

2023/01/11

1、包的声明与导入

go语言中包的本质:创建不同的文件夹,以功能模块来存放程序文件

go语言的源码复用建立在包基础之上

  • main包

    Go语言的入口main函数所在的包必须是main包,其他包不能使用

  • package

    src目录是以代码包的形式组织并保存go源码文件的,每个代码包和src目录下的文件夹一一对应,每个子目录都是一个代码包。

    Go语言中代码包与文件目录,不要求一致,比如文件目录叫hello,但是代码包的包名可以声明为main

    同一目录下的所有go文件的第一行添加包定义,以标记该文件归属的包

    package 包名
    

    关于包的使用

    1. package 声明的包和对应的目录名可以不一致,但习惯上还是写成一致的
    2. 一个目录下的所有go文件的package必须同名
    3. 同包下的函数不要导入包,可以直接使用,函数名要大写
    4. main包,main函数所在的包必须是main包,其他包不能使用
    5. 对于包外而言,导入包的时候可以使用”包名.函数名”的方式使用包内函数,如fmt.Println,函数名必须要大写首字母

main函数外面Run,编译执行单个go文件

main函数里面Run,编译执行整个项目,一个项目下只能有一个main函数入口

1、导入其他包的标准写法,需要goworks下面的src目录开始导

import "studygo/service/user"

2、批量导入包

import {
  // 系统包
  "fmt"
  
  // 自定义的包
  "studygo/service"
  "studygo/service/user"
  
  // 从网上下载的包
}

3、相对路径导入包

import "../pojo"

4、包名冲突的解决方式,起别名

/* 包的重命名可以用于解决包名冲突,包名过长,避免与变量或常量名称冲突等情况*/
import "crypto/rand" // 默认模式,访问 rand.Function ,跟fmt一样
import R "crypto/rand" // 包的别名,通过别名访问该包下的函数等,访问 R.function
import . "crypto/rand" // 简便模式,可以直接调用该包下的函数等 (不建议,怕方法重名),直接访问函数
import _ "crypto/rand" // 匿名导入,仅让该包执行init初始化函数

Go 语言使用名称首字母大小写来判断一个对象(函数、全局变量、类型、结构字段、函数)的访问权限,对于包而言同样如此,首字母大写,可被包外访问,即public,首字母小写,仅包内成员可以访问,即internal

2、init函数初始化

Go语言有一个特殊的函数init,它先于main函数执行,实现包级别的一些初始化操作,如建立数据库连接、初始化全局变量、注册、检查修复程序状态等。

init用在设置包、初始化变量或者其他要在程序运行前优先完成的引导工作。

即使包被导入多次,初始化只执行一次

// 匿名导入,仅执行该包下的init初始化函数,先main函数执行
// 注意必须是Goworks/src目录下的包路径
import (
	_ "awesomeProject/lesson10/test"
	"fmt"
)

func main()  {
	fmt.Println("main---")
}

test包:

多个init函数

a.go 下多个init函数

import "fmt"

func init(){
	fmt.Println("test a init1")
}

func init()  {
	fmt.Println("test a init2")
}

func init()  {
	fmt.Println("test a init3")
}

main包下也增加一个init函数

package main

// 匿名导入,执行该包下的init初始化函数,先与main函数执行
// 注意必须是Goworks/src目录下的包路径
import (
	_ "awesomeProject/lesson10/test"
	"fmt"
)

// init函数不需要传入参数,也不会返回任何值,与main相比,init没有被声明,因此也不能被引用
func init()  {
	fmt.Println("main init")
}

func main()  {
	fmt.Println("main---")
}

执行程序入口main函数,结果:

test a init1
test a init2
test a init3
main init
main---

init执行顺序

为了使用导入的包,首先必须将其初始化,初始化总是以单线程执行的,并且按照包的依赖关系顺序执行,这通过golang的运行时系统控制,如下图所示:

  1. 初始化导入的包(递归导入)
  2. 对包块中声明的变量进行计算和分配初始值
  3. 执行包中的init函数

例子:

上面的例子代码增加包test2与b.go

package test2

import "fmt"

func init() {
	fmt.Println("test2-b-init-1")
}

a.go匿名引入包test2

import "fmt"
import _ "awesomeProject/lesson10/test2"

func init(){
	fmt.Println("test a init1")
}

func init()  {
	fmt.Println("test a init2")
}

func init()  {
	fmt.Println("test a init3")
}

执行demo02.go的main函数

执行结果:

test2-b-init-1
test a init1
test a init2
test a init3
main init
main---

包test2下再增加多一个c.go,包含init函数

package test2

import "fmt"

func init() {
	fmt.Println("test2-c-init-1")
}

执行结果:

test2-b-init-1
test2-c-init-1
test a init1
test a init2
test a init3
main init
main---

把b.go改名称为d.go,重新执行,结果如下:

test2-c-init-1
test2-b-init-1
test a init1
test a init2
test a init3
main init
main---

所以同一个包下的多个init函数,根据go文件名称的首字母排序执行init函数

总结,init函数总是自上而下执行的

3、strings包常用函数

字符串常用操作

package main

import (
	"fmt"
	"strings"
)

func main() {
	// 定义一个字符串
	str := "xuexiangban,kuangshen"

	// Contains 是否包含指定的内容,返回布尔值,精确查询
	fmt.Println(strings.Contains(str, "k"))  // true
	fmt.Println(strings.Contains(str, "z"))  // false
	fmt.Println(strings.Contains(str, "ka")) //false

	// ContainsAny 是否包含指定的任意一个内容,有一个即可,返回布尔值
	fmt.Println(strings.ContainsAny(str, "ka")) // true

	// Count统计指定内容,在字符串中出现的次数
	fmt.Println(strings.Count(str, "n")) // 4

	// HasPrefix 以xx开头,返回布尔值
	// 场景,返回2023开头的文件
	filename := "20231203.mp4"
	if strings.HasPrefix(filename, "2023") {
		fmt.Println("找到2023开头的文件")
	}
	// HasSuffix,以xx结尾,返回布尔值
	if strings.HasSuffix(filename, "mp4") {
		fmt.Println("找到mp4结尾的文件")
	}

	// Index 寻找指定字符串第一次出现的位置,找到返回下标,找不到返回-1
	fmt.Println(strings.Index(str, "n")) // 6

	// IndexAny 寻找指定字符串任意字符第一次出现的位置,找到返回下标,找不到返回-1
	fmt.Println(strings.IndexAny(str, "xz")) // 0

	// LastIndex 寻找指定字符串最后一次出现的位置,找到返回下标,找不断返回-1
	fmt.Println(strings.LastIndex(str, "n")) // 20

	// Join 用指定符号进行字符串拼接
	str2 := []string{"a", "b", "c", "d"}
	str3 := strings.Join(str2, "-")
	fmt.Println(str3) // a-b-c-d

	// Split 分隔字符串返回切片,用的最多
	str4 := strings.Split(str3, "-")
	fmt.Println("Split 返回切片:", str4, "切片长度", len(str4)) // 切片长度4

	// Repeat 重复拼接自己,返回字符串
	str5 := strings.Repeat("a", 5)
	fmt.Println(str5) // aaaaa

	// Replace 替换指定字符,n代表替换个数,-1替换全部
	str6 := strings.Replace(str, "u", "*", -1)
	fmt.Println(str6) // x*exiangban,k*angshen
	// ReplaceAll

	// ToUpper 转大写  ToLower 转小写
	fmt.Println(strings.ToUpper(str))

	// 截取字符串 str[start,end],跟我们的数组转切片是非常像的
	str7 := str[0:5]
	fmt.Println(str7) // xuexi
	// index=5到结尾
	fmt.Println(str[5:]) // angban,kuangshen
}

4、strconv包常用函数

string convert ,字符串与基本类型之间的转换

前端:网页、小程序、app

后端:go代码

package main

import (
	"fmt"
	"strconv"
)

func main() {
	s1 := "true"
	// 1、字符串转换为bool类型
	b1, _ := strconv.ParseBool(s1)
	fmt.Printf("%T\n", b1)
	fmt.Println(b1)

	// 2、bool类型转换字符串
	s2 := strconv.FormatBool(b1)
	fmt.Printf("%T\n", s2)
	fmt.Println(s2)

	// 3、字符串与整型的互转, 转换为对应的进制数
  // 第一个参数,字符串
  // 第二个参数进制,如10进制,2进制
  // 第三个参数大小 
	s3 := "100"
	i1, _ := strconv.ParseInt(s3, 10, 64)
	fmt.Printf("%T\n", i1) // 打印类型 int64
	fmt.Println(i1) // 打印100

	// 整型转字符串,
  // 第一个参数,整数
  // 第二个参数进制,如10进制,2进制
	s4 := strconv.FormatInt(100, 2)
	fmt.Printf("%T\n", s4)
	fmt.Println(s4)

	// 4、整型与字符串快速互转的方法 Ascii to Integer 
	// atoi 字符串转整型 
	i2, _ := strconv.Atoi("-20")
	fmt.Printf("%T\n", i2)
	fmt.Println(i2 + 10) // -10
	// itoa 整型转字符串
	s5 := strconv.Itoa(30)
	fmt.Printf("%T\n", i2) // int
	fmt.Println(s5)  // 30
  
}

执行结果:

bool
true
string
true
int64
100
string
1100100
int
-10
int
30

5、时间与时间戳

time包

  • 获取当前时间
  • 时间格式化
  • 定时

时间和日期是我们编程中经常会用到的,我们讲一下go内置的time包的基本用法

获取当前时间

func main() {
	time1()
}

func time1() {
	now := time.Now()
	fmt.Println(now)
	year := now.Year() // 年
	month := now.Month() // 月
	day := now.Day() // 日
	hour := now.Hour()
	minute := now.Minute()
	second := now.Second()

	// 格式化打印,%02d 整数不足两位,左边用0补齐
	fmt.Printf("%d-%2d-%2d %2d:%2d:%2d", year, month, day, hour, minute, second)
}

执行结果:

2023-01-17 19:35:49.605219 +0800 CST m=+0.000101799
2023-01-17 19:35:49

时间格式化

时间类型有一个自带的方法Format进行格式化,需要注意的是Go语言中格式化时间模版不常见的YY-MM-DD Hh:M:S,而是go的诞生时间2006年1月2号15点04分,记忆口径 2006 1 2 3 4

func main() {
	// time1()
	now := time.Now()
	fmt.Println(now.Format("2006-01-02 15:04:05"))    // 24小时制
	fmt.Println(now.Format("2006-01-02 03:04:05 PM")) // 12小时制
	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"))
}

执行结果:

2023-01-17 19:45:20
2023-01-17 07:45:20 PM
2023/01/17 19:45
19:45 2023/01/17
2023-01-17

解析字符串格式的时间

返回time对象

func main() {
	// time1()
	now := time.Now()
	fmt.Println(now)

	// 时区
	loc, _ := time.LoadLocation("Asia/Shanghai") // 点进去看源码,UTC 0时区,Local 本地时区
	// 字符串转时间对象,需要指定时区
	timeObj, _ := time.ParseInLocation("2006-01-02 15:04:05", "2023-01-20 19:10:20", loc)
	fmt.Printf("%T\n", timeObj) // 打印时间对象类型
  fmt.Println(timeObj)
	fmt.Println(timeObj.Format("2006/01/02"))
}

执行结果:

2023-01-17 20:31:31.525175 +0800 CST m=+0.000107246
2023-01-20 19:10:20 +0800 CST
2023/01/20

时间戳

时间戳是自1970年1月1日(08:00:00GMT),到当前时间的总毫秒数,它也被称为Unix时间戳UnixTimestamp

func main() {
	// time1()
	now := time.Now()

	// 时间戳,获取当前时间戳
	timestamp := now.Unix() // 秒
	fmt.Println(timestamp)
  timestamp2 := now.UnixMilli() // 毫秒
	fmt.Println(timestamp2)
	timestamp3 := now.UnixMicro() // 微秒
	fmt.Println(timestamp3)
	timestamp4 := now.UnixNano() // 纳秒
	fmt.Println(timestamp4)
	// 时间戳转换时间对象, 获取 年月日时分秒
	time1 := time.Unix(timestamp, 0)
	fmt.Println(time1)
}

执行结果:

1673959436
2023-01-17 20:43:56 +0800 CST

6、生成随机数

随机数需要调用math/rand包,注意要设置种子数,这样每次生成的结果会不断变化

func main() {
	rand.Seed(13)        // 种子数不一样,生成的随机数才会不一样
	number := rand.Int() // 5920220759044228662,不建议使用
	fmt.Println(number)

	// 指定随机数的范围
	number2 := rand.Intn(100) // 0-9的随机数
	fmt.Println(number2)

	// 发现种子数不变,生成的随机数就不会变化
	// 把当前时间戳做为种子数,那么每次生成的随机数就会不一样了
	rand.Seed(time.Now().Unix())
	for i := 0; i < 10; i++ {
		num := rand.Intn(1000) // 0-999的随机数
		fmt.Printf("%03d\n", num)
	}

	// 20-100的随机数
	num3 := rand.Intn(20) + 80
	fmt.Println(num3)
}

7、定时器与时间判断

间隔常量Duration

time.Duration是time包定义的一个类型,它代表两个时间点之间经过的时间

以纳秒为单位

time包中定义的时间间隔类型的常量如下:

const{
  Nanosecond Duration =1 // 纳秒
  // 微妙
  Microsecond  = 1000 * Nanosecond
  // 毫秒
  Millisecond = 1000 * Microsecond
  // 秒
  Second = 1000 * Millisecond
  // 分
  Minute = 60 * Second
  // 时
  Hour = 60 * Minute
}

time.Duration 表示1纳秒,time.Second表示1秒

定时器

定时器time.tick

import (
	"fmt"
	"time"
)

func main() {
	// Tick 心跳
	ticker := time.Tick(time.Second) // 间隔1秒
  // ticker := time.Tick(3 * time.Second) // 间隔3秒
	for t := range ticker {          // 每隔1秒执行一次
		fmt.Println(t)
	}
  
  for i := 0; i < 10; i++ {
		fmt.Println(time.Now())
		// 让程序休眠1秒
		time.Sleep(time.Second)
	}
}

执行结果:

2023-01-17 22:27:23.797529 +0800 CST m=+1.002337217
2023-01-17 22:27:24.799321 +0800 CST m=+2.004135374
2023-01-17 22:27:25.799947 +0800 CST m=+3.004767502
2023-01-17 22:27:26.799172 +0800 CST m=+4.003999735
...

8、时间常用操作

Add

func main() {
	// Add
	now := time.Now()
	later := now.Add(time.Hour) // 1个小时后的时间
	fmt.Println(later)
	// Sub
	subtime := later.Sub(now)
	fmt.Println(subtime)
	// Equal
	fmt.Println(later.Equal(now))
	// Before
	fmt.Println(now.Before(later))
	// After
	fmt.Println(now.After(later))
}

执行结果:

2023-01-17 23:45:35.840123 +0800 CST m=+3600.000090272
1h0m0s
false
true
false

Sub

求两个时间之间的差值

Equal

判断两个时间是否相同,这种方法还会比较地点和时区信息,因此不同时区标准的时间也可以正确比较

Before

是否在某个时间点之前,如果t代表的时间点在u之前,返回true,否则返回false

After

是否在某个时间点之后

Post Directory