自定义优定软件网站建设,知名门户网站,杭州房产免费网站建设,wordpress地址修改错了无法访问文章目录 Go学习-Day1Go学习-Day2标识符变量基础语法字符串类型类型转换string和其他基本类型转换其他类型转stringstring转其他类型 指针类型运算符标准IO分支语句 Go学习-Day3循环语句函数声明init函数匿名函数闭包defer Go学习-Day4函数值传递#xff0c;引用传递常用的函数… 文章目录 Go学习-Day1Go学习-Day2标识符变量基础语法字符串类型类型转换string和其他基本类型转换其他类型转stringstring转其他类型 指针类型运算符标准IO分支语句 Go学习-Day3循环语句函数声明init函数匿名函数闭包defer Go学习-Day4函数值传递引用传递常用的函数 异常处理数组Slice切片 Go学习-Day5map增加和更新删除查询遍历for-rangemap切片关于哈希表遍历的一点看法对map的key排序 结构体与OOP声明、初始化、序列化方法工厂模式 Go学习-Day6封装继承接口 Go学习-Day7断言文件打开/关闭文件读取文件写入文件 命令行参数解析Argsflag包 JSON Go学习-Day8单元测试Goroutine进程和线程并发和并行Go协程和主线程MPG模式CPU相关协程并行的资源竞争 Go学习-Day9Channel声明存入取出一个简单的死锁分析 Go学习-Day10反射网络编程监听端口小Demo客户端发送接收 Go学习-Day1 个人博客CSDN博客 打卡。 Go语言的核心开发团队 Ken Thompson (C语言B语言Unix的发明者牛人)Rob Pike(UTF-8发明人)Robert Griesemer(协助HotSpot编译器Js引擎V8) Go语言有静态语言的安全和性能和动态语言开发维护的效率。 Go语言特性 继承了C语言很多概念Ken爷包括指针。引入包的概念垃圾回收机制天然并发核心管道通信机制(Channel)函数可以有多返回值新增切片slice,延时执行defer Hello World (一定要注意目录结构) 通过go build来编译go文件得到exe文件 关于文件夹架构一定要准确不然找不到包。 %GOPATH% src go_code project00 //项目名open这个项目 project01 main//包pkg//其他包 注意配置PATHGOPATH项目的位置GOROOTSDK的位置 并且配置一些settings里面相应的变量 琐碎的细节 go语言没有分号结尾因此一行就写一条语句定义的变量和导入的包如果没有用到就无法通过编译块注释不能嵌套尽量使用行注释
Go学习-Day2
标识符
驼峰法首字母大写可以在其他包里使用首字母小写只能在本包内使用跨包使用的import地址从src的子目录开始src以及src所在的GOPATH自动补全
变量基础语法 定义变量 var i int 10var关键字变量名变量类型 var i 10自动推断类型 i : 10简略写法 对应的可以声明多个变量 var a, b, c int 1, a, 2var a, str1, b 1, a, 2a, str1, b : 1, a, 2var (i 1j 2
)另一种声明方法开发中常用 import (fmtunsafe
)导包也可以类似这样 不能改变变量的类型例如开始赋值整数后来又赋值浮点数。默认值数默认为0字符串默认为空串 字符串类型 利用UTF-8编码支持中文 go中字符串是常量无法修改 引号 双引号 会识别转义字符反引号不识别转义字符防止SQL注入之类的 加号拼接可以分行写加号放行尾
类型转换 go不会自动转换类型需要显式转换 var i int 1
var j float32 float32(i)string和其他基本类型转换
其他类型转string
func main() {var a int 10var b float32 3.14var s string fmt.Sprintf(%d %.2f, a, b)fmt.Println(s)
}string转其他类型
b, err : strconv.ParseBool(true)
f, err : strconv.ParseFloat(3.1415, 64)//返回64位要 强转
i, err : strconv.ParseInt(-42, 10, 64)//进制 和 位数
u, err : strconv.ParseUint(42, 10, 64)返回值有两个可以使用_代替err下划线是特殊的变量表示忽略返回值。如果无法转换则返回0学英语parse是分析的意思strconv string-conversion
指针类型
和C语言类似不赘述。
运算符
没有三元运算符只能用if elseif后面没有小括号运算与C语言一致自增自减只能单独使用不能在自增自减的同时给变量赋值自增自减的和–都必须放在变量的后边
标准IO string也是基本类型传入地址。 func main() {var str string_, _ fmt.Scanln(str)fmt.Println(str)
}分支语句
基本和C语言一致switch 不用break可以匹配多个表达式逻辑或的关系case,switch后面是一个表达式不一定是常量case和switch的数据类型必须一致case的常量字面量不能重复switch后面可以不带表达式可以代替if else作分支选择fallthrough关键字可以穿透到下一分支用来代偿省略break的功能
Go学习-Day3
个人博客CSDN博客
循环语句 传统方法 func main() {for i : 1; i 10; i {fmt.Println(hello!)}
}for - range方法 func main() {str : abcdefor idx, val : range str {fmt.Printf(%v %c\n, idx, val)}
}idx是下标val是值 go没有while和do-while使用for来实现 for {if i 10 {break}fmt.Println(i)i}函数
声明 func 函数名形参列表返回值列表 {} func add(a int, b int) int {return a b
}分包写函数 package mainimport (fmtgo_code/project01/model
)func main() {fmt.Println(model.Add(1, 2))
} 包名和文件夹名可以不一致这样下面调用的时候也要用另外的包名一般来说我们习惯于名字保持一致这样我们导入包的时候就就能知道包名是什么了。 再复习一个点Go语言没有public private关键字是用变量和函数第一个字母大小写来判断公有还是私有大写是公有小写是私有。 导包的路径是从GOPATH/src 后面的部分一直导到包文件夹。 在同一包下不能又相同的函数名不支持函数的重载 如果要编译可执行文件必须声明main包main包是唯一的。
init函数
每个源文件都可以包含一个init函数这个函数会在main函数之前被调用,全局变量定义init函数main函数的顺序调用可能相当于类当中的构造函数
匿名函数 相当于把整个函数体当作函数的名字后面的括号就是传入的参数列表 func main() {res : func(a int, b int) int {return a b}(1, 2)fmt.Println(res)
}如果不带括号可以重复调用匿名函数类似lamda表达式 res : func(a int, b int) int {return a b}//fmt.Println(res)fmt.Println(res(1, 2))闭包 func main() {f : func() func(int) int {var n int 10return func(x int) int {n x nreturn n}}ff : f()ff(1)ff(2)fmt.Println(ff(3))
}答案16 这里是一个返回值是(int)int的匿名函数返回了一个含有未知参数并且引用了n的匿名函数对这个匿名函数多次调用。 函数和引用的外部变量构成了闭包相当于一个类第一次调用得到一个匿名函数可以类比成一个构造方法构造出了一个类n是类的一个成员。 或者我们这样想这个匿名函数和他所引用的变量构成的闭包在匿名函数第一次返回的时候这些变量也在相同的作用域进行声明。
defer to delay sth until a later time 推迟延缓展期摘自牛津 func main() {defer fmt.Println(ok1)defer fmt.Println(ok2)fmt.Println(ok3)fmt.Println(ok4)}输出顺序是3-4-2-1 defer先不执行等到函数快要释放的时候defer执行顺序遵从栈的顺序先进后出 当语句压入栈的时候相关引用的变量也会拷贝一份进入栈。
Go学习-Day4
个人博客CSDN博客
函数
值传递引用传递
值传递直接拷贝值一般是基本数据类型数组结构体也是引用传递传递地址 效率高指针slice切片map管道interface等
常用的函数 len(string str)//求字符串长度自带的不用包中文一个字三字节转成[]rune来处理 []byte转string str string([]byte{...})查找子串是否存在 若干字符函数 strings.Contains(aaa, aaa) //bool
strings.Index(aaa, aaa)//返回下标
strings.LastIndex(aaa, aaa)//返回最后一个下标,没有就返回-1
strings.Replace(str, str1, str2, n)//把1中str1替换成str2,n是替换个数-1表示全部替换
strings.Split(str, 某字符)//分割字符串
strings.TrimSpace(str)//裁剪空格,去掉前导和后导空格
strings.Trim(str, 字符集)//去掉指定字符
strings.TrimLeft()//同上去掉左侧并且还有TrimRight
strings.HasPrefix(str, 后缀)//前缀匹配
strings.HasSuffix()//同上但是后缀若干时间函数 now : time.Now()//返回时间类型当前时间
//2023-08-23 16:37:07.5402748 0800 CST m0.001148901 大概是这样
//时间类型是结构体可以使用.运算符来获取其他时间信息,now.Year()
//月份可以直接转int
time.Sleep(time.Millisecond * 100)//只能用乘法不能有浮点数利用时间单位常量
time.Unix()//获取unix秒时间戳
time.UnixNano()//unix纳秒时间戳内置函数built-in len()//统计字符串长度数组大小
new(Type) *Type //参数为类型返回一块对应大小的清空的内存块的指针异常处理 Go中没有try catch Go利用defer panic recover来处理异常 抛出一个panic的异常在defer中通过recover捕获异常 package mainimport fmtfunc test() {defer func() {err : recover() //捕获异常if err ! nil {fmt.Println(err)}}()num1 : 10num2 : 0res : num1 / num2fmt.Println(res)
}
func main() {test()fmt.Println(ok)
} 通过捕获异常可以使得程序不崩溃停止main函数的其他部分照常运行 自定义错误 func myError(x int) (err error) {if x 0 {return nil} else {return errors.New(错误)}
}func test() {err : myError(1)if err ! nil {panic(err)}
}func main() {test()
}panic会终止程序 捕获自定义错误 func myError(x int) (err error) {if x 0 {return nil} else {return errors.New(错误)}
}func test() {defer func() {err : recover() //捕获异常if err ! nil {fmt.Println(err)}}()err : myError(1)if err ! nil {panic(err)}
}func main() {test()fmt.Println(ok)
}数组 定义 func main() {var arr [10]intarr[0] 1fmt.Println(arr)}数组名地址arr 初始化 var arr [3]int [3]int{1, 2, 3}var arr [3]int{1, 2, 3}var arr [...]int{1, 2, 3}var arr [...]int{1: 800, 0: 900, 2: 999}//指定下标var arr : [...]int{1, 2, 3} //自动推导遍历for-range 同string 不赘述 数组中的元素可以是任何合法的类型但是不能混用 Go中数组是值类型会进行拷贝要想修改原数组需要使用指针写法类似C语言的行指针
Slice切片 切片是引用类型传递地址 切片和数组类似但是长度是可以变化的 声明 var a []intfunc main() {var arr [5]int [...]int{1, 2, 3, 4, 5}slice : arr[1:3] //从下标1与下标3,左闭右开的区间fmt.Println(slice)
}通过make声明 func main() {slice : make([]int, 2, 4)//容量可以不声明fmt.Println(slice)
}make在底层维护一个数组这个数组对外不可见 直接声明 var slive []string []string 遍历和数组类似不再赘述 简写 var slice arr[:end] // var slice arr[0:end]前缀不含end
var slice arr[start:]//var slice arr[start:]后缀
var slice arr[:]//var slice arr[0:len(arr)]全长切片可以继续切片 切片可以追加可以追加多个数可以追加多个切片利用append将追加后的切片赋值给原来的切片 slice1 append(slice1, 1, 2, 3)//追加数
slice1 append(slice1, slice1...)//要三个点Go底层会创建一个新的数组然后切片这个新的数组这些过程均不可见 string可以进行切片处理 str : sssssss
slice : str[2:]//从下标2开始切后缀string底层也指向一个byte数组我们用切片来拷贝这个只读的byte数组再进行操作 通过切片能够改变字符串 arr : byte[](str)
arr[0] a
str string(arr)
//但是不支持中文
arr : rune[](str)
arr[0] 好
str string(arr)
//弄中文
func main() {str : ?????arr : []rune(str)arr[0] 好fmt.Println(string(arr))
}Go学习-Day5
个人博客CSDN博客
map map是一个key-value的数据结构又称为字段或关联数组 Golang自带的map是哈希表 声明 import fmtfunc main() {var a map[int]intfmt.Println(a)
}slicemap和func不能作为键值 声明map是不会分配内存的初始化需要用make import fmtfunc main() {var a map[int]inta make(map[int]int, 3)//可以存放三个键值对fmt.Println(a)
}Go的map的键值是没有顺序的 自动增长 func main() {a : make(map[int]int)a[1] 2a[2] 1fmt.Println(a)
}直接初始化 func main() {a : map[int]int{1: 1,2: 2,//这里也要,}fmt.Println(a)
}增加和更新
直接给键值赋值即可
删除 delete(map, 1)//删除map键值为1的对,如果不存在不会操作如果要完全清空 遍历key来删除或者让map赋值一个新的map给GC回收
查询
val, flag : mp[1] //flag是bool,找到是true没找到是falseval是对应值遍历for-range func main() {a : map[int]int{1: 1,2: 2,}for k, v : range a {fmt.Println(k, v)}fmt.Println(a)
}map切片
同样slice的用法注意map也要make就行相当于在底层维护一个map类型的数组
关于哈希表遍历的一点看法
大概是这样的在底层有一个迭代器链表或者有一个指针数组每次存放一个指针迭代器依次访问这些指针但是在添加元素的时候 会rehash导致顺序变化。所以Go的设计者把他设置成无序每次都打乱这个数组防止程序员误用哈希map
对map的key排序 将键值追加到切片内然后对切片排序 import (fmtsort
)func main() {mp : make(map[int]int, 10)mp[1] 2mp[3] 1mp[2] 5mp[5] 6var keys []int //切片for key, _ : range mp {keys append(keys, key)}sort.Ints(keys)fmt.Println(keys)
}map是引用类型
结构体与OOP
声明、初始化、序列化 go语言是用struct来面向对象的 声明 type Node struct {X intY int
}func main() {var a Node//空间自动分配fmt.Println(a)}
//输出{0 0}赋值 func main() {var a Nodea.X 1a.Y 2fmt.Println(a)}
//输出{1 2}初始化 //方法一
a : Node{1, 2}//方法二
ptr *Node new(Node)
(*ptr).X 1
(*ptr).Y 2
//--------
func main() {var ptr *Node new(Node)(*ptr).X 1(*ptr).Y 2fmt.Println(*ptr)}
//---底层自动识别,这样也可以
func main() {var ptr *Node new(Node)ptr.X 1ptr.Y 2fmt.Println(*ptr)
}
//方法三
var node *Node Node{}结构体的所有字段在内存中是连续的 两个结构体的字段类型完全相同的话可以强制类型转换 用type struct1 struct2给struct2取别名相当于定义了一个新的类型两者之间可以强制类型转换但是不能直接赋值 struct的每个字段上可以写上一个tag该tag可以通过反射机制获取常见于序列化和反序列化 复习一个点Go没有public和private所以用首字母大写和小写来确定是公共的还是私有的 序列化对象转json字符串 反序列化json字符串转对象 动手 import (encoding/jsonfmt
)type Node struct {Name stringPassword string
}func main() {a : Node{aaaaaa,123456,}str, _ : json.Marshal(a)fmt.Println(a)fmt.Println(string(str))
}
//输出
//{aaaaaa 123456}
//{Name:aaaaaa,Password:123456} 但是这种大写的变量很多客户端不习惯所以使用tag,如果使用小写就无法被结构体外部函数使用无法序列化 import (encoding/jsonfmt
)type Node struct {Name string json:namePassword string json:password
}func main() {a : Node{aaaaaa,123456,}str, _ : json.Marshal(a)fmt.Println(a)fmt.Println(string(str))
}
//输出
//{aaaaaa 123456}
//{name:aaaaaa,password:123456}方法 方法是作用在指定类型上的函数 func (a Node) ok() {fmt.Println(ok)
}
//这个函数绑定给了Node
//调用
var a Node
p.ok()动手 import (fmt
)type Node struct {Name string json:namePassword string json:password
}func (a Node) ok() {fmt.Println(a.Name)
}
func main() {a : Node{aaaaaa,123456,}a.ok()
}
//输出了Node的名字这个方法只能用指定类型来调用不能直接调用 如果想要修改原来的参数我们使用结构体指针并且这更常用不用深拷贝速度更快 type Node struct {Name string json:namePassword string json:password
}func (a *Node) ok() {a.Name bbbb
}
func main() {a : Node{aaaaaa,123456,}a.ok()//编译器底层自动识别变为afmt.Println(a)
} 可以通过实现String方法可以自定义格式化输出
工厂模式
类似于构造函数在结构体所在包下写相应构造的函数返回结构体指针这样就可以在结构体私有的情况下在其他包调用这个函数直接返回结构体对象
Go学习-Day6
个人博客CSDN博客
封装
类似java的类的封装这里我们利用大小写和工厂模式来实现封装的功能略过
继承 相似的类具有相似的方法反复绑定相同的方法代码冗余所以引入了继承的概念 嵌套匿名结构体来实现继承的效果 动手实践 type Node struct {Name string json:namePassword string json:password
}type Point struct {NodeX intY int
}func (a *Node) ok() {a.Name bbbb
}
func main() {var a Point Point{Node{aaa, bbb},1,2,}a.ok()fmt.Println(a)
}注意看a.ok()其实是a.Node.ok底层自动识别可以省略匿名结构体 基本数据类型可以匿名但是不能出现多个相同类型的匿名基本类型
接口 多态主要就是由接口来实现的 声明 type inter interface {a()b()
}实现接口 import fmttype inter interface {a()b()
}
type obj1 struct {
}type union struct {
}func (o obj1) a() {fmt.Println(okkk)//用node对象给接口的a()实现
}func (o obj1) b() {fmt.Println(ohhh)
}func (u union) ok(in inter) {in.a()in.b()
}func main() {x : union{}y : obj1{}x.ok(y)
} 从上面我们可以看到在声明一个接口之后我们用工厂方法法obj对应接口的所有方法给实现了然后另外整一个抽象类似的的结构体绑定一个方法运行接口这样我们就能通过接口把这两个类给链接在一起。 总结。 interface类型可以定义一组方法方法不用实现并且不能含有变量 Go语言没有implements关键字只要一个变量类型绑定了接口中所有的方法这个变量就能实现这个接口 这个变量就能导入到含有接口作为变量的函数内
Go学习-Day7
个人博客CSDN博客
断言
type Node struct {x inty int
}func main() {var a interface{}var n Node Node{1, 2}a nvar b Nodeb a.(Node)fmt.Println(b)
}此处我们有一个结构体n给空接口a赋值空接口没有方法相当于方法被n给完全实现所以是可以赋值给接口的然后我们想把接口赋值给具体的结构体对象但是这里会报错需要使用类型断言的语法。用.(类型)类声明a的类型。 类型断言之后编译器会判断这个变量是否是指向这个类型如果是就转换成这个类型来赋值 就是把抽象的接口转换成具体的类型的方法 如果类型不匹配的话就会报panic通过这个方法可以判断接口个具体类型执行特定操作 if a, flag : u.(xxx); flag true {xxxx
} x.(type)会返回类型
文件
打开/关闭文件
import (fmtos
)func main() {f, err : os.Open(E:\\JetBrains\\GoLandSpace\\src\\go_code\\project01\\main\\test.txt)if err ! nil {fmt.Println(err)}fmt.Printf(%v, f)err f.Close()if err ! nil {fmt.Println(err)}
}
//返回{0xc00010c780}先用os包打开文件再用方法关闭上面的绝对路径当中如果用正斜杠要双写反斜杠就不用
读取文件 import (bufiofmtioos
)func main() {f, err : os.Open(E:/JetBrains/GoLandSpace/src/go_code/project01/main/test.txt)if err ! nil {fmt.Println(err)}defer f.Close() //函数推出的时候自动关闭reader : bufio.NewReader(f) //创建一个缓冲区来读入for {str, err : reader.ReadString(\n) //读到换行符就停止if err io.EOF {break //读到末尾}fmt.Println(str)}fmt.Println(-------------------)
}首先打开文件然后按行读取注意读到EOF要结束死循环 ioutil.ReadFile可以一次性读入到一个字节数组内不过文件需要比较小的情况下使用
写入文件
import (bufiofmtos
)func main() {filePath : E:/JetBrains/GoLandSpace/src/go_code/project01/main/a.txtfile, err : os.OpenFile(filePath, os.O_WRONLY|os.O_CREATE, 0666)//后面那个int在windows下无用if err ! nil {fmt.Printf(%v, err)return}defer file.Close()str : ok\nwriter : bufio.NewWriter(file)writer.WriteString(str)writer.Flush() //从缓冲区压入文件}写入文件的流程也是非常亲切我们写入写入缓冲区之后我们必须要flush刷新一下缓冲区将缓冲区内的字符刷到磁盘上通过不同的标识符有追加截断清除的写入操作类似的可以拷贝文件有io.Copy()函数可以更方便地拷贝文件
命令行参数解析
Args
os.Args这是go的命令行参数的数组但是不方便
flag包
flag.StringVar(xxx, x, , sss)
//第一个变量是传入的值所存的变量的地址x是传入的参数的名字是默认值 sss是说明设置完这些变量之后使用flag.parse()来解析命令行的参数
JSON
JSON(JavaScript Object Notation)是一种轻量级的数据交换格式 key-val的形式中括号表示数组大括号表示对象一个个的键值对用号分隔开序列化方法上面写过了用json.Marshal()反序列化用json.Unmarshal([]byte(str), xxx)
Go学习-Day8
个人博客CSDN博客
单元测试 testing框架会将xxx_test.go的文件引入调用所有TestXxx的函数 在cal_test.go文件里面写这个 package mainimport testingfunc TestAdd(t *testing.T) {a, b : 1, 2if add(a, b) ! 4 {t.Fatalf(Wrong Answer!)}
}在cal.go文件里写这个 package mainfunc add(a int, b int) int {return a b
} 运行go test -v的命令就能运行单测 可以得到结果 RUN TestAddcal_test.go:8: Wrong Answer!
--- FAIL: TestAdd (0.00s) testing框架import这个test文件之后会调用所有TestXxx的函数注意大写
Goroutine
进程和线程
进程是程序的在操作系统的一次执行过程线程是比进程更小的单位一个进程能创建销毁多个线程一个程序至少有一个进程一个进程至少有一个线程
并发和并行
多线程在单核上运行就是并发多线程在多核上运行就是并行
Go协程和主线程 主线程类似进程 协程类似线程是轻量级的线程 协程的特点 有独立的空间共享程序的堆空间调度由用户控制协程是轻量级的线程 import (fmtstrconvtime
)func test() {for i : 0; i 5; i {fmt.Println(test() calls! strconv.Itoa(i))time.Sleep(time.Second)}
}func main() {go test()for i : 0; i 5; i {fmt.Println(main() calls! strconv.Itoa(i))time.Sleep(time.Second)}
} 输出 main() calls! 0
test() calls! 0
test() calls! 1
main() calls! 1
main() calls! 2
test() calls! 2
test() calls! 3
main() calls! 3
main() calls! 4
test() calls! 4 go关键字会另起一个协程主线程执行到这里会开一个协程并行执行如果主线程执行完毕退出协程会被强制退出
MPG模式 MMachine是操作系统的主线程也就是物理线程 PProcessor协程执行的上下文 GGorountine协程 Go语言的协程是轻量级的是逻辑态的可以起上万个协程而C/java的多线程是内核态的几千个就会耗光CPU
CPU相关
runtime.NumCPU()
//获取本地CPU数目
runtime.GOMAXPROCS(int)
//设置GO最大可用的CPU数目
//Go Max Processors协程并行的资源竞争 多个协程同时访问一个资源会发生冲突会发生并发问题 在java中我们有锁和原子类来保证并发安全 声明一个全局锁变量lock lock sync.Mutex
//sync是同步的意思Muti-excluded互斥锁lock.Lock()//在进行并发的读写操作的时候先上个锁
...//在进行操作的时候别的协程会排队等待
lock.Unlock()//解锁之后才能给别的协程使用主线程读的时候也需要加锁因为底层不知道协程已经解锁了会发生资源冲突 但是这样不同协程之间没办法通讯不知道什么时候协成完成任务了白白空转浪费时间或者提前结束主线程终止协程管道可能能解决这些问题明天再学
Go学习-Day9
个人博客CSDN博客
Channel
Channel本质是一个队列多goroutine访问时不需要加锁Channel天然线程安全channel有类型只能写入相同类型channel是引用类型channel必须初始化才能写入数据make分配内存
声明 var intChan chan intintChan make(chan int, 3)java不是很熟悉感觉chan有点像java的原子类
存入取出 intChan- xxx //存入
a : intChan//取出管道不会自然增长不能超过容量不能从空的管道里取出数据会上DeadLock 如果想要存储任意类型的管道可以用空借口 var allChan chan interface{}但是取出的时候注意类型断言 close(intChan)channel关闭之后就不能再写入了但是能继续读出 关闭之后能用for-range来遍历如果不关闭的话会出现死锁 死锁的情况很多建议多找几篇文章看看写写实操一下 空的缓冲chan相当于无缓冲的chan无缓冲的chan需要接收者传入者否则就会死锁注意及时关闭 只向管道内写入不读取就会deadlock读得慢没有关系 关键是要给每个管道安排一个发送者和接收者
一个简单的死锁分析
package mainimport (fmttime
)func write(intChan chan int) {for i : 0; i 5; i {fmt.Println(写入: , i)intChan - itime.Sleep(time.Second)}//close(intChan)
}func read(intChan chan int, exitChan chan bool) {for {val, ok : -intChanif !ok {break}fmt.Println(读到, val)}exitChan - trueclose(exitChan)
}
func main() {intChan : make(chan int, 20)exitChan : make(chan bool, 1)go write(intChan)go read(intChan, exitChan)for {_, ok : -exitChanif !ok {break}}
}
输出
写入: 0
读到 0
写入: 1
读到 1
写入: 2
读到 2
写入: 3
读到 3
写入: 4
读到 4
fatal error: all goroutines are asleep - deadlock!goroutine 1 [chan receive]:
main.main()E:/JetBrains/GoLandSpace/src/go_code/project01/main/hello.go:36 0xe8goroutine 7 [chan receive]:
main.read(0x0?, 0x0?)E:/JetBrains/GoLandSpace/src/go_code/project01/main/hello.go:19 0x99
created by main.mainE:/JetBrains/GoLandSpace/src/go_code/project01/main/hello.go:33 0xd9Process finished with the exit code 2
下面是个人的分析不一定对有大佬可以来指正如果我们不closechannel是可以读的我们可以边读边写并且读的速度是可以更慢或者更快的go底层会通过上下文自行判断。但是这里我们写的协程我们关闭channel在程序运行完之后自行关闭此时我们读的协程会卡在intChan等待读入但是此时还不会报错因为协程会因为主线程结束而结束。但是后面的exitChan会导致报错
package mainimport (fmttime
)func write(intChan chan int) {for i : 0; i 5; i {fmt.Println(写入: , i)intChan - itime.Sleep(time.Second)}//close(intChan)
}func read(intChan chan int, exitChan chan bool) {for {val, ok : -intChanif !ok {break}fmt.Println(读到, val)}fmt.Println(到了这里)//exitChan - true//close(exitChan)
}
func main() {intChan : make(chan int, 20)exitChan : make(chan bool, 1)go write(intChan)go read(intChan, exitChan)time.Sleep(time.Second * 10)//for {// _, ok : -exitChan// if !ok {// break// }//}
} 这样并没有报错并且发现到了这里没有打印说明read函数作为intChan的接收者一直在等待这时候。 但是主线程运行到下面的for的时候此时exitChan是空的因为intChan一直在死循环等待所以触发了死锁 只读只写 var chanIn chan- int//只写var chanOut -chan int//只读select {case …}可以安全地取出数据 使用recover捕获协程终端 panic
Go学习-Day10
个人博客CSDN博客
反射 编写函数适配器序列化和反序列话可以用到 反射可以在运行时动态获取变量的各种信息例如类型结构体本身的信息修改变量的值调用关联的方法 反射是不是和映射相反是一种逆函数 变量到空接口相互转换空接口和reflect.value相互转换 动手一下 import (fmtreflect
)func test(a interface{}) {b : reflect.TypeOf(a)fmt.Println(b)
}func main() {var a int 10test(a)
}打印 “int” reflect.TypeOf()//从接口获取原类型
reflect.ValueOf()//从接口获取reflect.Value类型.Int能取到具体的类型
//如果需要原类型需要类型断言
reflect.Interface//把reflect.Value转换成空接口Kind是大的种类Type是小的类型 常量在定义的时候必须初始化 reflect.Value.Kind返回的是常量 如果传入指针类型的话反射常常需要改变原来的值指针类型需要.Elem方法取到值再用.SetInt之类的方修改原来的值 Value//指reflect.Value
Value.NumField()//获取字段数
Value.Field()//根据下标获取第几个字段,返回的也是relect.Value
Tpye//指reflect.Type
Tpye.Field().Tag.Get(key)//可以获取tag键值是结构体里面设置的例如json:的key就是json,序列化反序列化的键值固定取json其实可以自定义
Value.NumMethod()//获取方法数
Value.Method().Call(...)//获取第几个方法然后调用
//这个顺序是按照函数名字典序排列的Call传的是Value切片返回的也是Value切片
//输入的时候需要定义一个Value切片用reflect.ValueOf(xx)插入这个切片
Value.Elem().Field().SetXxx//修改字段
...FieldByName()//可以用字段名来找
Value.New()//为指针申请空间可以通过反射来创建类型网络编程
Golang的主要设计目标之一就是面向大规模的后端服务程序网络通信是服务端程序必不可少的一部分网络编程有两种 TCPTransmission Control Protocol socket编程和HTTP编程建立在前者之上做服务器尽量少开端口一个端口只能被一个程序监听
监听端口小Demo net包提供了可以指的I/O接口 package mainimport (fmtnet
)func main() {fmt.Println(开始监听)//使用tcp协议监听本机listen, err : net.Listen(tcp, 0.0.0.0:8888)if err ! nil {fmt.Println(err, err)}//延迟关闭defer listen.Close()//循环等待for {//等待客户端连接fmt.Println(等待连接...)//获取连接conn, err : listen.Accept()if err ! nil {fmt.Println(err, err)} else {fmt.Println(con, conn)}//起一个协程为客户端服务}
} 用telnet呼叫一下 telnet 127.0.0.1 8888 开始监听
等待连接...
con {{0xc00010ec80}}
等待连接...
//返回客户端 conn, err : net.Dial(tcp, ip...:端口)
//获取连接
//Dial是拨号的意思通过端口就能和对应的程序进行交流 func main() {conn, err : net.Dial(tcp, 127.0.0.1:8888)if err ! nil {fmt.Println(err, err)}fmt.Println(连接成功conn, conn)
}
//注意此时要开着上面的监听程序
//输出 连接成功conn {{0xc00010ca00}}
发送接收
server.go
package mainimport (fmtnet
)func process(conn net.Conn) {//连接过多不关闭的话就会导致其他连接无法成功defer conn.Close()for {buf : make([]byte, 512)//如果没有Write会停在这里类似我们stdin输入的时候光标会停在输入的位置//如果连接突然中断的话这里会报错//TCP底层会定时发送消息检查连接是否存在n, err : conn.Read(buf)if err ! nil {fmt.Println(err, err)return//有可能是关闭了}//字节切片要强制转换//buf后面的存的可能是乱七八糟的东西注意取前n个fmt.Print(string(buf[:n]))}
}func main() {fmt.Println(开始监听)//使用tcp协议监听本机listen, err : net.Listen(tcp, 0.0.0.0:8888)if err ! nil {fmt.Println(err, err)}//延迟关闭defer listen.Close()//循环等待for {//等待客户端连接fmt.Println(等待连接...)//获取连接conn, err : listen.Accept()if err ! nil {fmt.Println(err, err)} else {fmt.Println(con, conn)}//起一个协程为客户端服务go process(conn)}
}
client.go
package mainimport (bufiofmtnetos
)func main() {conn, err : net.Dial(tcp, 127.0.0.1:8888)if err ! nil {fmt.Println(err, err)}fmt.Println(连接成功conn, conn)//创建标准stdin的readerreader : bufio.NewReader(os.Stdin)//读取一行str, err : reader.ReadString(\n)if err ! nil {fmt.Println(err, err)}n, err : conn.Write([]byte(str))if err ! nil {fmt.Println(err, err)}fmt.Println(发送了n个字节n, n)
}
一个小点发送的字节数多2应该是回车键的缘故可能这里是当成\n\r