107.【并发与标准库】

217 阅读32分钟

Golang过渡期

(一)、Golang包

1.创建包

包可以区分命令空间 (一个文件夹不能有两个同名文件),也可以更好的管理项目。Go中创建一个包,一般是创建一个文件夹。在该文件夹的go文件中,使用package关键字声明包名称。通常文件夹名称和包名称相同,同一个文件下面只有一个包

包名等于文件夹名
在这里插入图片描述

(1).创建一个包

声明man包,在main包下面我们需要实现一个main函数
同一个文件夹只能有声明一个包

package main

func main() {
	println("test package")
}

(2).导入包

要使用某一个包下面的变量与方法,我们需要导入这个包之后我们才能进行调用。导入包的注意事项: “要导入从GOPATH开始的包路径”。列如,在service.go中导入dao包.

前提条件:

我们一定要有三个文件夹 src pkg bin

// 声明包名-有且只能声明一个
package service

import "demo/dao"  //导入包

// code 代码实现

func main() {
	
}

(3).包注意事项

  1. 一个文件夹只能有一个package

    • import后面的其实是GOPATH开始的相对路径,包括最后一段。但由于一个目录只能有一个package,所以 import 一个路径就等于是 import 了这个路径下的包。
  2. 一个package的文件不能在多个文件夹下

    • 如果多个文件夹下面有重名的 package,他们其实是彼此无关的pacakage
    • 如果一个go文件需要同时使用不同目录下的同名 pacakage, 需要在 import 这些目录时为每一个目录指定一个package的别名。

2.包管理工具 go module

(1).简介

go module是 golang1.11新添加的特性,用来管理模块中包的依赖关系
在这里插入图片描述

(2).go mod使用方法

  1. 初始化模块
go mod init <项目模块名>
  1. 依赖关系处理,很具go.mod文件
go mod tidy
  1. 将依赖包复制到项目下的vendor目录
go mod vendor

如果包被屏蔽(墙),可以使用这个命令,随后使用 go build -mod=vendor 编译

  1. 显示依赖关系
go list -m all
  1. 显示详细依赖关系
go list -m -json all
  1. 下载依赖
go mod download [path@version]

(3).实列演示

1. 首先打开我们的终端,我们需要对项目进行初始化的操作

go mod init 项目名

在这里插入图片描述
2.然后在service包下的userservice.go编写一个方法

user_service.go

// 声明包名-有且只能声明一个

package service

import "fmt"

// code 代码实现

func TestUseService() {
	fmt.Printf("test user service_use")
}

3.在终端控制台进入我们被引入包的文件路径然后重构

cd service  #进入包
go build 重构
PS C:\Environment\GoWorks\src\demo> go mod init demo
go: C:\Environment\GoWorks\src\demo\go.mod already exists
PS C:\Environment\GoWorks\src\demo> cd service  #进入service包
PS C:\Environment\GoWorks\src\demo\service> go build  #开始重构
PS C:\Environment\GoWorks\src\demo\service> 

4.转入我们需要导入包的go文件中

main.go

package main

//  导入demo目录下的service包进入这个go文件中
import "demo/service"

func main() {
	println("test package")
	service.TestUseService() // 通过包名引用指定方法
}

在这里插入图片描述
5.如果我们此时还想在service包中添加方法,我们仍然需要go build

Customer_service.go

// 声明包名-有且只能声明一个

package service

func TestCustomer() {
	println("test TestCustomer ...")
}

我们需要在终端控制台的 service包下 继续 go build

go build

6.再次进行调用我们新添加的方法

package main

//  导入demo目录下的service包进入这个go文件中
import (
	"demo/service"
)

func main() {
	println("test package")
	service.TestUseService() // 通过包名引用指定方法
	service.TestCustomer()   //再次通过包名调用
}

7.如果我们想要引入外部的包

进入官网的宝库: pkg.go.dev/

找到我么要导入包的命令,然后我们在终端进行输入这个命令

1. 假设我们引入 gin 这个包

go get -u github.com/gin-gonic/gin

然后在需要用的go文件引入包

使用这个包的一部分函数,否则在运行的时候编译器会自动给屏蔽掉

import "github.com/gin-gonic/gin"
package main

//  导入demo目录下的service包进入这个go文件中
import (
	"demo/service"
	"github.com/gin-gonic/gin"
	"net/http"
)

func main() {
	println("test package")
	service.TestUseService() // 通过包名引用指定方法
	service.TestCustomer()   //再次通过包名调用
	r := gin.Default()
	r.GET("/ping", func(c *gin.Context) {
		c.JSON(http.StatusOK, gin.H{
			"message": "pong",
		})
	})
	r.Run() // listen and serve on 0.0.0.0:8080 (for windows "localhost:8080")
}

然后在终端退到我们的项目目录下执行

PS C:\Environment\GoWorks\src\demo\service> cd ..
PS C:\Environment\GoWorks\src\demo> go mod tidy
go: downloading github.com/stretchr/testify v1.8.2
go: downloading gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405
go: downloading github.com/go-playground/assert/v2 v2.2.0
go: downloading github.com/davecgh/go-spew v1.1.1
go: downloading github.com/google/go-cmp v0.5.5
go: downloading golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543
demo imports
        github.com/gin-gonic/gin tested by
        github.com/gin-gonic/gin.test imports
        github.com/stretchr/testify/assert: github.com/stretchr/testify@v1.8.2: read "https:/goproxy.cn/@v/v1.8.2.zip": read tcp 192.168.1.5:62970->222.35.78.41:443: wsarecv: A connect
ion attempt failed because the connected party did not properly respond after a period of time, or established connection failed because connected host has failed to respond.
demo imports
        github.com/gin-gonic/gin imports
        github.com/gin-gonic/gin/binding tested by
        github.com/gin-gonic/gin/binding.test imports
        github.com/stretchr/testify/require: github.com/stretchr/testify@v1.8.2: read "https:/goproxy.cn/@v/v1.8.2.zip": read tcp 192.168.1.5:62970->222.35.78.41:443: wsarecv: A connec
tion attempt failed because the connected party did not properly respond after a period of time, or established connection failed because connected host has failed to respond.
demo imports
        github.com/gin-gonic/gin imports
        github.com/gin-gonic/gin/binding imports
        github.com/go-playground/validator/v10 tested by
        github.com/go-playground/validator/v10.test imports
        github.com/go-playground/assert/v2: github.com/go-playground/assert/v2@v2.2.0: Get "https://goproxy.cn/github.com/go-playground/assert/v2/@v/v2.2.0.zip": read tcp 192.168.1.5:6
2970->222.35.78.41:443: wsarecv: A connection attempt failed because the connected party did not properly respond after a period of time, or established connection failed because conne
cted host has failed to respond.
demo imports
        github.com/gin-gonic/gin imports
        github.com/gin-gonic/gin/internal/json imports
        github.com/bytedance/sonic tested by
        github.com/bytedance/sonic.test imports
        github.com/davecgh/go-spew/spew: github.com/davecgh/go-spew@v1.1.1: Get "https://goproxy.cn/github.com/davecgh/go-spew/@v/v1.1.1.zip": read tcp 192.168.1.5:62970->222.35.78.41:
443: wsarecv: A connection attempt failed because the connected party did not properly respond after a period of time, or established connection failed because connected host has faile
d to respond.
demo imports
        github.com/gin-gonic/gin imports
        github.com/gin-gonic/gin/binding imports
        google.golang.org/protobuf/proto imports
        google.golang.org/protobuf/reflect/protoregistry tested by
        google.golang.org/protobuf/reflect/protoregistry.test imports
        github.com/google/go-cmp/cmp/cmpopts imports
        golang.org/x/xerrors: golang.org/x/xerrors@v0.0.0-20191204190536-9bdfabe68543: Get "https://goproxy.cn/golang.org/x/xerrors/@v/v0.0.0-20191204190536-9bdfabe68543.zip": read tcp
 192.168.1.5:62970->222.35.78.41:443: wsarecv: A connection attempt failed because the connected party did not properly respond after a period of time, or established connection failed
 because connected host has failed to respond.
PS C:\Environment\GoWorks\src\demo>

启动项目
在这里插入图片描述


(二)、Golang并发编程

1.并发编程之协程 go

Golang中的并发是函数相互独立运行的能力,Goroutines是并发运行的函数。Golang提供了Goroutines作为并发处理操作的一种方式。

