iota 的使用
关于 init() 函数有几个需要注意的地方:
init() 函数是用于程序执行前做包的初始化的函数,比如初始化包里的变量等;
一个包可以出线多个 init() 函数,一个源文件也可以包含多个 init() 函数;
同一个包中多个 init() 函数的执行顺序没有明确定义,但是不同包的init函数是根据包导入的依赖关系决定的(看下图);
init() 函数在代码中不能被显示调用、不能被引用(赋值给函数变量),否则出现编译错误;
一个包被引用多次,如 A import B,C import B,A import C,B 被引用多次,但 B 包只会初始化一次;
引入包,不可出现死循坏。即 A import B,B import A,这种情况编译失败;
cap() 可以测量切片最长可以达到多少
指针的变量指向变量的内存地址
var ip *int
package main //定义包名
import(
"fmt"
)
func main(){
a:=100//定义变量
var pt * int //定义指针
pt=&a //变量的内存地址赋值给指针 定义指针和赋值可写为 pt:=&a
fmt.Println(a)//输出100
fmt.Println(pt)//输出a的内存地址
fmt.Println(*pt)//输出100
}
package main //定义包名
import(
"fmt"
)
fuc test(a,b int){
t:=*a
*a=*b
*b=t
}
func main(){
a:=5
b:=40
test(&a,&b)
//简短模式的规范
//1.必须使用显示初始化;
//2.不能提供数据类型,编译器会自动推导;
//3.只能在函数内部使用简短模式;
fmt.Println(a,b)//输出 40 5
}
package main //定义包名
import(
"fmt"
)
func main(){
str:=new(string)//定义指针 string类型 也可定义其他类型
*str="go语言"
fmt.Println(str)//输出内存地址
fmt.Println(*str)//输出“go语言”
}
定义类型别名与定义类型
type newint int // 定义类型 NewInt 会形成一种新的类型,NewInt 本身依然具备 int 类型的特性。
type goo= int //定义类型别名
//非本地类型不能定义方法,
查看http://c.biancheng.net/view/25.html
//一维数组
var a[3] int //定义数组
a=[3]int{1,2,3}//赋值
//也可写为
a:=[3]int{1,2,3}
//二维数组
a:=[2][2]int{{1,2},{1,3}}
自我理解相当与取数组中的某个区间的值
切片、函数、MAP不能做比较!
a:=[3]int{1,2,3}//数组
fmt.Println(a[1:2]) //1是开始位置,2是结束位置 对应的是数组的索引 a[1:2]切片
//也可自定义切片
a:=[]int{1,2}
//多维切片
a:=[][]int{{1,2},{3}}
//make函数生成切片
make( []Type, size, cap )
其中 Type 是指切片的元素类型,size 指的是为这个类型分配多少个元素,cap 为预分配的元素数量,这个值设定后不影响 size,只是能提前分配空间,降低多次分配空间造成的性能问题
//例子
a:=make([]int,2,10)
fmt.Println(a)//输出[0,0]
//从数组或切片生成新的切片拥有如下特性:
//取出的元素数量为:结束位置 - 开始位置;
//取出元素不包含结束位置对应的索引,切片最后一个元素使用 slice[len(slice)] 获取;
//当缺省开始位置时,表示从连续区域开头到结束位置;
//当缺省结束位置时,表示从开始位置到整个连续区域末尾;
//两者同时缺省时,与切片本身等效;
//两者同时为 0 时,等效于空切片,一般用于切片复位。
//追加
append()
//例子
a:=[]int{1,2}
a=append(a,1)
fmt.Println(a)//输出[1,2,1]
a=append(a,1,2,3)
fmt.Println(a)//输出[1,2,1,2,3]
a=append(a,[]int{3,4}...)//追加切片,尾部的三个点是必须的
fmt.Println(a)//输出[1,2,3,4]
a=append([]int{3,4},a...)//在首部添加
fmt.Println(a)//输出[3,4,1,2]
a=append(a[:i],append([]int{3,5},a[i:]...)...)//指定位置添加
fmt.Println(a)//假如i=1输出[1,3,5,2]
copy()//复制
b:=[]int{1}
a:=[] int{2,3,4,6}
copy(a,b)
fmt.Println(a)//输出[1,3,4,6]只会复制b一个元素到a的前一个位置
copy(b,a)
fmt.Println(b)//输出[2]只会复制a前一个元素到b中
//删除元素
a:=[]int{1,2,3}
a=a[1:]
fmt.Println(a)//输出[2,3]
a=append(a[1:],a[:2]...)
fmt.Println(a)//输出[2,3]
range//循环迭代
a:=[]int{1,2,3}
for index,value:=range a{
fmt.Printf("Index: %d Value: %d\n", index, value)
}
//输出 index:0 value:1,index:1 value:2,index:2 value:3
//1)知识点:nil 切片和空切片。nil 切片和 nil 相等,一般用来表示一个不存在的切片;空切片和 nil不
//相等,表示一个空的集合
//2) 操作符 [i:j:k],k 主要是用来限制切片的容量
var maptest map[int]int // maptest映射名程 [int]索引类型 int值类型
make(map[int]int)//make新建映射
//for循环
for i,v:range maptest{
fmt.Println(i,v)
}
//map删除
delete('映射名称','索引')
无须初始化,直接声明即可。
sync.Map 不能使用 map 的方式进行取值和设置等操作,而是使用 sync.Map 的方法进行调用,Store 表示存
储,Load 表示获取,Delete 表示删除。
使用 Range 配合一个回调函数进行遍历操作,通过回调函数返回内部遍历出来的值,Range 参数中回调函数的返回
值在需要继续迭代遍历时,返回 true,终止迭代遍历时,返回 false。
import(
"fmt"
"sync"
)
var a sync.Map//定义
a.Store("name",33)//保存键值
a.Store("name",33)
a.Store("name",33)
a.Load("name")//读取
a.Delete("name")//删除 删除不存在的值不会报错
//遍历
a.Range(func(i, v interface{}) bool{
fmt.Println(i,v)
return true
})
import "container/list"
func main() {
l := list.New()
// 尾部添加
l.PushBack("canon")
// 头部添加
l.PushFront(67)
// 尾部添加后保存元素句柄
element := l.PushBack("fist")
// 在fist之后添加high
l.InsertAfter("high", element)
// 在fist之前添加noon
l.InsertBefore("noon", element)
// 删除
l.Remove(element)
//循环
for i := l.Front(); i != nil; i = i.Next() {
fmt.Println(i.Value)
}
//使用 for 语句进行遍历,其中 i:=l.Front() 表示初始赋值,只会在一开始执行一次,每次循环会进行一次 i != nil 语句判断,如果返回 false,表示退出循环,反之则会执行 i = i.Next()。
}
if condition {
do...
}
//特殊写法
if err:=conetent(); err!=nil{
do...
return
}
//content()是一个函数返回一个值,err!=nil才是判断条件
for i:=1;i>0;i--{
do...
}
//i:=1 i>0 可以省略但是;必须写
for i,v:=range str{
do...
}
//遍历数组、切片、字符串、列表、map(映射)
//i可以省略为_
var a = "hello"
switch a {
case "hello":
fmt.Println(1)
case "world":
fmt.Println(2)
default:
fmt.Println(0)
}
for i:=1;i>0;i--{
for i:=1;i>0;i--{
goto breakHere
}
}
return
breakHere
do...
//可直接跳出循环
//汇总错误
err := firstCheckError()
if err != nil {
goto onExit
}
err = secondCheckError()
if err != nil {
goto onExit
}
fmt.Println("done")
return
onExit:
fmt.Println(err)
exitProcess()
支持返回多个参数
func main(){
c,d:=test(1,2)
}
func test(a,b int)(c,d int){
return 1,2
}
//a b是形参 c d是返回值
支持返回多个参数
func main(){
var t func()
t=test()
c,d:=t(1,2)//相当于调用test()
}
func test(a,b int)(c,d int){
return 1,2
}
//a b是形参 c d是返回值
Go语言中闭包是引用了自由变量的函数,被引用的自由变量和函数一同存在,即使已经离开了自由变量的环境也不会
被释放或者删除,在闭包中可以继续使用这个自由变量,因此,简单的说:函数 + 引用环境 = 闭包
//在闭包内部修改引用的变量
// 准备一个字符串
str := "hello world"
// 创建一个匿名函数
foo := func() {
// 匿名函数中访问str
str = "hello dude"
}
// 调用匿名函数
foo()//输出hello dude
//闭包的记忆效应
func test(value int) func() int{
return func() int{
value++
return value
}
}
func main(){
a:=test(1)
fmt.Println(a())//输出2
fmt.Println(a())//输出3
}
闭包的记忆效应被用于实现类似于设计模式中工厂模式的生成器,相当于模板
// 创建一个玩家生成器, 输入名称, 输出生成器
func playerGen(name string) func() (string, int) {
// 血量一直为150
hp := 150
// 返回创建的闭包
return func() (string, int) {
// 将变量引用到闭包中
return name, hp
}
}
func main() {
// 创建一个玩家生成器
generator := playerGen("high noon")
// 返回玩家的名字和血量
name, hp := generator()
// 打印值
fmt.Println(name, hp)
}
//当函数的参数数量不确定,
func test(a ...int){
}
//当函数参数类型和数量不确定
func test(a ...interface{}){
}
//可变参数变量是一个包含所有参数的切片,如果要将这个含有可变参数的变量传递给下一个可变参数函数,可以在传递时给可变参数变量后面添加...,这样就可以将切片中的元素进行传递,而不是传递可变参数变量本身。
func test(a ...interface{}){
}
func print(a ...interface{}){
test(a...)//
}
func mian(){
print(1,2,3)
}
func main(){
fmt.Println("bengin")
defer fmt.Println(1)
defer fmt.Println(2)
defer fmt.Println(3)
fmt.Println("end")
//依次输出
bengin
end
3
2
1
}
//手动触发宕机
package main
func main() {
panic("crash")
}
//代码运行崩溃并输出如下:
//panic: crash
//goroutine 1 [running]:
//main.main()
// D:/code/main.go:4 +0x40
//exit status 2
//在运行依赖的必备资源缺失时主动触发宕机
//regexp 是Go语言的正则表达式包,正则表达式需要编译后才能使用,而且编译必须是成功的,表示正则表达式可用。
//编译正则表达式函数有两种,具体如下:
//1) func Compile(expr string) (*Regexp, error)
//编译正则表达式,发生错误时返回编译错误同时返回 Regexp 为 nil,该函数适用于在编译错误时获得编译错误进//行处理,同时继续后续执行的环境。
//2) func MustCompile(str string) *Regexp
//当编译正则表达式发生错误时,使用 panic 触发宕机,该函数适用于直接使用正则表达式而无须处理正则表达式错误的情况。
//MustCompile 的代码如下:
func MustCompile(str string) *Regexp {
regexp, error := Compile(str)
if error != nil {
panic(`regexp: Compile(` + quote(str) + `): ` + error.Error())
}
return regexp
}
//代码说明如下:
//第 1 行,编译正则表达式函数入口,输入包含正则表达式的字符串,返回正则表达式对象。
//第 2 行,Compile() 是编译正则表达式的入口函数,该函数返回编译好的正则表达式对象和错误。
//第 3 和第 4 行判断如果有错,则使用 panic() 触发宕机。
//第 6 行,没有错误时返回正则表达式对象。
//当 panic() 触发的宕机发生时,panic() 后面的代码将不会被运行,但是在 panic() 函数前面已经运行过的 defer 语句依然会在宕机发生时发生作用,参考下面代码:
package main
import "fmt"
func main() {
defer fmt.Println("宕机后要做的事情1")
defer fmt.Println("宕机后要做的事情2")
panic("宕机")
}
//Recover 是一个Go语言的内建函数,可以让进入宕机流程中的 goroutine 恢复过来,recover 仅在延迟函数
//defer 中有效,在正常的执行过程中,调用 recover 会返回 nil 并且没有其他任何效果,如果当前
//的 goroutine 陷入恐慌,调用 recover 可以捕获到 panic 的输入值,并且恢复正常的执行。
package main
import (
"fmt"
"runtime"
)
// 崩溃时需要传递的上下文信息
type panicContext struct {
function string // 所在函数
}
// 保护方式允许一个函数
func ProtectRun(entry func()) {
// 延迟处理的函数
defer func() {
// 发生宕机时,获取panic传递的上下文并打印
err := recover()
switch err.(type) {
case runtime.Error: // 运行时错误
fmt.Println("runtime error:", err)
default: // 非运行时错误
fmt.Println("error:", err)
}
}()
entry()
}
func main() {
fmt.Println("运行前")
// 允许一段手动触发的错误
ProtectRun(func() {
fmt.Println("手动宕机前")
// 使用panic传递上下文
panic(&panicContext{
"手动触发panic",
})
fmt.Println("手动宕机后")
})
// 故意造成空指针访问错误
ProtectRun(func() {
fmt.Println("赋值宕机前")
var a *int
*a = 1
fmt.Println("赋值宕机后")
})
fmt.Println("运行后")
}
/**代码输出结果:
运行前
手动宕机前
error: &{手动触发panic}
赋值宕机前
runtime error: runtime error: invalid memory address or nil pointer dereference
运行后
对代码的说明:
第 9 行声明描述错误的结构体,保存执行错误的函数。
第 17 行使用 defer 将闭包延迟执行,当 panic 触发崩溃时,ProtectRun() 函数将结束运行,此时 defer 后的闭包将会发生调用。
第 20 行,recover() 获取到 panic 传入的参数。
第 22 行,使用 switch 对 err 变量进行类型断言。
第 23 行,如果错误是有 Runtime 层抛出的运行时错误,如空指针访问、除数为 0 等情况,打印运行时错误。
第 25 行,其他错误,打印传递过来的错误数据。
第 44 行,使用 panic 手动触发一个错误,并将一个结构体附带信息传递过去,此时,recover 就会获取到这个结构体信息,并打印出来。
第 57 行,模拟代码中空指针赋值造成的错误,此时会由 Runtime 层抛出错误,被 ProtectRun() 函数的 recover() 函数捕获到。*/
panic 和 recover 的组合有如下特性:
有 panic 没 recover,程序宕机。
有 panic 也有 recover,程序不会宕机,执行完对应的 defer 后,从宕机点退出当前函数后继续执行。
package main
import(
"fmt"
"time"
)
func test(){
start:=time.Now()
sum:=1
for i:=1;i<10000;i++{
sum++
}
end:=time.Since(start)//也可写为end:=time.Now().Sub(start)
fmt.Println(end)
}
func main(){
test()//输出运行时间
}
#函数功能测试
//index.go
package main
import ()
func main(){
}
func Ga(width,height int) int{
return width*height
}
//index_test.go
package main
import(
"testing"
)
func TestGa(t*testing.T){
a:=Ga(100,300)
if a!=30000{
t.Error("测试失败")
}
}
//1.文件名规范:index.go index_test.go 测试文件名=测试文件名+_test
//2.包名一致
//3.函数名以TEST+测试的函数名
//开始测试
//go test 报错go.mod file not found in current directory or any parent directory; see 'go
//go env 查看GO111MODULE="" 修改 export GO111MODULE="on"
// go test 文件夹名
//[root@iZ2cy7Z src]# go test
//PASS
//ok src 0.003s
#函数压力测试
//修改index_test.go
func BenchmarkGa(t*testing.B){
for i:=1;i<t.N;i++{
Ga(100,300)
}
}
//[root@iZ2cy7Z src]# go test -bench="."
//goos: linux
//goarch: amd64
//pkg: src
//cpu: Intel(R) Xeon(R) CPU E5-2682 v4 @ 2.50GHz
//BenchmarkGa 1000000000 0.4376 ns/op
//PASS
//ok src 0.480s
#覆盖率测试
func TestGa(t*testing.T){
a:=Ga(100,300)
if a!=30000{
t.Error("测试失败")
}
}
func BenchmarkGa(t*testing.B){
for i:=1;i<t.N;i++{
Ga(100,300)
}
}
//[root@iZ2cy7Z src]# go test -cover
//PASS
//coverage: 100.0% of statements
//ok src 0.003s
//测试最好覆盖100%
#定义:自定义的方式形成新的类型,结构体就是这些类型中的一种复合类型,结构体是由零个或多个任意类型的值聚合
成的实体,每个值都可以称为结构体的成员。
结构体的定义格式如下:
type 类型名 struct {
字段1 字段1类型
字段2 字段2类型
…
}
对各个部分的说明:
类型名:标识自定义结构体的名称,在同一个包内不能重复。
struct{}:表示结构体类型,type 类型名 struct{}可以理解为将 struct{} 结构体定义为类型名的类型。
字段1、字段2……:表示结构体字段名,结构体中的字段名必须唯一。
字段1类型、字段2类型……:表示结构体各个字段的类型。
#实例化
//基本的实例化形式
//var ins T 其中,T 为结构体类型,ins 为结构体的实例。
type Point struct {
X int
Y int
}
var p Point
p.X = 10
p.Y = 20
//创建指针类型的结构体
ins := new(T)
//其中:
//T 为类型,可以是结构体、整型、字符串等。
//ins:T 类型被实例化后保存到 ins 变量中,ins 的类型为 *T,属于指针。
type Player struct{
Name string
HealthPoint int
MagicPoint int
}
tank := new(Player)
tank.Name = "Canon"
tank.HealthPoint = 300
//取结构体的地址实例化
ins := &T{}
//其中:
//T 表示结构体类型。
//ins 为结构体的实例,类型为 *T,是指针类型。
type Command struct {
Name string // 指令名称
Var *int // 指令绑定的变量
Comment string // 指令的注释
}
var version int = 1
cmd := &Command{}
cmd.Name = "version"
cmd.Var = &version
cmd.Comment = "show version"
#go语言类型内嵌和结构体内嵌
package main
import "fmt"
type A struct {
ax, ay int
}
type B struct {
A
bx, by float32
}
func main() {
b := B{A{1, 2}, 3.0, 4.0}
fmt.Println(b.ax, b.ay, b.bx, b.by)
fmt.Println(b.A)
}
//输出:
1 2 3 4
{1 2}
//结构体中有相同命名要标识清楚
type A struct {
a int
}
type B struct {
a int
}
type C struct {
A
B
}
func main() {
c := &C{}
c.A.a = 1
fmt.Println(c)
}
//知识点[链接](https://blog.csdn.net/qq_35902556/article/details/52452577)
//1)
type A struct{
name int
}
func(a A)B(){
a.name=5
}
func(a *A)C(){
a.name=9
}
func main(){
c:=A{8}
c.B()
fmt.Println(c.name)//输出8 (a A)是值类型接收者 引用name 改变不了原来的值
c.C()
fmt.Println(c.name)//输出9 (a A)是指针类型接收者
}
#定义:链表是一种物理存储单元上非连续、非顺序的存储结构,数据元素的逻辑顺序是通过链表中的指针链接次
序实现的。分为三种单向链表,双向链表,循环链表
//单向链表中每个结点包含两部分,分别是数据域和指针域,上一个结点的指针指向下一结点,依次相连,形成链表
package main
import "fmt"
//定义单向链表
type Node struct {
data int
next *Node
}
func Shownode(p *Node) { //遍历
for p != nil {
fmt.Println(*p)
p = p.next //移动指针
}
}
func main() {
var head = new(Node)
head.data = 1
var node1 = new(Node)
node1.data = 2
head.next = node1
var node2 = new(Node)
node2.data = 3
node1.next = node2
Shownode(head)
}
//运行结果如下:
{1 0xc00004c1e0}
{2 0xc00004c1f0}
{3 <nil>}
//双向链表
单向链表只有一个方向,结点只有一个后继指针 next 指向后面的结点。而双向链表,顾名思义它支持两个方向,每
个结点不止有一个后继指针 next 指向后面的结点,还有一个前驱指针 prev 指向前面的结点。
//循环链表
循环链表跟单链表唯一的区别就在尾结点。单向链表的尾结点指针指向空地址,表示这就是最后的结点了,而循环链表的尾结点指针是指向链表的头结点,它像一个环一样首尾相连,所以叫作“循环”链表,如下图所示。
#ReadWriter 对象
ReadWriter 对象可以对数据 I/O 接口 io.ReadWriter 进行输入输出缓冲操作,ReadWriter 结构定义如下:
type ReadWriter struct {
*Reader
*Writer
}
默认情况下,ReadWriter 对象中存放了一对 Reader 和 Writer 指针,它同时提供了对数据 I/O 对象的读写
缓冲功能。
可以使用 NewReadWriter() 函数创建 ReadWriter 对象,该函数的功能是根据指定的 Reader 和 Writer
创建一个 ReadWriter 对象,ReadWriter 对象将会向底层 io.ReadWriter 接口写入数据,或者从
io.ReadWriter 接口读取数据。该函数原型声明如下:
func NewReadWriter(r *Reader, w *Writer) *ReadWriter
在函数 NewReadWriter() 中,参数 r 是要读取的来源 Reader 对象,参数 w 是要写入的目的 Writer 对象。
#Reader对象
Reader 对象可以对数据 I/O 接口 io.Reader 进行输入缓冲操作,Reader 结构定义如下:
type Reader struct {
//contains filtered or unexported fields
)
默认情况下 Reader 对象没有定义初始值,输入缓冲区最小值为 16。当超出限制时,另创建一个二倍的存储空间。
//创建Reader对象
//俩种方式:NewReader() NewReadersize()
##操作 Reader 对象
操作 Reader 对象的方法共有 11 个,分别是 Read()、ReadByte()、ReadBytes()、ReadLine()、
ReadRune ()、ReadSlice()、ReadString()、UnreadByte()、UnreadRune()、Buffered()、Peek()
###Read()
定义:Read() 方法的功能是读取数据,并存放到字节切片 p 中
结构:func (b *Reader) Read(p []byte) (n int, err error)
package main
import(
"fmt"
"bytes"
"bufio"
)
func main(){
data:=[]byte("go语言非常好")//string转换为byte
ra:=bytes.NewReader(data)
a:=bufio.NewReader(ra)
var tr [128]byte
n,err:=a.Read(tr[:])
fmt.Println(string(tr[:n]),n,err)//string(tr[:n]) byte转string
}
### ReadByte()
定义:ReadByte() 方法的功能是读取并返回一个字节,如果没有字节可读,则返回错误信息。该方法原型如下:
结构:func (b *Reader) ReadByte() (c byte,err error)
func main(){
data:=[]byte("go语言非常好")//string转换为byte
ra:=bytes.NewReader(data)
a:=bufio.NewReader(ra)
var tr [128]byte
c,err:=a.ReadByte()
fmt.Println(string(c),err)//string(tr[:n]) byte转string 输出g
}
### ReadBytes()
定义:ReadBytes() 方法的功能是读取数据直到遇到第一个分隔符“delim”,并返回读取的字节序列(
包括“delim”)。
结构:func (b *Reader) ReadBytes(delim byte) (line []byte, err error)
func main(){
data:=[]byte("go语言非常好,go语言入门")//string转换为byte
ra:=bytes.NewReader(data)
a:=bufio.NewReader(ra)
var delim =','
line ,err:=a.ReadBytes(delim )
fmt.Println(string(line ),err)//string(tr[:n]) byte转string 输出go语言非常好,
}
###ReadLine()
定义:ReadLine() 是一个低级的用于读取一行数据的方法,大多数调用者应该使用 ReadBytes('\n') 或者
ReadString('\n')。ReadLine 返回一行,不包括结尾的回车字符,如果一行太长(超过缓冲区长度),参数
isPrefix 会设置为 true 并且只返回前面的数据,剩余的数据会在以后的调用中返回。
结构:func (b *Reader) ReadLine() (line []byte, isPrefix bool, err error)
func main(){
data:=[]byte("go语言非常好.\r\n.go语言入门")//string转换为byte
ra:=bytes.NewReader(data)
a:=bufio.NewReader(ra)
line ,isPrefix ,err:=a.ReadBytes(delim )
fmt.Println(string(line ),err)//string(tr[:n]) byte转string 输出go语言非常好
}
### ReadRune() 方法
定义:ReadRune() 方法的功能是读取一个 UTF-8 编码的字符,并返回其 Unicode 编码和字节数。如果编码
错误,ReadRune 只读取一个字节并返回 unicode.ReplacementChar(U+FFFD) 和长度 1。
结构:func (b *Reader) ReadRune() (r rune, size int, err error)
func main(){
data:=[]byte("go语言非常好")//string转换为byte
ra:=bytes.NewReader(data)
a:=bufio.NewReader(ra)
line ,size ,err:=a.ReadRune()
fmt.Println(string(line),size,err)//string(tr[:n]) byte转string 输出g 1
}
### ReadSlice() 方法
ReadSlice() 方法的功能是读取数据直到分隔符“delim”处,并返回读取数据的字节切片,下次读取数据时返回的
切片会失效。如果 ReadSlice 在查找到“delim”之前遇到错误,它返回读取的所有数据和那个错误(通常是
io.EOF)。
如果缓冲区满时也没有查找到“delim”,则返回 ErrBufferFull 错误。ReadSlice 返回的数据会在下次 I/O
操作时被覆盖,大多数调用者应该使用 ReadBytes 或者 ReadString。只有当 line 不以“delim”结尾时,
ReadSlice 才会返回非空 err。
结构:func (b *Reader) ReadSlice(delim byte) (line []byte, err error)
func main(){
data:=[]byte("go语言非常好,go语言入门")//string转换为byte
ra:=bytes.NewReader(data)
a:=bufio.NewReader(ra)
var delim =','
line ,err:=a.ReadSlice(delim )
fmt.Println(string(line ),err)//string(tr[:n]) byte转string 输出go语言非常好,
line ,err:=a.ReadSlice(delim )
fmt.Println(string(line ),err)//string(tr[:n]) byte转string 输出go语言入门 EOF
line ,err:=a.ReadSlice(delim )
fmt.Println(string(line ),err)//string(tr[:n]) byte转string 输出go语言入门 EOF
}
### ReadString() 方法
ReadString() 方法的功能是读取数据直到分隔符“delim”第一次出现,并返回一个包含“delim”的字符串。如果
ReadString 在读取到“delim”前遇到错误,它返回已读字符串和那个错误(通常是 io.EOF)。只有当返回的字
符串不以“delim”结尾时,ReadString 才返回非空 err。
结构:func (b *Reader) ReadString(delim byte) (line string, err error)
func main(){
data:=[]byte("go语言非常好,go语言入门")//string转换为byte
ra:=bytes.NewReader(data)
a:=bufio.NewReader(ra)
var delim =','
line ,err:=a.ReadString(delim )
fmt.Println(line,err)//string(tr[:n]) byte转string 输出go语言非常好,
}
### UnreadByte() 方法
UnreadByte() 方法的功能是取消已读取的最后一个字节(即把字节重新放回读取缓冲区的前部)。只有最近一次读取的单个字节才能取消读取。该方法原型如下:
func (b *Reader) UnreadByte() error
### UnreadRune() 方法
UnreadRune() 方法的功能是取消读取最后一次读取的 Unicode 字符。如果最后一次读取操作不是 ReadRune,UnreadRune 会返回一个错误(在这方面它比 UnreadByte 更严格,因为 UnreadByte 会取消上次任意读操作的最后一个字节)。该方法原型如下:
func (b *Reader) UnreadRune() error
### Buffered() 方法
Buffered() 方法的功能是返回可从缓冲区读出数据的字节数,
func main() {
data := []byte("Go语言入门教程")
rd := bytes.NewReader(data)
r := bufio.NewReader(rd)
var buf [14]byte
n, err := r.Read(buf[:])
fmt.Println(string(buf[:n]), n, err)//输出Go语言入门 14
rn := r.Buffered()
fmt.Println(rn)//输出6
n, err = r.Read(buf[:])
fmt.Println(string(buf[:n]), n, err)//输出教程 6
rn = r.Buffered()
fmt.Println(rn)//输出教程 0
}
### Peek() 方法
定义:Peek() 方法的功能是读取指定字节数的数据,这些被读取的数据不会从缓冲区中清除。在下次读取之后,本次返回
的字节切片会失效。如果 Peek 返回的字节数不足 n 字节,则会同时返回一个错误说明原因,如果 n 比缓冲区要
大,则错误为 ErrBufferFull。该方法原型如下:
结构:func (b *Reader) Peek(n int) ([]byte, error)
func main() {
data := []byte("Go语言入门教程")
rd := bytes.NewReader(data)
r := bufio.NewReader(rd)
var buf [14]byte
n, err := r.Peek(2)
fmt.Println(string(n), n, err)//输出Go
n, err := r.Peek(8)
fmt.Println(string(n), n, err)//输出Go语言
n, err := r.Peek(20)
fmt.Println(string(n), n, err)//输出Go语言入门教程
}
# Writer 对象
Writer 对象可以对数据 I/O 接口 io.Writer 进行输出缓冲操作,Writer 结构定义如下:
type Writer struct {
//contains filtered or unexported fields
}
创建的俩种方式:NewWriter() NewWriter()
## 操作 Writer 对象
操作 Writer 对象的方法共有 7 个,分别是 Available()、Buffered()、Flush()、Write()、WriteByte()、WriteRune() 和 WriteString() 方法,下面分别介绍。
### Available()
定义:Available() 方法的功能是返回缓冲区中未使用的字节数,该方法原型如下:
结构:func (b *Writer) Available() int
package main
import (
"bufio"
"bytes"
"fmt"
)
func main() {
wr := bytes.NewBuffer(nil)
w := bufio.NewWriter(wr)
p := []byte("C语言中文网")
fmt.Println("写入前未使用的缓冲区为:", w.Available())//输出写入前未使用的缓冲区为: 4096
w.Write(p)
fmt.Printf("写入%q后,未使用的缓冲区为:%d\n", string(p), w.Available())//输出写入"C语言
//中文网"后,未使用的缓冲区为:4080
}
### Buffered() 方法
定义:Buffered() 方法的功能是返回已写入当前缓冲区中的字节数,该方法原型如下:
结构:func (b *Writer) Buffered() int
func main() {
wr := bytes.NewBuffer(nil)
w := bufio.NewWriter(wr)
p := []byte("C语言中文网")
fmt.Println("写入前使用的缓冲区为:", w.Buffered())//输出写入前使用的缓冲区为:0
w.Write(p)
fmt.Printf("写入%q后,使用的缓冲区为:%d\n", string(p), w.Buffered())//输出写入"C语言
//中文网"后,使用的缓冲区为:16
}
### Flush() 方法
定义:Flush() 方法的功能是把缓冲区中的数据写入底层的 io.Writer,并返回错误信息。如果成功写入,error 返回
nil,否则 error 返回错误原因。该方法原型如下:
结构:func (b *Writer) Flush() error
func main() {
wr := bytes.NewBuffer(nil)
w := bufio.NewWriter(wr)
p := []byte("C语言中文网")
w.Write(p)
fmt.Printf("未执行 Flush 缓冲区输出 %q\n", string(wr.Bytes()))//输出""
w.Flush()
fmt.Printf("执行 Flush 后缓冲区输出 %q\n", string(wr.Bytes()))//输出C语言中文网
}
### Write() 方法
定义:Write() 方法的功能是把字节切片 p 写入缓冲区,返回已写入的字节数 nn。如果 nn 小于 len(p),则
同时返回一个错误原因。该方法原型如下:
结构:func (b *Writer) Write(p []byte) (nn int, err error)
func main() {
wr := bytes.NewBuffer(nil)
w := bufio.NewWriter(wr)
p := []byte("C语言中文网")
n, err := w.Write(p)
w.Flush()
fmt.Println(string(wr.Bytes()), n, err)//输出C语言中文网
}
### WriteByte() 方法
定义:WriteByte() 方法的功能是写入一个字节,如果成功写入,error 返回 nil,否则 error 返回错误原因
结构:func (b *Writer) WriteByte(c byte) error
其中,参数 c 是要写入的字节数据,比如 ASCII 字符。示例代码如下:
package main
import (
"bufio"
"bytes"
"fmt"
)
func main() {
wr := bytes.NewBuffer(nil)
w := bufio.NewWriter(wr)
var c byte = 'G'
err := w.WriteByte(c)
w.Flush()
fmt.Println(string(wr.Bytes()), err)//输出G
}
### WriteRune() 方法
定义:WriteRune() 方法的功能是以 UTF-8 编码写入一个 Unicode 字符,返回写入的字节数和错误信息。
结构:func (b *Writer) WriteRune(r rune) (size int,err error)
其中,参数 r 是要写入的 Unicode 字符。示例代码如下:
package main
import (
"bufio"
"bytes"
"fmt"
)
func main() {
wr := bytes.NewBuffer(nil)
w := bufio.NewWriter(wr)
var r rune = 'G'
size, err := w.WriteRune(r)
w.Flush()
fmt.Println(string(wr.Bytes()), size, err)//输出G 1
}
### WriteString() 方法
定义:WriteString() 方法的功能是写入一个字符串,并返回写入的字节数和错误信息。如果返回的字节数小于
len(s),则同时返回一个错误说明原因。该方法原型如下:
结构:func (b *Writer) WriteString(s string) (int, error)
其中,参数 s 是要写入的字符串。示例代码如下:
纯文本复制
package main
import (
"bufio"
"bytes"
"fmt"
)
func main() {
wr := bytes.NewBuffer(nil)
w := bufio.NewWriter(wr)
s := "C语言中文网"
n, err := w.WriteString(s)
w.Flush()
fmt.Println(string(wr.Bytes()), n, err)//输出C语言中文网
}
定义:接口是双方约定的一种合作协议。接口实现者不需要关心接口会被怎样使用,调用者也不需要关心接口的实现
细节。接口是一种类型,也是一种抽象结构,不会暴露所含数据的格式、类型及结构。
接口声明的格式
每个接口类型由数个方法组成。接口的形式代码如下:
type 接口类型名 interface{
方法名1( 参数列表1 ) 返回值列表1
方法名2( 参数列表2 ) 返回值列表2
…
}
对各个部分的说明:
接口类型名:使用 type 将接口定义为自定义的类型名。Go语言的接口在命名时,一般会在单词后面添加 er,如有
写操作的接口叫 Writer,有字符串功能的接口叫 Stringer,有关闭功能的接口叫 Closer 等。
方法名:当方法名首字母是大写时,且这个接口类型名首字母也是大写时,这个方法可以被接口所在的包(package)
之外的代码访问。
开发中常见的接口及写法
Go语言提供的很多包中都有接口,例如 io 包中提供的 Writer 接口:
纯文本复制
type Writer interface {
Write(p []byte) (n int, err error)
}
#接口实现的条件
1)接口的方法与实现接口的类型方法格式一致
2)接口中所有方法均被实现
#接口与类型的关系
//接口实现不受限于结构体,任何类型都可以实现接口
1)一个类型可以实现多个接口
type Socket struct {
}
func (s *Socket) Write(p []byte) (n int, err error) {
return 0, nil
}
func (s *Socket) Close() error {
return nil
}
2)多个类型可以实现相同的接口
一个接口的方法,不一定需要由一个类型完全实现,接口的方法可以通过在类型中嵌入其他类型或者结构体来实现。也就是说,使用者并不关心某个接口的方法是通过一个类型完全实现的,还是通过多个结构嵌入到一个结构体中拼凑起来共同实现的。
type Service interface {
Start() // 开启服务
Log(string) // 日志输出
}
// 日志器
type Logger struct {
}
// 实现Service的Log()方法
func (g *Logger) Log(l string) {
}
// 游戏服务
type GameService struct {
Logger // 嵌入日志器
}
// 实现Service的Start()方法
func (g *GameService) Start() {
}
var s Service = new(GameService)//Service接口名称 GameService结构体名称
s.Start()
s.Log(“hello”)
定义:类型断言是一个使用在接口值上的操作,用于检查接口类型变量所持有的值是否实现了期望的接口或者具体的类型。
结构:value, ok := x.(T)
其中,x 表示一个接口的类型,T 表示一个具体的类型(也可为接口类型)。
该断言表达式会返回 x 的值(也就是 value)和一个布尔值(也就是 ok),可根据该布尔值判断 x 是否为 T 类型:
如果 T 是具体某个类型,类型断言会检查 x 的动态类型是否等于具体类型 T。如果检查成功,类型断言返回的结果
是 x 的动态值,其类型是 T。
如果 T 是接口类型,类型断言会检查 x 的动态类型是否满足 T。如果检查成功,x 的动态值不会被提取,返回值是
一个类型为 T 的接口值。
无论 T 是什么类型,如果 x 是 nil 接口值,类型断言都会失败。
package main
import (
"fmt"
)
func main() {
var x interface{}
x = 10
value, ok := x.(int)
fmt.Print(value, ",", ok)
}
类 型 | 实现sort.lnterface 的类型 | 直接排序方法 | 说 明 |
---|---|---|---|
字符串(String) | StringSlice | sort.Strings(a [] string) | 字符 ASCII 值升序 |
整型(int) | IntSlice | sort.Ints(a []int) | 数值升序 |
双精度浮点(float64) | Float64Slice | sort.Float64s(a []float64) | 数值升序 |
//字符串排序(sort.Slice)
names := []string{
"3. Triple Kill",
"5. Penta Kill",
"2. Double Kill",
"4. Quadra Kill",
"1. First Blood",
}
sort.Strings(names)
// 遍历打印结果
for _, v := range names {
fmt.Printf("%s\n", v)
}
//结构体排序
package main
import (
"fmt"
"sort"
)
type HeroKind int
const (
None = iota
Tank
Assassin
Mage
)
type Hero struct {
Name string
Kind HeroKind
}
func main() {
heros := []*Hero{
{"吕布", Tank},
{"李白", Assassin},
{"妲己", Mage},
{"貂蝉", Assassin},
{"关羽", Tank},
{"诸葛亮", Mage},
}
sort.Slice(heros, func(i, j int) bool {
if heros[i].Kind != heros[j].Kind {
return heros[i].Kind < heros[j].Kind
}
return heros[i].Name < heros[j].Name
})
for _, v := range heros {
fmt.Printf("%+v\n", v)
}
}
//嵌套
type Writer interface {
Write(p []byte) (n int, err error)
}
type Closer interface {
Close() error
}
type WriteCloser interface {
Writer
Closer
}
//在代码中使用类似于一个类型使用多个接口
package main
import (
"io"
)
// 声明一个设备结构
type device struct {
}
// 实现io.Writer的Write()方法
func (d *device) Write(p []byte) (n int, err error) {
return 0, nil
}
// 实现io.Closer的Close()方法
func (d *device) Close() error {
return nil
}
func main() {
// 声明写入关闭器, 并赋予device的实例
var wc io.WriteCloser = new(device)
// 写入数据
wc.Write(nil)
// 关闭设备
wc.Close()
// 声明写入器, 并赋予device的新实例
var writeOnly io.Writer = new(device)
// 写入数据
writeOnly.Write(nil)
}
//将接口转换为其他接口
var obj interface = new(bird)
f, isFlyer := obj.(Flyer)
//将接口转换为其他类型
p1 := new(pig)
var a Walker = p1
p2 := a.(*pig)
fmt.Printf("p1=%p p2=%p", p1, p2)
//将值保存到空接口
var any interface{}
any = 1
fmt.Println(any)
//从空接口获取值
var a int = 1
// 声明i变量, 类型为interface{}, 初始值为a, 此时i的值变为1
var i interface{} = a
var b int = i.(int)
//空接口的值比较
类型不同的空接口间的比较结果不相同
不能比较空接口中的动态值,比如切片等
//type-switch 流程控制的语法或许是Go语言中最古怪的语法。 它可以被看作是类型断言的增强版。它和
//switch-case 流程控制代码块有些相似。 一个 type-switch 流程控制代码块的语法如下所示:
switch t := areaIntf.(type) {
case *Square:
fmt.Printf("Type Square %T with value %v\n", t, t)
case *Circle:
fmt.Printf("Type Circle %T with value %v\n", t, t)
case nil:
fmt.Printf("nil value: nothing to check?\n")
default:
fmt.Printf("Unexpected type %T\n", t)
}
//创建一个 error 最简单的方法就是调用 errors.New 函数,它会根据传入的错误信息返回一个新的 error,
//也可自定义报错方法
package main
import (
"errors"
"fmt"
"math"
)
func Sqrt(f float64) (float64, error) {
if f < 0 {
return -1, errors.New("math: square root of negative number")
}
return math.Sqrt(f), nil
}
func main() {
result, err := Sqrt(-13)
if err != nil {
fmt.Println(err)
} else {
fmt.Println(result)
}
}
package main
import (
"fmt"
"log"
"net/http"
)
func main() {
http.HandleFunc("/", index) // index 为向 url发送请求时,调用的函数
log.Fatal(http.ListenAndServe("localhost:8000", nil))
}
func index(w http.ResponseWriter, r *http.Request) {
fmt.Fprintf(w, "C语言中文网")
}
//也可以渲染页面
index.html
!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>C语言中文网</title>
</head>
<body>
<h1>C语言中文网</h1>
</body>
</html>
func main() {
// 在/后面加上 index ,来指定访问路径
http.HandleFunc("/index", index)
log.Fatal(http.ListenAndServe("localhost:8000", nil))
}
func index(w http.ResponseWriter, r *http.Request) {
content, _ := ioutil.ReadFile("./index.html")
w.Write(content)
}
包的习惯用法:
包名一般是小写的,使用一个简短且有意义的名称。
包名一般要和所在的目录同名,也可以不同,包名中不能包含- 等特殊符号。
包一般使用域名作为目录名称,这样能保证包名的唯一性,比如 GitHub 项目的包一般会放到GOPATH/src/github.com/userName/projectName 目录下。
包名为 main 的包为应用程序的入口包,编译不包含 main 包的源码文件时不会得到可执行文件。
一个文件夹下的所有源码文件只能属于同一个包,同样属于同一个包的源码文件不能放在多个文件夹下。
包的四种引用格式
标准引用格式 import "fmt"
自定义别名引用格式 import F "fmt"
省略引用格式 import . "fmt"
匿名引用格式 import _ "fmt"
封装的好处:
隐藏实现细节;
可以对数据进行验证,保证数据安全合理。
如何体现封装:
对结构体中的属性进行封装;
通过方法,包,实现封装。
封装的实现步骤:
将结构体、字段的首字母小写;
给结构体所在的包提供一个工厂模式的函数,首字母大写,类似一个构造函数;
提供一个首字母大写的 Set 方法(类似其它语言的 public),用于对属性判断并赋值;
提供一个首字母大写的 Get 方法(类似其它语言的 public),用于获取属性的值。
1)在/src下新建包文件//GOROOT 表示 Go 开发包的安装目录 go env
2)包文件下新建文件
package renming
import(
)
type people struct{
Name string
}
func NewPeople(name string) *people{
return &people{
Name:name,
}
}
//调用
package main
import(
"fmt"
"renming"
)
func main(){
ren:=renming.NewPeople("duzhil")
fmt.Println(ren)
}
go env查看GOPATH路径
在路径下新建文件src bin和pkg自动生成
go install 命令执行后会在bin文件夹下生成相关编译得包
//互斥锁 Mutex
func (m *Mutex) Lock()
func (m *Mutex) Unlock()
//例子:
func main() {
var a = 0
var lock sync.Mutex
for i := 0; i < 1000; i++ {
go func(idx int) {
lock.Lock()//加锁
defer lock.Unlock()//解锁
a += 1
fmt.Printf("goroutine %d, a=%d\n", idx, a)
}(i)
}
// 等待 1s 结束主程序
// 确保所有协程执行完
time.Sleep(time.Second)
}
//一个互斥锁只能同时被一个 goroutine 锁定,其它 goroutine 将阻塞直到互斥锁被解锁
func main() {
ch := make(chan struct{}, 2)
var l sync.Mutex
go func() {
l.Lock()
defer l.Unlock()
fmt.Println("goroutine1: 我会锁定大概 2s")
time.Sleep(time.Second * 2)
fmt.Println("goroutine1: 我解锁了,你们去抢吧")
ch <- struct{}{}
}()
go func() {
fmt.Println("goroutine2: 等待解锁")
l.Lock()
defer l.Unlock()
fmt.Println("goroutine2: 欧耶,我也解锁了")
ch <- struct{}{}
}()
// 等待 goroutine 执行结束
for i := 0; i < 2; i++ {
<-ch
}
}
//读写锁有如下四个方法:
写操作的锁定和解锁分别是func (*RWMutex) Lock和func (*RWMutex) Unlock;
读操作的锁定和解锁分别是func (*RWMutex) Rlock和func (*RWMutex) RUnlock。
读写锁的区别在于:
当有一个 goroutine 获得写锁定,其它无论是读锁定还是写锁定都将阻塞直到写解锁;
当有一个 goroutine 获得读锁定,其它读锁定仍然可以继续;
当有一个或任意多个读锁定,写锁定将等待所有读锁定解锁之后才能够进行写锁定。
所以说这里的读锁定(RLock)目的其实是告诉写锁定,有很多协程或者进程正在读取数据,写操作需要等它们读(读解锁)完才能进行写(写锁定)。
我们可以将其总结为如下三条:
同时只能有一个 goroutine 能够获得写锁定;
同时可以有任意多个 gorouinte 获得读锁定;
同时只能存在写锁定或读锁定(读和写互斥)。
链接
func main() {
b:="abcabcabc"
src:=regexp.MustCompile(`(?p<>abc){2}`)
reslut:=src.FindAllStringSubmatch(b,-1)
fmt.Println(reslut)
}
//获取当前时间
package main
import (
"fmt"
"time"
)
func main() {
b:=time.Now()//获取当前时间
timestamp1 := now.Unix() //时间戳
timestamp2 := now.UnixNano() //纳秒时间戳
timeObj := time.Unix(timestamp, 0) //将时间戳转为时间格式
year := timeObj.Year() //年
month := timeObj.Month() //月
day := timeObj.Day() //日
hour := timeObj.Hour() //小时
minute := timeObj.Minute() //分钟
second := timeObj.Second() //秒
}
//时间操作函数
func mian(){
b:=time.Now()
c:=b.Add(time.Hour)//支持时分秒 其他会报错
//获取俩个时间得差
b:=time.Now()
l:=b.Add(time.Hour)
c:=b.Sub(l)
fmt.Println(c)
//比较俩个时间是否相等
c:=b.Equal(l)
//判断一个时间点是否在另一个时间点之前:
c:=b.Before(l)
//判断一个时间点是否在另一个时间点之后:
c:=b.After(l)
}
//定时器
func main() {
ticker := time.Tick(time.Second) //定义一个1秒间隔的定时器
for i := range ticker {
fmt.Println(i) //每秒都会执行的任务
}
}
时间类型有一个自带的 Format 方法进行格式化,需要注意的是Go语言中格式化时间模板不是常见的Y-m-d H:M:S 而是使用Go语言的诞生时间 2006 年 1 月 2 号 15 点 04 分 05 秒。
提示:如果想将时间格式化为 12 小时格式,需指定 PM。
now := time.Now()
// 格式化的模板为Go的出生时间2006年1月2号15点04分 Mon Jan
// 24小时制
fmt.Println(now.Format("2006-01-02 15:04:05.000 Mon Jan"))
// 12小时制
fmt.Println(now.Format("2006-01-02 03:04:05.000 PM Mon Jan"))
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"))
Parse 函数可以解析一个格式化的时间字符串并返回它代表的时间。
func Parse(layout, value string) (Time, error)
与 Parse 函数类似的还有 ParseInLocation 函数。
func ParseInLocation(layout, value string, loc *Location) (Time, error)
ParseInLocation 与 Parse 函数类似,但有两个重要的不同之处:
第一,当缺少时区信息时,Parse 将时间解释为 UTC 时间,而 ParseInLocation 将返回值的 Location 设置为 loc;
第二,当时间字符串提供了时区偏移量信息时,Parse 会尝试去匹配本地时区,而 ParseInLocation 会去匹配 loc。
func main() {
var layout string = "2006-01-02 15:04:05"
var timeStr string = "2019-12-12 15:22:12"
timeObj1, _ := time.Parse(layout, timeStr)
fmt.Println(timeObj1)
timeObj2, _ := time.ParseInLocation(layout, timeStr, time.Local)
fmt.Println(timeObj2)
}
链接
flag.Type()
基本格式如下:
flag.Type(flag 名, 默认值, 帮助信息) *Type
//Type 可以是 Int、String、Bool 等,返回值为一个相应类型的指针,例如我们要定义姓名、年龄、婚否三个命令行参数
package main
import (
"fmt"
"flag"
)
func main() {
name:=flag.String("name","张三","姓名")
fmt.Println(*name)//输出张三
}
flag.TypeVar()
基本格式如下:
flag.TypeVar(Type 指针, flag 名, 默认值, 帮助信息)
func main() {
var name string
flag.StringVar(&name,"name","张三","姓名")
fmt.Println(name)//输出张三
}
最早的时候,Go语言所依赖的所有的第三方库都放在 GOPATH 这个目录下面,这就导致了同一个库只能保存一个版本
的代码。如果不同的项目依赖同一个第三方的库的不同版本,应该怎么解决?
go module 是Go语言从 1.11 版本之后官方推出的版本管理工具,并且从 Go1.13 版本开始,go module 成为
了Go语言默认的依赖管理工具。
使用module
1)go env //查看Go的相关信息
2)export GO111MODULE=on//设置GO111MODULE
3)export GOPROXY=https://goproxy.cn//设置代理服务器
//项目中使用
1)新建文件夹
2)go mod init 文件夹名称//生成go.mod
3)新建main.go文件 并引入包
4)go get //下载依赖包
5)go run mian.go//运行代码会发现 go mod 会自动查找依赖自动下载
使用 replace 替换无法直接获取的 package
由于某些已知的原因,并不是所有的 package 都能成功下载,比如:golang.org 下的包。
modules 可以通过在 go.mod 文件中使用 replace 指令替换成 github 上对应的库,比如:
replace (
golang.org/x/crypto v0.0.0-20190313024323-a1f597ede03a => github.com/golang/crypto v0.0.0-20190313024323-a1f597ede03a
)
或者
replace golang.org/x/crypto v0.0.0-20190313024323-a1f597ede03a => github.com/golang/crypto v0.0.0-20190313024323-a1f597ede03a
定义:虽然,线程池为逻辑编写者提供了线程分配的抽象机制。但是,如果面对随时随地可能发生的并发和线程处理需
求,线程池就不是非常直观和方便了。能否有一种机制:使用者分配足够多的任务,系统能自动帮助使用者把任务分配
到 CPU 上,让这些任务尽量并发运作。这种机制在 Go语言中被称为 goroutine。
goroutine 是 Go语言中的轻量级线程实现,由 Go 运行时(runtime)管理。Go 程序会智能地将 goroutine
中的任务合理地分配给每个 CPU
goroutine 在多核 cpu 环境下是并行的,如果代码块在多个 goroutine 中执行,那么我们就实现了代码的并行
//使用普通函数创建 goroutine
go 函数名( 参数列表 )
//使用匿名函数创建goroutine
go func( 参数列表 ){
函数体
}( 调用参数列表 )
go run -race//可查看运行状态
Go语言提供了传统的同步 goroutine 的机制,就是对共享资源加锁。atomic 和 sync 包里的一些函数就可以对共享的资源进行加锁操作。
原子函数
原子函数能够以很底层的加锁机制来同步访问整型变量和指针,示例代码如下所示:
package main
import (
"fmt"
"runtime"
"sync"
"sync/atomic"
)
var (
counter int64
wg sync.WaitGroup
)
func main() {
wg.Add(2)
go incCounter(1)
go incCounter(2)
wg.Wait() //等待goroutine结束
fmt.Println(counter)
}
func incCounter(id int) {
defer wg.Done()
for count := 0; count < 2; count++ {
atomic.AddInt64(&counter, 1) //安全的对counter加1
runtime.Gosched()
}
}
另外两个有用的原子函数是 LoadInt64 和 StoreInt64。这两个函数提供了一种安全地读和写一个整型值的方式
另一种同步访问共享资源的方式是使用互斥锁,互斥锁这个名字来自互斥的概念。互斥锁用于在代码上创建一个临界区,保证同一时间只有一个 goroutine 可以执行这个临界代码。
package main
import (
"fmt"
"runtime"
"sync"
)
var (
counter int64
wg sync.WaitGroup
mutex sync.Mutex
)
func main() {
wg.Add(2)
go incCounter(1)
go incCounter(2)
wg.Wait()
fmt.Println(counter)
}
func incCounter(id int) {
defer wg.Done()
for count := 0; count < 2; count++ {
//同一时刻只允许一个goroutine进入这个临界区
mutex.Lock()
{
value := counter
runtime.Gosched()
value++
counter = value
}
mutex.Unlock() //释放锁,允许其他正在等待的goroutine进入临界区
}
}
//设置cpu的数量
runtime.GOMAXPROCS(逻辑CPU数量)
这里的逻辑CPU数量可以有如下几种数值:
<1:不修改任何数值。
=1:单核心执行。
>1:多核并发执行。
runtime.Numcpu()获取cpu的数量
//声明通道类型
var 通道名称 chan 通道类型
//创建通道
通道实例 := make(chan 数据类型)
//通道发送数据的格式
通道变量 <- 值
var ch chan interface{}
ch=make(chan interface{})
ch<-8
1) 阻塞接收数据
阻塞模式接收数据时,将接收变量作为<-操作符的左值,格式如下:
data := <-ch
执行该语句时将会阻塞,直到接收到数据并赋值给 data 变量。
2) 非阻塞接收数据
使用非阻塞方式从通道接收数据时,语句不会发生阻塞,格式如下:
data, ok := <-ch
data:表示接收到的数据。未接收到数据时,data 为通道类型的零值。
ok:表示是否接收到数据。
非阻塞的通道接收方法可能造成高的 CPU 占用,因此使用非常少。如果需要实现接收超时检测,可以配合 select 和计时器 channel 进行,可以参见后面的内容。
3) 接收任意数据,忽略接收的数据
阻塞接收数据后,忽略从通道返回的数据,格式如下:
<-ch
var 通道实例 chan<- 元素类型 // 只能写入数据的通道
var 通道实例 <-chan 元素类型 // 只能读取数据的通道
关闭 channel
close(ch)
通道实例 := make(chan 通道类型, 缓冲大小)
定义: