Golang过渡期
- (一)、Golang包
- (二)、Golang并发编程
-
- 1.并发编程之协程 go
- 2.并发编程之通道 channel
- 3.并发编程之WaitGroup实现同步
- 4.并发编程之runtime包
- 5.并发编程之Mutex互斥锁实现同步
- 6.并发编程之channel的遍历
- 7.并发编程之select
- 8.并发编程之Timer
- 9.并发编程之Ticker
- 10.并发编程之原子变量的引入
- 11.并发编程之原子操作详解
- (三)、Golang标准库
- 1.标准库os模块之文件目录
- 2.标准库os模块之文件读操作
- 3.标准库os模块之文件写操作
- 4.标准库os包之进程 💢
- 5.标准库os包之环境相关
- 6.标准库io包之输入输出🛑
- 7.标准库ioutil包🛑 【推荐】
- 8.标准库bufio包之读
- 9.标准库bufio之写
- 10.标准库bufio之Scanner
- 11.标准库log包
- 12.标准库builtin ⭐
- 13. 标准库bytes之常用函数
- 14.标准库bytes之Reader
- 15. 标准库之bytes之Buffer
- 16.标准库之errors
- 17.标准库之sort
- 18.标准库之json
- 19.标准库之encoding/xml
(一)、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).包注意事项
-
一个文件夹只能有一个package
- import后面的其实是GOPATH开始的相对路径,包括最后一段。但由于一个目录只能有一个package,所以 import 一个路径就等于是 import 了这个路径下的包。
-
一个package的文件不能在多个文件夹下
- 如果多个文件夹下面有重名的 package,他们其实是彼此无关的pacakage
- 如果一个go文件需要同时使用不同目录下的同名 pacakage, 需要在 import 这些目录时为每一个目录指定一个package的别名。
2.包管理工具 go module
(1).简介
go module是 golang1.11新添加的特性,用来管理模块中包的依赖关系
(2).go mod使用方法
- 初始化模块
go mod init <项目模块名>
- 依赖关系处理,很具go.mod文件
go mod tidy
- 将依赖包复制到项目下的vendor目录
go mod vendor
如果包被屏蔽(墙),可以使用这个命令,随后使用 go build -mod=vendor 编译
- 显示依赖关系
go list -m all
- 显示详细依赖关系
go list -m -json all
- 下载依赖
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).通道的发送和接受
- 通道的发送
通道变量名<-xxxx
- 通道的接受
xxx:=<-通道变量名
(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).基础介绍
- select是Go中的一个控制层结构,类似于
switch语句,用于处理异步IO操作。select会监听case语句中channel的读写操作,当case中的channel读写操作为非阻塞状态(即能读写)时,将会触发相应的动作。
1.select 中的case语句必须是一个channel操作
2.select 中的default子句总是可以运行的
- 如果有多个case都可以运行,select会随机公平地选出一个执行,其他不会执行
- 如果没有可运行地 case 语句,且有 default 语句,那么就会执行 default地动作。
- 如果没有可运行地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).定时器的实列
- 利用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)
}
- 不设置变量名进行定时阻塞
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())
}
- 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实列
- 基础使用
每隔两秒打印一下
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创建文件目录
- 第一种创建单极目录
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()
}
- 第二种创建联机目录
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删除文件
- 删除文件信息
package main
import (
"fmt"
"os"
)
/*
利用GO语言进行操作 -> 操作系统
*/
func remove() {
err := os.Remove("test.txt")
if err != nil {
println(err)
} else {
fmt.Printf("删除成功!")
}
}
func main() {
remove()
}
- 联机删除文件目录
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从某一个文件中读或者写
- 文件的读取
文件读取的时候是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()
}
- 写的操作
字节数组传入的操作....
package main
import (
"os"
)
/*
利用GO语言进行操作 -> 操作系统
*/
func write() {
s := "你好,全名制作人"
os.WriteFile("test.txt", []byte(s), os.ModePerm) // 第一个参数是写哪里,第二个参数是内容(需要转换为byte),第三个是权限设置
}
func main() {
write()
}
2.标准库os模块之文件读操作
这里结束和file结构体相关的文件读操作
(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()
}
- 假如为空就创建
文件 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
- 第一种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()
}
- 第二种空间足够大 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()
}
- 从指定的字节开始读取 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()
}
- 遍历一个目录
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()
}
- 定位的操作
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)
}
- 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())
}
}
- 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.Reader或io.Writer接口对象,创建另一个也实现了该接口,且同时还提供了缓冲和一些文本I/O的帮助函数的对象。
(2).bufio实列读取
- 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系列。
- print : 单纯打印日志
- panic: 打印日志,抛出
panic异常 - 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 区别:
- make 只能用来
分配及初始化类型为slice,map,chan的数据;new 可以分配任意类型的数据。 - new 分配
返回的使指针,即*T; make返回的是引用, 即T - 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。
核心的两个函数
- 将struct编写成json,可以接受任意类型
func Marshal(v interface{}) ([]byte,error)
- 将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)
}