创建一个协程非常简单,就是在一个任务函数前面添加一个go关键字;

go task()

(1).没有添加协程的操作

time.Sleep(time.Millisecond * 500) //睡眠 100毫秒

没有添加协程的操作: 好像打印机一个个的输出

package main

import (
	"fmt"
	"time"
)

func showMsg(msg string) {
	/***
		发现打印机的方式- 输入一个停顿一会会
	 */
	for i := 0; i < len(msg); i++ {
		fmt.Printf("%c", msg[i])
		time.Sleep(time.Millisecond * 100) //睡眠 100毫秒
	}
}

func main() {
	showMsg("java")
	showMsg("golang")
}

j->a->v->a->g->o->l->a->n->g

(2).添加协程的操作

添加完毕协程之后--

package main

import (
	"fmt"
	"time"
)

func showMsg(msg string) {
	/***
	发现打印机的方式- 输入一个停顿一会会
	*/
	for i := 0; i < len(msg); i++ {
		fmt.Printf("%c", msg[i])
		time.Sleep(time.Millisecond * 500) //睡眠 100毫秒
	}
}
func main() {
	go showMsg("java")   // 开启一个go协程
	showMsg("golang")

}

在这里插入图片描述
如果我们给每一个方法都执行协程,那么我们需要在主函数处多等等添加个睡眠

为什么需要在后面添加一个休眠?

因为主函数结束,那么所有的线程都最将会被销毁,我们添加休眠的目的就是给线程续命。

package main

import (
	"fmt"
	"time"
)

func showMsg(msg string) {
	/***
	发现打印机的方式- 输入一个停顿一会会
	*/
	for i := 0; i < len(msg); i++ {
		fmt.Printf("%c", msg[i])
		time.Sleep(time.Millisecond * 500) //睡眠 100毫秒
	}
}

func main() {
	go showMsg("java")
	go showMsg("golang")
	time.Sleep(time.Second * 5)   //我们需要在这里添加一个睡眠
}

(3).协程原理探究

Go文件的协程实质上就是: 开辟一个新的线程。主函数退出之后,那么线程也就会自动关闭
在这里插入图片描述

2.并发编程之通道 channel

Go 提供了一种称为通道的机制,用于在coroutinue(协程)之间共享数据。当您为coroutinue执行并发活动时,需要受coroutinue之间共享资源或数据,通道充当coroutinue之间的管道(通道)并提供一种机制来保护同步交互

需要在声明通道时指定数据类型。我们可以共享内置、命名、结构和引用类型的值和指针。数据在通道上传递: 在任何给定时间只有一个coroutinue可以访问数据项: 因此按照设计不会发生数据竞争

根据数据交互的行为,有两种类型的通道: 无缓冲通道和缓冲通道。无缓冲通道用于执行coroutinue之间的同步通信,而缓冲通道用于执行异步通信无缓冲通道保证在发送和接受发生的瞬间执行两个coroutinue之间的交换。缓冲通道没有这样的保证。

通道由make函数创建,该函数指定 chan 关键字和通道的元素类型。

在这里插入图片描述

(1).创建无缓存通道和缓存通道

1. 无缓存 ->同步
 Unbuffed:=make(chan int)  //整形无缓存通道
2. 有缓存 ->异步
buffed:=make(chan int,10)  //整形有缓冲通道

(2).通道的发送和接受

  1. 通道的发送
通道变量名<-xxxx
  1. 通道的接受
xxx:=<-通道变量名

(3).通道的发送和接受特性

  1. 对于同一个通道,发送操作之间是互斥的,接受操作之间也是互斥的
  2. 发送操作和接受操作中对元素值的处理是不可分割的
  3. 发送操作在完全完成之前会被阻塞,接受操作也是如此

如果不添加go 进行协程实现那么会出现: 死锁错误

package main

import (
	"fmt"
	"math/rand"
	"time"
)

var values = make(chan int) //创建一个int类型的无缓存区的通道

func send() {
	rand.Seed(time.Now().UnixNano()) //获取随机数,不加随机种子,每次遍历获取都是重复的一些随机数据
	value := rand.Intn(10)           // 随机取一个10范围之内的数
	fmt.Printf("send:%v\n", value)
	time.Sleep(time.Second * 5)  //阻塞五秒
	values <- value //把值放到通道中
}

func main() {

	defer close(values) //关闭通道
	go send()           //协程执行这个方法 **** 如果不协程执行会报错
	println("wait...")
	value := <-values //取值
	fmt.Printf("receive:%v\n", value)
	println("end...")

}

在这里插入图片描述

3.并发编程之WaitGroup实现同步

waitGroup: 就是指所有的线程实现完毕之后,再继续执行。

(1).等待组的步骤

1. 我们需要引入包
import "sync"
2. 定义等待组的全局变量
var wp sync.WaitGroup //定义等待组
3. 在开启协助的下面我们需要向等待组中+1
wp.Add(1) //每次启动一个线程我们就向等待组中添加1
4. 在线程执行的函数最后向等待组-1
defer wp.Done() //每一个线程完成之后我们就在等待组中减少1
5. 在主线程最后进行等待
wp.Wait() //在这里实时等待,直到等待组中的值为-1,终止执行。 比睡眠更加高效

(2).等待组的实列

package main

import (
	"fmt"
	"sync"
)

var wp sync.WaitGroup //定义等待组
func showMsg(i int) {
	defer wp.Done() //每一个线程完成之后我们就在等待组中减少1
	fmt.Printf("%v\n", i)
}

func main() {
	//  开启十个协程
	for i := 0; i < 10; i++ {
		go showMsg(i)
		wp.Add(1) //每次启动一个线程我们就向等待组中添加1
	}

	// 主协程: 假如我们不在主线程这里设置阻塞的话,那么他就会直接关闭。不会等待其他线程执行完毕

	wp.Wait() //在这里实时等待,直到等待组中的值为-1,终止执行。 比睡眠更加高效。
}

输出结果: 直到所有的等待组全部完成即可

(3).等待组的原理

假如我们开启一个线程,就向等待组中元素+1.如果一个等待组中的一个线程完成了,那么就申请向等待组元素-1.

在这里插入图片描述

4.并发编程之runtime包

(1).runtime.Gosched()

让出CPU时间片,重新等待安排任务。
只是给一个机会,但是结果不一定。

package main

import (
	"fmt"
	"runtime"
)

func show(msg string) {
	for i := 0; i < 2; i++ {
		fmt.Printf("%v\n", msg)
	}
}

func main() {
	// 子线程
	go show("小弟")
	for i := 0; i < 2; i++ {
		runtime.Gosched() // 我是老大,给你个机会能够超越我  -》只是机会
		println("老大")
	}
	println("end....")
}

结果: 谁先输出不一定看运气

(2).runtime.Goexit()

退出当前协程

package main

import (
	"fmt"
	"runtime"
	"time"
)

func show(msg string) {
	for i := 0; i < 10; i++ {
		if i >= 5 {
			runtime.Goexit() //直接退出这个协程
		}
		fmt.Printf("%v\n", msg)
	}
}

func main() {
	// 子线程
	go show("小弟")
	time.Sleep(time.Second * 1) //阻塞一秒
	println("end....")

}

结果: 执行到第五个,线程直接终止

(3).runtime.GOMAXPROCS(n)

设定执行程序的最大CPU数是多少?

package main

import (
	"fmt"
	"runtime"
	"time"
)

func a() {
	for i := 0; i < 10; i++ {
		fmt.Printf("a:%v\t", i)
	}
}
func b() {
	for i := 0; i < 10; i++ {
		fmt.Printf("b:%v\t", i)
	}
}

func main() {

	fmt.Printf("runtime.numcpu:%v\n", runtime.NumCPU()) //默认是最大CPU进行执行操作
	runtime.GOMAXPROCS(2)       //可以指定CPU的数量

	go a()
	go b()

	time.Sleep(time.Second)
}

在这里插入图片描述

5.并发编程之Mutex互斥锁实现同步

除了使用channel实现同步之外,还可以使用Mutex互斥的方式实现同步

(1).Mutex使用步骤

1. 导入包
import "sync"
2. 定义变量
var lock sync.Mutex //定义锁
3. 上锁
lock.Lock() //上锁
4. 解锁
lock.Unlock()  //解锁

(2).Mutex的实列

package main

import (
	"fmt"
	"sync"
	"time"
)

var i int = 100

var wt sync.WaitGroup //设置等待组

var lock sync.Mutex //定义锁
func add() {
	defer wt.Done() //放在这个子线程的最后执行
	time.Sleep(time.Millisecond * 10)
	lock.Lock() //上锁
	i += 1
	fmt.Printf("i++ ->%v\n", i)

	lock.Unlock()  //解锁
}

func sub() {
	defer wt.Done() //放在这个子线程的最后执行

	lock.Lock() //上锁
	i -= 1
	time.Sleep(time.Millisecond * 10) //先等会-因为可能还没有解锁
	fmt.Printf("i-- ->%v\n", i)

	lock.Unlock()  //解锁
}
func main() {

	for i := 0; i < 100; i++ {
		wt.Add(1) //+1
		go add()
		wt.Add(1) //+1
		go sub()
	}
	wt.Wait()
	fmt.Printf("end ->%v", i)
}

输出结果: 要么i++先输出完毕,要么i–先输出完毕

6.并发编程之channel的遍历

(1).放入的比读取的少

分为两种情况: 是否关闭资源

1.没有关闭管道资源

package main

import "fmt"

var c = make(chan int)

func main() {
	go func() { // 添加go 目的主要是为了同步
		for i := 0; i < 2; i++ {
			c <- i
		}
	}()

	// 假如就写入两个那么读取三个会有什么样的结果?
	for i := 0; i < 3; i++ {
		r := <-c
		fmt.Printf("%v\t", r)
	}
}

会产生死锁反应
在这里插入图片描述
2.关闭管道资源

package main

import "fmt"

var c = make(chan int)

func main() {
	go func() { // 添加go 目的主要是为了同步
		for i := 0; i < 2; i++ {
			c <- i
		}
		close(c) //--------------------关闭资源,会默认为类型的值
	}()

	// 假如就写入两个那么读取三个会有什么样的结果?
	for i := 0; i < 3; i++ {
		r := <-c
		fmt.Printf("%v\t", r)
	}
}

不会报错
在这里插入图片描述

(2).三种遍历方式

1.第一种for循环进行遍历

package main

import "fmt"

var c = make(chan int)

func main() {
	go func() { // 添加go 目的主要是为了同步
		for i := 0; i < 2; i++ {
			c <- i
		}
		close(c) //--------------------关闭资源,会默认为类型的值
	}()

	// 假如就写入两个那么读取三个会有什么样的结果?
	for i := 0; i < 3; i++ {
		r := <-c
		fmt.Printf("%v\t", r)
	}

}

2.range遍历

遍历管道的值的话,只有一个v值,没有下标

package main

import "fmt"

var c = make(chan int)

func main() {
	go func() { // 添加go 目的主要是为了同步
		for i := 0; i < 2; i++ {
			c <- i
		}
		close(c) //--------------------关闭资源,会默认为类型的值
	}()

	// 假如就写入两个那么读取三个会有什么样的结果?
	for v := range c {  //  只有一个
		fmt.Printf("%v\t", v)
	}
}

在这里插入图片描述
3.利用for if

package main

import "fmt"

var c = make(chan int)

func main() {
	go func() { // 添加go 目的主要是为了同步
		for i := 0; i < 2; i++ {
			c <- i
		}
		close(c) //--------------------关闭资源,会默认为类型的值
	}()

	// 假如就写入两个那么读取三个会有什么样的结果?
	for true {
		v, ok := <-c // 这个是判断是否存在值,如果存在那么就为true
		if ok {
			fmt.Printf("%v\t", v)
		} else {
			break
		}
	}
}

在这里插入图片描述

7.并发编程之select

(1).基础介绍

  1. select是Go中的一个控制层结构,类似于switch 语句,用于处理异步IO操作。select监听case语句中channel的读写操作,当case中的channel读写操作为非阻塞状态(即能读写)时,将会触发相应的动作。

1.select 中的case语句必须是一个channel操作
2.select 中的default子句总是可以运行的

  1. 如果有多个case都可以运行,select会随机公平地选出一个执行,其他不会执行
  2. 如果没有可运行地 case 语句,且有 default 语句,那么就会执行 default地动作
  3. 如果没有可运行地case语句,且没有default语句,select 将阻塞,直到某个case通信可以运行.

(2).select测试

package main

import (
	"fmt"
	"time"
)

var chanInt = make(chan int)
var chanString = make(chan string)

func main() {

	go func() {  //开启一个协助
		chanInt <- 100
		chanString <- "hello"
		defer close(chanString)
		defer close(chanInt)
	}()
	for true {
		select {
		case r := <-chanInt:
			fmt.Printf("%v\n", r)
		case r := <-chanString:
			fmt.Printf("%v\n", r)
		default:
			println("default....")
		}
		time.Sleep(time.Second)
	}
}

输出结果不固定: 三个顺序不定。

2.假如没有default并且没有case 那么就会报死锁

前提是没有关闭资源,关闭资源就不会报死锁

package main

import (
	"fmt"
	"time"
)

var chanInt = make(chan int)
var chanString = make(chan string)

func main() {

	go func() {
		chanInt <- 100
		chanString <- "hello"
		//defer close(chanString)
		//defer close(chanInt)
	}()

	for true {
		select {
		case r := <-chanInt:
			fmt.Printf("%v\n", r)

		case r := <-chanString:
			fmt.Printf("%v\n", r)
		}
		time.Sleep(time.Second)
	}
}

在这里插入图片描述

8.并发编程之Timer

(1).基础介绍

Timer顾名思义就是定时器的意思。可以实现一些定时操作,器内部也是通过channel来实现的。

1.第一种定义方式
time.NewTime()   //进行创建
2.第二种定义方式
time.After()  //进行创建

(2).定时器的实列

  1. 利用NerTime() 指定时间后再执行
package main

import (
	"fmt"
	"time"
)

func main() {

	// 1.第一种定义定时器
	timer := time.NewTimer(time.Second * 2) //设置指定时间后执行管道读出
	fmt.Printf("现在的时间是: %v\n", time.Now())
	t1 := <-timer.C //阻塞的,指定时间到了便会读出
	fmt.Printf("管道读出的时间是: %v\n", t1)
}

在这里插入图片描述

  1. 不设置变量名进行定时阻塞
package main

import (
	"fmt"
	"time"
)

func main() {

	 1.第一种定义定时器
	//timer := time.NewTimer(time.Second * 2) //设置指定时间后执行管道读出
	//fmt.Printf("现在的时间是: %v\n", time.Now())
	//t1 := <-timer.C //阻塞的,指定时间到了便会读出
	//fmt.Printf("管道读出的时间是: %v\n", t1)
	fmt.Printf("现在的时间是: %v\n", time.Now())
	timer := time.NewTimer(time.Second * 2) //设置指定时间后执行管道读出
	<-timer.C   //这里也会等待两秒钟
	fmt.Printf("现在的时间是: %v\n", time.Now())
}

在这里插入图片描述

  1. After创建定时器
package main

import (
	"fmt"
	"time"
)

func main() {

	 1.第一种定义定时器
	//timer := time.NewTimer(time.Second * 2) //设置指定时间后执行管道读出
	//fmt.Printf("现在的时间是: %v\n", time.Now())
	//t1 := <-timer.C //阻塞的,指定时间到了便会读出
	//fmt.Printf("管道读出的时间是: %v\n", t1)
	//fmt.Printf("现在的时间是: %v\n", time.Now())
	//timer := time.NewTimer(time.Second * 2) //设置指定时间后执行管道读出
	//<-timer.C
	//fmt.Printf("现在的时间是: %v\n", time.Now())
	
	fmt.Printf("现在的时间是: %v\n", time.Now())
	<-time.After(time.Second * 2)  //利用After进行创建定时器
	fmt.Printf("现在的时间是: %v\n", time.Now())

}

在这里插入图片描述

(3).强制取消定时器

package main

import (
	"fmt"
	"time"
)

func main() {

	 1.第一种定义定时器
	//timer := time.NewTimer(time.Second * 2) //设置指定时间后执行管道读出
	//fmt.Printf("现在的时间是: %v\n", time.Now())
	//t1 := <-timer.C //阻塞的,指定时间到了便会读出
	//fmt.Printf("管道读出的时间是: %v\n", t1)
	//fmt.Printf("现在的时间是: %v\n", time.Now())
	//timer := time.NewTimer(time.Second * 2) //设置指定时间后执行管道读出
	//<-timer.C
	//fmt.Printf("现在的时间是: %v\n", time.Now())
	fmt.Printf("现在的时间是: %v\n", time.Now())
	timer := time.NewTimer(time.Second * 2)
	go func() {
		<-timer.C
		println("func.....")
	}()
	s := timer.Stop()  // 我们执行这个方法那么定时器就不会执行
	if s {
		println("stop....")
	}
	fmt.Printf("现在的时间是: %v\n", time.Now())
	time.Sleep(time.Second * 3)
}

在这里插入图片描述

(4).定时器时间的修改

package main

import (
	"fmt"
	"time"
)

func main() {

	 1.第一种定义定时器
	//timer := time.NewTimer(time.Second * 2) //设置指定时间后执行管道读出
	//fmt.Printf("现在的时间是: %v\n", time.Now())
	//t1 := <-timer.C //阻塞的,指定时间到了便会读出
	//fmt.Printf("管道读出的时间是: %v\n", t1)
	fmt.Printf("现在的时间是: %v\n", time.Now())
	timer := time.NewTimer(time.Second * 2) //设置指定时间后执行管道读出
	timer.Reset(time.Second * 5)
	<-timer.C
	fmt.Printf("现在的时间是: %v\n", time.Now())
	//fmt.Printf("现在的时间是: %v\n", time.Now())
	//timer := time.NewTimer(time.Second * 2)
	//go func() {
	//	<-timer.C
	//	println("func.....")
	//}()
	//s := timer.Stop() // 我们执行这个方法那么定时器就不会执行
	//if s {
	//	println("stop....")
	//}
	//fmt.Printf("现在的时间是: %v\n", time.Now())
	//time.Sleep(time.Second * 3)
}

在这里插入图片描述

9.并发编程之Ticker

Timer只执行一次,Ticker可以周期的执行

(1).定义方式

1.第一种定义方式
time.NewTicker()

(2).Ticker实列

  1. 基础使用

每隔两秒打印一下

package main

import "time"

func main() {
	ticker := time.NewTicker(time.Second * 2) //每两秒执行一次
	counter := 1
	for _ = range ticker.C { //如果没有变量接受那么可以省略 :  ------>出现周期性的使用这个定时器
		println("ticker....")
		counter++
		if counter >= 5 {
			ticker.Stop()
			break
		}
	}
}

在这里插入图片描述

package main

import (
	"fmt"
	"time"
)

func main() {
	ticker := time.NewTicker(time.Second) //每两秒执行一次

	chanInt := make(chan int)
	go func() {
		for _ = range ticker.C { //如果没有变量接受那么可以省略 :  ------>出现周期性的使用这个定时器

			select {
			case chanInt <- 1:
				println("send: 1")
			case chanInt <- 2:
				println("send: 2")
			case chanInt <- 1:
				println("send: 3")
			}
		}
	}()
	sum := 0
	for v := range chanInt {
		fmt.Printf("receice: %v\n", v)
		sum += v
		if sum >= 10 {
			break
		}
	}
}

在这里插入图片描述

10.并发编程之原子变量的引入

(1).我们可以通过枷锁实现同步

为什么我实现枷锁: 联想银行的同步取钱操作。

package main

import (
	"fmt"
	"sync"
	"time"
)

/*
银行业务. 一定要考虑好保护加锁
*/

var mt sync.Mutex //定义锁
var i = 100

func add() {
	mt.Lock()  //当我上锁了,别人对我下面用的变量都不能用,
	i++
	mt.Unlock()
}
func sub() {
	mt.Lock()
	i--
	mt.Unlock()
}
func main() {
	for i := 0; i < 100; i++ {
		go add()
		go sub()
	}
	time.Sleep(time.Second * 1)
	fmt.Printf("%v", i) // 假如不上锁的话,会出现数据合演不对。
}

在这里插入图片描述

(2).autoic包实现乐观锁

package main

import (
	"fmt"
	"sync/atomic"
	"time"
)

/*
银行业务. 一定要考虑好保护加锁
*/

var i int32 = 100

func add() {
	// 这里的机制类似于 乐观锁的操作: 在我修改之前不允许有人修改,否则就不修改
	atomic.AddInt32(&i, 1) // 就是值int32这个类型的那个地址,每次调用添加几

}
func sub() {

	atomic.AddInt32(&i, -1)

}
func main() {
	for i := 0; i < 100; i++ {
		go add()
		go sub()
	}
	time.Sleep(time.Second * 1)
	fmt.Printf("%v", i) // 假如不上锁的话,会出现数据合演不对。
}

在这里插入图片描述

11.并发编程之原子操作详解

(1).automic的介绍

automic 提供的原子操作能够确保任意时刻只有一个协程对变量进行操作,善用 atomic 能够避免程序中出现大量的锁的操作。

automic常见操作有:

  • 增减
  • 载入 (读)
  • 比较并交换 cas
  • 交换
  • 存储 (写)

(2).原子性增减操作

automic包中提供了如下以Add为前缀的增减 操作:

func AddInt32(add *int32,delta int32) (new int32)
func AddInt32(add *int64,delta int64) (new int64)
func AddUInt32(add *uint32,delta uint32) (new uint32)
func AddInt32(add *uint64,delta uint64) (new uint64)
func AddInt32(add *uintptr,delta uintptr) (new uintptr)  //指针类型

增减的操作:

func add() {
	// 这里的机制类似于 乐观锁的操作: 在我修改之前不允许有人修改,否则就不修改
	atomic.AddInt32(&i, 1) // 就是值int32这个类型的那个地址,每次调用添加几

}
func sub() {

	atomic.AddInt32(&i, -1)

}

(3).原子性载入和存储

相当于银行的存钱和查看余额。

package main

import (
	"fmt"
	"sync/atomic"
	"time"
)

/*
银行业务. 一定要考虑好保护加锁
*/

var i int32 = 100

func add() {
	// 这里的机制类似于 乐观锁的操作: 在我修改之前不允许有人修改,否则就不修改

	atomic.AddInt32(&i, 1)      // 就是值int32这个类型的那个地址,每次调用添加几
	atomic.StoreInt32(&i, 200)  //存储: 相当于写的操作 当我写的时候不允许其他人写, -》类似于向银行存钱
	val := atomic.LoadInt32(&i) //载入 也就是读的操作 当我读取的时候不允许其他人读
	fmt.Printf("add-> %v\n", val)

}
func sub() {

	atomic.AddInt32(&i, -1)
	atomic.StoreInt32(&i, 200)  //存储: 相当于写的操作 当我写的时候不允许其他人写,
	val := atomic.LoadInt32(&i) //载入 也就是读的操作:  当我读取的时候不允许其他人读
	fmt.Printf("sub-> %v\n", val)
}
func main() {
	for i := 0; i < 100; i++ {
		go add()
		go sub()
	}
	time.Sleep(time.Second * 1)
	fmt.Printf("%v", i) // 假如不上锁的话,会出现数据合演不对。
}

在这里插入图片描述

(4).原子性比较交换

假如说: 旧值一直没有发生变化,那么就和新值发生交换返回true,否则不交换返回false。

package main

import (
	"fmt"
	"sync/atomic"
	"time"
)

/*
银行业务. 一定要考虑好保护加锁
*/

var i int32 = 100

func add() {
	// 这里的机制类似于 乐观锁的操作: 在我修改之前不允许有人修改,否则就不修改

	atomic.AddInt32(&i, 1)      // 就是值int32这个类型的那个地址,每次调用添加几
	atomic.StoreInt32(&i, 200)  //存储: 相当于写的操作 当我写的时候不允许其他人写, -》类似于向银行存钱
	val := atomic.LoadInt32(&i) //载入 也就是读的操作 当我读取的时候不允许其他人读
	fmt.Printf("add-> %v\n", val)

}
func sub() {

	atomic.AddInt32(&i, -1)
	atomic.StoreInt32(&i, 200)  //存储: 相当于写的操作 当我写的时候不允许其他人写,
	val := atomic.LoadInt32(&i) //载入 也就是读的操作:  当我读取的时候不允许其他人读
	fmt.Printf("sub-> %v\n", val)
}
func main() {
	for i := 0; i < 100; i++ {
		go add()
		go sub()
	}
	time.Sleep(time.Second * 1)
	swapInt32 := atomic.CompareAndSwapInt32(&i, 100, 200) // 就是说假如值为100那么我们就交换,假如100被修改成其他的值,那么我们就不进行交换返回false  -->也就是说有其他线程干扰了就返回flase
	fmt.Printf("%v", swapInt32)
	fmt.Printf("%v", i) // 假如不上锁的话,会出现数据合演不对。
}

(三)、Golang标准库

1.标准库os模块之文件目录

(1).查看源码的两种方式

查看源码: pkg.go.dev/std

在这里插入图片描述

(2).操纵OS创建文件

GO语言操纵操作系统-> 创建一个可执行文件。如果多次执行同一个文件名,会产生覆盖不会报错误信息

create, err := os.Create("test.txt") //返回值: 第一个是文件对象,第二个是错误日志
package main

import (
	"fmt"
	"os"
)

/*
	利用GO语言进行操作 -> 操作系统
*/

func createFile() {
	create, err := os.Create("test.txt") //返回值: 第一个是文件对象,第二个是错误日志
	if err != nil {
		println(err)
	} else {
		fmt.Printf("恭喜您文件创建成功,文件名是:%v", create.Name())
	}
}

func main() {
	createFile() //如果多次调用,会直接覆盖
}

在这里插入图片描述

(3).操纵OS创建文件目录

  1. 第一种创建单极目录
package main

import (
	"fmt"
	"os"
)

/*
	利用GO语言进行操作 -> 操作系统
*/

func makeDir() {
	err := os.Mkdir("test", os.ModePerm) //第一个参数是目录名,第二个参数是目录权限
	if err != nil {
		println(err)
	} else {
		fmt.Printf("恭喜您创建成功!")
	}
}

func main() {
	makeDir()
}
  1. 第二种创建联机目录
package main

import (
	"fmt"
	"os"
)

/*
	利用GO语言进行操作 -> 操作系统
*/
func makeDirAll() {
	err := os.MkdirAll("test/b/c/d", os.ModePerm)
	if err != nil {
		println(err)
	} else {
		fmt.Printf("恭喜您创建成功!")
	}
}
func main() {
	//createFile() //如果多次调用,会直接覆盖
	makeDirAll()
}

在这里插入图片描述

(4).操纵OS删除文件

  1. 删除文件信息
package main

import (
	"fmt"
	"os"
)

/*
	利用GO语言进行操作 -> 操作系统
*/

func remove() {
	err := os.Remove("test.txt")
	if err != nil {
		println(err)
	} else {
		fmt.Printf("删除成功!")
	}

}
func main() {

	remove()
}

在这里插入图片描述

  1. 联机删除文件目录
package main

import (
	"fmt"
	"os"
)

/*
	利用GO语言进行操作 -> 操作系统
*/

func remove() {
	err := os.RemoveAll("test") //会联机删除文件类似于 :  rm -rf /*
	if err != nil {
		fmt.Printf("%v", err)
	} else {
		fmt.Printf("删除成功!")
	}
}
func main() {
	remove()
}

在这里插入图片描述

(5)操纵OS 获取目录地址/修改目录地址/修改目录名

package main

import (
	"fmt"
	"os"
)

/*
	利用GO语言进行操作 -> 操作系统
*/

func wd() {
	getwd, err := os.Getwd()   //  获取当前的工作路径
	if err != nil {
		fmt.Printf("%v\n", err)
	} else {
		fmt.Printf("当前目录是: %v", getwd)
	}
	os.Chdir("d:/")  //把工作目录修改到我们设定的目录
	os.Rename("test.txt","test2.txt")  //  把test目录名修改成test2.txt
}
func main() {
	wd()
}

(6).操纵OS从某一个文件中读或者写

  1. 文件的读取

文件读取的时候是byte字节,我们需要将其转换成string

package main

import (
	"fmt"
	"os"
)

/*
	利用GO语言进行操作 -> 操作系统
*/

func read() {
	file, err := os.ReadFile("test.txt") //  返回的是文件信息和错误信息
	if err != nil {
		fmt.Printf("%v", err)
	} else {
		fmt.Printf("读取的文件信息是: %v", string(file[:])) // 读取的是一个个字节,我们需要将其转换成字符串
	}
}

func main() {
	read()
}

在这里插入图片描述

  1. 写的操作

字节数组传入的操作....

package main

import (
	"os"
)

/*
	利用GO语言进行操作 -> 操作系统
*/

func write() {
	s := "你好,全名制作人"
	os.WriteFile("test.txt", []byte(s), os.ModePerm) // 第一个参数是写哪里,第二个参数是内容(需要转换为byte),第三个是权限设置
}

func main() {
	write()
}

在这里插入图片描述

2.标准库os模块之文件读操作

这里结束和file结构体相关的文件读操作

(1).打开和关闭文件

  1. 假如为空就报错

假如文件为空,不会再此创建,而会报错

open, err := os.Open("test.txt") //  返回值:  文件对象和错误信息
package main

import (
	"fmt"
	"os"
)

func openClose() {
	open, err := os.Open("test.txt") //  返回值:  文件对象和错误信息
	if err != nil {
		fmt.Printf("%v", err)
	} else {
		fmt.Printf("已经打开的文件名是: %v\n", open.Name())
	}
	open.Close() // 关闭文件
}

func main() {
	openClose()
}

在这里插入图片描述

  1. 假如为空就创建

在这里插入图片描述
文件 a.txt是不存在的,我们直接执行就会被创建
在这里插入图片描述

package main

import (
	"fmt"
	"os"
)

func openClose() {
	open, err := os.OpenFile("a.txt", os.O_RDWR|os.O_CREATE, 755) // 参数: 文件名. 文件的权限是读和创建 . 主用户可以读写执行其他的读和执行
	if err != nil {
		fmt.Printf("%v", err)
	} else {
		fmt.Printf("已经打开的文件名是: %v\n", open.Name())
	}
	open.Close() // 关闭文件
}

func main() {
	openClose()
}

在这里插入图片描述

(2).读取文件

假如说:我们的缓冲区不够大,文件内容读不完。那么我们有两种办法:第一种使用for循环进行一直读取,直到报错信息等于 io.EOF

  1. 第一种for循环读取 read
package main

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

func readOps() {

	open, err := os.Open("test.txt") //  返回值:  文件对象和错误信息
	for {
		if err != nil {
			fmt.Printf("%v", err)
		} else {
			fmt.Printf("已经打开的文件名是: %v\n", open.Name())
			bytes := make([]byte, 10)     //创建一个缓冲区,类型是byte数组,大小是10个字节
			read, err := open.Read(bytes) // 进行读取的操作,里面的参数是一个缓冲区 byte数组.---->返回的参数是: 占用的字节数
			if err == io.EOF { //假如说这个错误等于他那么我们就终止循环
				break
			} else {
				fmt.Printf("%v\n", read)
				fmt.Printf("读取到的内容是:%v\n", string(bytes)) //打印我们读取的文件,里面的参数是缓冲区
			}
		}

	}
	open.Close() // 关闭文件
}
func main() {
	readOps()
}

在这里插入图片描述

  1. 第二种空间足够大 read
package main

import (
	"fmt"
	"os"
)

func readOps() {
	open, err := os.Open("test.txt") //  返回值:  文件对象和错误信息
	if err != nil {
		fmt.Printf("%v", err)
	} else {
		fmt.Printf("已经打开的文件名是: %v\n", open.Name())
		bytes := make([]byte, 100)    //创建一个缓冲区,类型是byte数组,大小是10个字节
		read, err := open.Read(bytes) // 进行读取的操作,里面的参数是一个缓冲区 byte数组.---->返回的参数是:
		if err != nil {
			fmt.Printf("%v", err)
		} else {
			fmt.Printf("%v\n", read)
			fmt.Printf("读取到的内容是:%v\n", string(bytes)) //打印我们读取的文件,里面的参数是缓冲区
		}

	}

	open.Close() // 关闭文件
}

func main() {
	readOps()
}

在这里插入图片描述

  1. 从指定的字节开始读取 reatAt

在这里插入图片描述

package main

import (
	"fmt"
	"os"
)

func readOps() {
	open, err := os.Open("test.txt") //  返回值:  文件对象和错误信息
	if err != nil {
		fmt.Printf("%v", err)
	} else {
		fmt.Printf("已经打开的文件名是: %v\n", open.Name())
		bytes := make([]byte, 100)     //创建一个缓冲区,类型是byte数组,大小是10个字节
		at, _ := open.ReadAt(bytes, 3) // 从第几个字节开始读取,前面的不读取
		fmt.Printf("读取的字节个数是:%v\n", at)
		fmt.Printf("读取的内容是:%v\n", string(bytes))

	}
	open.Close() // 关闭文件
}

func main() {
	readOps()
}

在这里插入图片描述

  1. 遍历一个目录
package main

import (
	"fmt"
	"os"
)

func readOps() {
	dir, _ := os.ReadDir("a")
	for _, v := range dir {
		fmt.Printf("是否是一个目录: %v", v.IsDir())
		fmt.Printf("目录名字: %v", v.Name())
	}
}

func main() {
	readOps()
}

在这里插入图片描述

  1. 定位的操作

在这里插入图片描述

package main

import (
	"fmt"
	"os"
)

func readOps() {
	open, _ := os.Open("test.txt")
	open.Seek(3, 0)// 就是从哪里开始读取 3代表从第三位开始。0是相对于开头
	bytes := make([]byte, 10)
	read, _ := open.Read(bytes)
	fmt.Printf("读取的字节个数为:%v\n", read)
	fmt.Printf("读取到的内容是:%v\n", string(bytes))
	open.Close()
}

func main() {
	readOps()
}

在这里插入图片描述

3.标准库os模块之文件写操作

(1).利用缓存进行写的操作

open->默认只有读。 openfile->可以设置权限
file, _ := os.OpenFile("test.txt", os.O_RDWR|os.O_APPEND, 755) // 读写的权限|追加 . 755
file, _ := os.OpenFile("test.txt", os.O_RDWR|os.O_TRUNC, 755) // 读写权限+覆盖
file, _ := os.OpenFile("test.txt", os.O_RDWR|os.O_CREATE, 755) //读写权限+创建	
package main

import "os"

func write() {
	file, _ := os.OpenFile("test.txt", os.O_RDWR|os.O_APPEND, 755) // 读写的权限|追加 . 755
	s := "你好,世界的人民!!!"
	file.Write([]byte(s))
	file.Close()
}

func main() {
	write()
}

在这里插入图片描述

(2).直接写字符串不用缓冲区

package main

import "os"

func write() {
	file, _ := os.OpenFile("test.txt", os.O_RDWR|os.O_TRUNC, 755) // 读写的权限|追加 . 755
	s := "你好,世界的人民!!!"
	file.WriteString(s)
	file.Close()
}

func main() {
	write()
}

在这里插入图片描述

(3).从第几个字节开始编写

常见于段落空格

package main

import "os"

func write() {
	file, _ := os.OpenFile("test.txt", os.O_RDWR|os.O_TRUNC, 755) // 读写的权限|追加 . 755
	s := "你好,世界的人民!!!"
	file.WriteAt([]byte(s), 3)
	file.Close()
}

func main() {
	write()
}

在这里插入图片描述

4.标准库os包之进程 💢

5.标准库os包之环境相关

所有的环境变量

(1).对环境变量的修改

package main

import (
	"fmt"
	"os"
)

func main() {
	// 获得所有环境变量
	s := os.Environ()
	fmt.Printf("%v\n", s)
	// 获得某个环境变量
	s2 := os.Getenv("GOPATH")
	fmt.Printf("%v\n", s2)
	// 设置环境变量
	os.Setenv("env1", "env1")
	s2 = os.Getenv("sss")
	fmt.Printf("%v\n", s2)
	println("-----------------------")
	// 查找
	s3, b := os.LookupEnv("env1")
	fmt.Printf("b: %v\n", b)
	fmt.Printf("s3: %v\n", s3)

	// 替换
	os.Setenv("NAME", "gopher")
	os.Setenv("BURROW", "/usr/gopher")
	
	println(os.ExpandEnv("$NAME lives in ${BURROW}."))
	
	// 清空环境变量
	//os.Clearenv()
}

小白Go语言请不要运行!!!!!


6.标准库io包之输入输出🛑

(1).基础介绍

Go语言中,为了方便开发者使用,将IO操作封装以下几个包

  • IO 为IO原语提供基本的接口
  • IO 封装了一些实用的IO函数
  • fmt实现格式化 IO,雷士c语言中的printf/scanf
  • bufio 实现带缓冲I/O

(2).基本的IO接口

io是接口 os实现了io接口

在io包中最重要的是两个接口: Reader和Writer接口。

Reader 接口

type Reader interface{
	read(p []byte){n int, err error}
}

Writer 接口

type Writer interface{
	write(p []byte){n int, err error}
}

(3).实现IO接口的类型

哪些类型实现了读和写的接口

1. os 实现了 ->读和写的接口
2. strings.Reader -> 读
3. bufio.Reader/Writer ->读写
4. bytes.Buffer ->读写
5. bytes.Reader -> 读
6. compress/gzip.Reader/StreamWriter ->读写
7. crypto/cipher.StreamReader/StreamWriter ->读写
8. crypto/tls.Conn ->读写
9. encoding/csv.Reader/Writer ->读写

第二个实现方式的

package main

import (
	"fmt"
	"strings"
)

func main() {

	reader := strings.NewReader("Hello world")
	buf := make([]byte, 20) //设置缓冲区
	reader.Read(buf)        // 读到缓冲区中
	fmt.Printf("%v", string(buf))
}

(4).IO实现拷贝文件

package main

import (
	"io"
	"log"
	"os"
	"strings"
)

func testCopy() {
	reader := strings.NewReader("hello world")
	// 也可以拷贝到一个文件中去
	_, err := io.Copy(os.Stdout, reader)  // 参数第一个是 输出到控制台  第二个是: 读取
	if err != nil {
		log.Fatal(err)
	}
}
func main() {

	testCopy()
}

在这里插入图片描述

7.标准库ioutil包🛑 【推荐】

(1).基础介绍

封装了一些实用的io函数
在这里插入图片描述

(2).实现方法

ReadAll

package main

import (
	"fmt"
	"io/ioutil"
	"log"
	"strings"
)

func main() {
	r := strings.NewReader("Go is a general-purpose language designed with systems programming in mind.")

	b, err := ioutil.ReadAll(r)  //读取全部
	if err != nil {
		log.Fatal(err)
	}

	fmt.Printf("%s", b)
}

在这里插入图片描述

  1. ReadDir

遍历目录

package main

import (
	"fmt"
	"io/ioutil"
	"log"
)

func main() {
	files, err := ioutil.ReadDir(".")
	if err != nil {
		log.Fatal(err)
	}

	for _, file := range files {
		fmt.Println(file.Name())
	}
}
  1. ReadFile

读取文件

package main

import (
	"fmt"
	"io/ioutil"
	"log"
)

func main() {
	content, err := ioutil.ReadFile("test.txt")
	if err != nil {
		log.Fatal(err)
	}

	fmt.Printf("File contents: %s", content)
}

在这里插入图片描述

WriteFile

写文件

package main

import (
	"io/ioutil"
	"log"
)

func main() {
	message := []byte("Hello, Gophers!")
	err := ioutil.WriteFile("hello", message, 0644)
	if err != nil {
		log.Fatal(err)
	}
}

在这里插入图片描述

8.标准库bufio包之读

(1).bufio的基础介绍

bufio包实现了有缓冲的I/O。它包装一个io.Readerio.Writer接口对象,创建另一个也实现了该接口,且同时还提供了缓冲和一些文本I/O的帮助函数的对象。

(2).bufio实列读取

  1. bufio.NerReader()放入缓冲区 -默认4096
package main

import (
	"bufio"
	"fmt"
	"strings"
)

func main() {
	reader := strings.NewReader("hello world") // 创建一个只能读的对象

	reader2 := bufio.NewReader(reader) // 利用bufio存放读的数据对象
	readString, _ := reader2.ReadString('\n') // 读出数据
	fmt.Printf("%v\n", readString)
}

2.bufio.NerReaderSize() 设置缓冲区大小

package main

import (
	"bufio"
	"fmt"
	"strings"
)

func main() {
	reader := strings.NewReader("hello world") // 创建一个只能读的对象

	reader2 := bufio.NewReaderSize(reader, 10) // 利用bufio存放读的数据对象
	readString, _ := reader2.ReadString('\n') // 读出数据
	fmt.Printf("%v\n", readString)
}

9.标准库bufio之写

(1).bufio.writer()

写完之后,我们要刷新缓存
pkg.go.dev/std

package main

import (
	"bufio"
	"os"
)

func main() {
	file, _ := os.OpenFile("test.txt", os.O_RDWR|os.O_APPEND, 777)
	writer := bufio.NewWriter(file)

	writer.WriteString("哈哈哈哈")
	writer.Flush() // 一定要刷新一下缓冲区
	defer file.Close()
}

在这里插入图片描述

10.标准库bufio之Scanner

(1). bufio.Scanner() 扫描

package main

import (
	"bufio"
	"fmt"
	"strings"
)

func main() {
	reader := strings.NewReader("ABC DEF GHI JKL")
	scanner := bufio.NewScanner(reader)
	scanner.Split(bufio.ScanWords) // 以空格输出
	for scanner.Scan() {           // 假如说扫描不到值就返回false
		fmt.Printf("%v", scanner.Text())
	}
}

在这里插入图片描述

11.标准库log包

(1).介绍

golang内置了 log 包,实现简单的日志服务。通过调用 log 包的函数,可以实现简单的日志打印功能。

log使用

log包有三个系列的日志打印函数,分别是print系列、panic系列、fatal系列。

  1. print : 单纯打印日志
  2. panic: 打印日志,抛出panic异常
  3. fatal: 打印/日志,强制结束程序(os.exit),defer函数不会执行。

(2).print 日志打印

package main

import "log"

func main() {
	log.Println("你好") //打印日志
	log.Printf("my log %d->", 100)
}

在这里插入图片描述

(3).panic 日志打印

就是在panic这个输出语句 输出之后,就会抛出异常。后面得语句就不会生效。除非定义在panic得方法和defer方法

package main

import "log"

func main() {
	defer println("11111111")
	log.Panicln("sdsd") //执行完这个语句之后,就抛出异常。后面的所有代码就失效了。
	println("sdsd")
}

在这里插入图片描述

(4).fatal 日志打印

package main

import "log"

func main() {
	defer println("11111111")  //不会被打印 直接中断
	log.Fatal("222222")
	println("3333333")
}

在这里插入图片描述

(5).进行设置日志配置

log.SetOutput(file)  //********日志配置**** 输出在这个文件中
package main

import (
	"log"
	"os"
)

func main() {
	// log.Llongfile  ->指出绝对路径
	// log.Lshortfile -> 指出文件的位置
	log.SetFlags(log.Ldate | log.Ltime | log.Lshortfile) // 日期和时间和文件的位置
	log.SetPrefix("jsxs: ")                              // 前缀
	file, err := os.OpenFile("test.txt", os.O_WRONLY|os.O_APPEND, 775)
	if err != nil {
		log.Println("文件有误")
	}
	log.SetOutput(file)  //********日志配置**** 输出在这个文件中
	log.Println("mylog...\n")
}

在这里插入图片描述

(6).自定义logge

log 包为我们提供了内置函数,让我们能够自定义logger。从效果上来看,就是将标题3中的标准配置日志、日之前缀配置、日志输出位置整合到一个函数中,使日志配置不在那么繁琐。

log 包中提供了 func New(out io.Writer, prefix string, flag int) *loger函数来实现定义logger

package main

import (
	"log"
	"os"
)

func main() {
	file, err := os.OpenFile("test.txt", os.O_WRONLY|os.O_APPEND|os.O_CREATE, 775)
	if err != nil {
		log.Println("文件有误")
	}
	logger := log.New(file, "MyLog:", log.Ltime|log.Ldate|log.Lshortfile)  // 设置格式
	logger.Println("112121212122")
}

在这里插入图片描述

12.标准库builtin ⭐

这个包提供了一些类型声明、变量和常量声明,还有一些便利的函数,这个包不需要导入,这些变量可以直接使用。

(1).常用函数

1.追加
append(): 切片追加
2.长度
len() : 数组/字符串/切片长度
3.控制台打印
print() println()
4.抛出异常
panic() 终止后面的程序

(2).new 函数 和 make 函数 ⭐

new 和 make 区别:

  1. make 只能用来分配初始化类型slice,map,chan的数据;new 可以分配任意类型的数据。
  2. new 分配返回的使指针,即*T; make 返回的是引用, 即T
  3. new 分配的空间被清零; make分配后会进行初始化

new

package main

import "fmt"

func main() {
	b := new(bool)
	fmt.Printf("%T\n", b)
	fmt.Printf("%v\n", *b)
	i := new(int)
	fmt.Printf("%T\n", i)
	fmt.Printf("%v\n", *i)
	s := new(string)
	fmt.Printf("%T\n", s)
	fmt.Printf("%v\n", *s)
}

在这里插入图片描述

make

内建函数make(T,args)new(T)的用途不一样,它只用来创建slice,map和channel,并且返回一个初始化的(而不是置零),类型为T的值(而不是*T)。之所以不同,是因为这三个类型的背后引用了使用前必须初始化的数据结构。列如: slice是一个三元描述符,包含一个指向数据 (在数组中)的指针,长度以及容量,在这些项被初始化之前,slic都是nil的,对于slice,map和channel,make初始化这些内部数据结构,并准备好可以的值。

make([]int,10,100) //分配100个容量,长度为10的整型数组
new([]int)//返回一个指向新分配的,被置零的slice结构体的指针,即指向nil的slice指针
package main

import "fmt"

func main() {
	var p *[]int = new([]int)
	fmt.Printf("%v\n", *p)
	ints := make([]int, 10)
	fmt.Printf("%v\n", ints)
}

在这里插入图片描述

13. 标准库bytes之常用函数

bytes包提供了对字节切片进行读写操作的一系列函数,字节切片处理的函数比较多。分为基本处理函数比较函数后缀检查函数索引函数分割函数大小写处理函数子切片处理函数

(1).字节切片与字符串的转换

字符串转成byt切片缓存

package main

import (
	"fmt"
	"os"
)

func main() {
	s := "黎鸣先生"
	bytes := []byte(s)
	file, _ := os.OpenFile("a.txt", os.O_RDWR|os.O_APPEND, 775)
	write, _ := file.Write(bytes)
	fmt.Printf("写入的字节数:%v\n", write)
}

在这里插入图片描述

(2).字节切片是否包含

package main

import (
	"bytes"
	"fmt"
	"strings"
)

func main() {
	s := "duoke360.com"
	b := []byte(s)
	b1 := []byte("duoke360")
	b2 := []byte("DuoKe360")
	strings.Contains("hello world", "hello")
	b3 := bytes.Contains(b, b1)
	fmt.Printf("%v\n", b3)
	b3 = bytes.Contains(b, b2)
	fmt.Printf("%v\n", b3)
}

在这里插入图片描述

(3).统计字节切片的某个字符串的个数

package main

import (
	"bytes"
)

func main() {

	b1 := []byte("helooooooo")
	s1 := []byte("h")
	s2 := []byte("e")
	s3 := []byte("o")
	println(bytes.Count(b1, s1))
	println(bytes.Count(b1, s2))
	println(bytes.Count(b1, s3))
}

在这里插入图片描述

(4).对字节切片进行重复输出

package main

import (
	"bytes"
)

func main() {

	b1 := []byte("helooooooo")

	println(string(bytes.Repeat(b1, 1)))
	println(string(bytes.Repeat(b1, 2)))
	println(string(bytes.Repeat(b1, 2)))
}

在这里插入图片描述

(5).对字节切片的替换

package main

import (
	"bytes"
)

func main() {

	b1 := []byte("helooooooo")
	old := []byte("o")
	news := []byte("e")
	println(string(bytes.Replace(b1, old, news, 0)))  // 替换0
	println(string(bytes.Replace(b1, old, news, 1))) // 替换1
	println(string(bytes.Replace(b1, old, news, 2))) //替换两个
	println(string(bytes.Replace(b1, old, news, -1)))  //全部替换

}

在这里插入图片描述

(6).查看文本中有几个汉字

package main

import (
	"bytes"
	"fmt"
)

func main() {

	s := []byte("你好世界")
	runes := bytes.Runes(s)
	fmt.Printf("转换前字符串的长度:%v\n", len(s))
	fmt.Printf("转后前字符串的长度:%v\n", len(runes))

}

在这里插入图片描述

(7).字节切片间的链接

package main

import (
	"bytes"
)

func main() {

	s2 := [][]byte{[]byte("你好"), []byte("世界")}
	s4 := []byte(",")
	println(string(bytes.Join(s2, s4)))
	s3 := []byte("#")
	println(string(bytes.Join(s2, s3)))
}

在这里插入图片描述

14.标准库bytes之Reader

Reader实现了 io.Reader、io.ReaderAt、io.WriterTo、io.Seeker、io.ByteScaaner、io.RuneScanner等接口,Reader是只读的,可以seek。

(1).常用方法

package main

import (
	"bytes"
	"fmt"
)

func main() {

	date := "123456789"
	// 通过[]byte创建Reader
	re := bytes.NewReader([]byte(date))
	// 返回来读取部分的长度
	fmt.Println("长度为: ", re.Len())
	//返回底层数据总长度
	println("底层数据长度: ", re.Size())
	println("---------")
	buf := make([]byte, 2)
	for {
		// 读取数据
		read, err := re.Read(buf) // read,就是指有多少字节数
		if err != nil {
			break
		}
		println(string(buf[:read])) //进行读取的操作
	}
	println("--------")
	//设置偏移量,因为上面的操作已经修改了读取位置等信息
	re.Seek(0, 0) // 重新定位,定位到开头  ----- 如果不重新定位的话,会接着上面的buf继续读取
	for true {
		// 一个字节一个字节的读取
		b, err := re.ReadByte()
		if err != nil {
			break
		}
		println(string(b))
	}
	println("-----")

	re.Seek(0, 0)
	off := int64(0) // 为0
	for true {
		// 指定偏移量读取
		at, err := re.ReadAt(buf, off) //   每次读取两个
		if err != nil {
			break
		}
		off += int64(at) //
		println(off, string(buf[:at]))
	}
}

15. 标准库之bytes之Buffer

缓冲区具有读取和写入方法的可变大小的字节缓冲区。Buffer的零值是准备使用的空缓冲区。

(1).基本用法

声明一个Buffer的四种方法

var b bytes.Buffer  //直接定义一个BUffer变量,不用初始化
b:=new(bytes.Buffer) //使用new返回Buffer
b:=bytes.NweBuffer(s []byte)  // 从一个[]byte切片,构造一个Buffer
b:=bytes.NewBufferString(s string) //从一个string 构造一个Buffer

往Buffer中写入数据

b.Write(d []byte)  // 姜切片写入Buffer尾部
b.WriteString(s string) // 将字符串s写入Buffr尾部
b.WriteByte(c byte) //将字符串c写入Buffer尾部
b.WriteRune(r rune) // 将一个rune类型的数据放到缓冲器的尾部
b.WriteTo(w io.Writer) //将Buffer中内容输出到实现了io.Writer接口的可写入对象中

(2).Buffer的定义和实现

package main

import (
	"bytes"
	"fmt"
)

func main() {
	var b bytes.Buffer
	fmt.Printf("b: %v\n", b)                   // 第一个是切片 第二个是偏移量 第三个是读的位置 - {[] 0 0}
	b1 := bytes.NewBufferString("Hello world") // 返回的值字符串缓冲区
	fmt.Printf("%v\n", b1)
	buffer := bytes.NewBuffer([]byte("你好 世界"))
	fmt.Printf("b2: %v\n", buffer)
}

在这里插入图片描述

(3).写入并读出

package main

import (
	"bytes"
	"fmt"
)

func main() {
	var b bytes.Buffer
	writeString, err := b.WriteString("hello")
	if err != nil {
		fmt.Printf("%v\n", err)
	}
	fmt.Printf("%v\n", writeString)
	i := b.Bytes()
	fmt.Printf("%v\n", string(i))
}

在这里插入图片描述

16.标准库之errors

errors包实现了错误的函数,Go语言使用error类型来返回函数执行过程中遇到的错误,如果返回的errror值为nil,则表示遇到错误,否则errror会返回一个字符串,用于说明遇到了什么错误。

type error interface{
	Errorr()
}

我们可以用任何类型去实现它,(只要添加一个Error方法即可)。error不一定表示一个错误,它可以表示任何的信息,不一定是字符串。比如io包中使用error类型的 io.EOF表示数据读取结束,而不是遇到了什么错误。

(1). errors.new(“”)

errors包实现了一个最简单的errror类型,只包含一个字符串,它可以记录大多数情况遇到的错误信息。errors包的用法也很简单,只有一个New函数,只有一个 New 函数,用于生成一个最简单的error对象

package main

import (
	"errors"
	"fmt"
)

func check(s string) (string, error) {
	if s == "" {
		err := errors.New("字符串不能为空")  //创建一个简单的报错信息
		return "nil", err
	} else {
		return s, nil
	}
}

func main() {
	s, err := check("")
	fmt.Printf("字符串是: %v\t 报错信息: %v\n", s, err)
}

在这里插入图片描述

(2).自定义错误

package main

import (
	"fmt"
	"time"
)

type MyError struct {
	when time.Time
	what string
}

func (my MyError) Error() string { //  结构体实现Error的接口,
	return fmt.Sprintf("%v;%v", my.when, my.what) //  这里打印的是一个字符串
}

func opps() error { //返回错误的对象
	return MyError{
		time.Now(),
		"这个文件已经过期!",
	}
}
func main() {
	if err := opps(); err != nil {
		fmt.Printf("%v", err)
	}
}

在这里插入图片描述

17.标准库之sort

sort包提供了排序切片和用户自定义数据集以及相关功能的函数。
sort包主要针对 []int、 []float64、[]string、以及其他自定义切片的排序。

(1).常用方法

在这里插入图片描述

18.标准库之json

这个包可以实现json的编码和解码,就是将json字符串转换为struct,或者将struct转换成json。

核心的两个函数

  1. 将struct编写成json,可以接受任意类型
func Marshal(v interface{}) ([]byte,error)
  1. 将json转换成struct结构体
func Unmarshal(data []byte, v interface{}) error

两个核心结构体

从输入流读取并解析json

type Decode struct{}

写json到输出流

type Encoder struct{}

(1).结构体转json

package main

import (
	"encoding/json"
	"fmt"
)

type Person struct {
	Name  string
	Age   int
	Email string
}

func main() {
	person := Person{"tom", 20, "2261203961@qq.com"}
	marshal, _ := json.Marshal(person)
	fmt.Printf("%v", string(marshal))
}

在这里插入图片描述

(2).json转换结构体

也可以解析嵌套结构体

package main

import (
	"encoding/json"
	"fmt"
)

type Person struct {
	Name  string
	Age   int
	Email string
}

func main() {
	bytes := []byte(`{"Name":"tom","Age":20,"Email":"2261203961@qq.com"}`) // 这里是反引号 不是单引号
	var m Person
	json.Unmarshal(bytes, &m)
	fmt.Printf("%v\n", m)
}

在这里插入图片描述

19.标准库之encoding/xml

核心的两个函数
将struct转换成xml

func Marshal(v interface{}) ([]byte,error)

将xml转换成struct结构体

func Unmarshal(data []byte, v intreface{}) error

两个核心结构体

从输入流读取并解析xml

type Decoder struct{..}

写xml到输出流

type Encoder structer{..}

(1).结构体转xml

package main

import (
	"encoding/xml"
	"fmt"
)

type Person struct {
	XMLName xml.Name `xml:"person"` //反引号
	Name    string   `xml:"name"`
	Age     int      `xml:"age"`
	Email   string   `xml:"email"`
}

func main() {
	person := Person{
		Name:  "tom",
		Age:   20,
		Email: "sdsds.com",
	}
	b, _ := xml.MarshalIndent(person, " ", " ") //美化
	fmt.Printf("%v", string(b))
}

在这里插入图片描述

(2).xml转结构体

package main

import (
	"encoding/xml"
	"fmt"
)

type Person struct {
	XMLName xml.Name `xml:"person"` //反引号
	Name    string   `xml:"name"`
	Age     int      `xml:"age"`
	Email   string   `xml:"email"`
}

func main() {
	s := `
	<person>
  <name>tom</name>
  <age>20</age>
  <email>sdsds.com</email>
 </person>
	`
	bytes := []byte(s)
	var per Person
	xml.Unmarshal(bytes, &per)
	fmt.Printf("%v", per)
}

在这里插入图片描述