【Java转Go】快速上手学习笔记(四)之基础篇三
目录
- 泛型
- 内置泛型的使用
- 切片泛型和泛型函数
- map泛型
- 泛型约束
- 泛型完整代码
- 接口
- 反射
- 协程
- 特点
- WaitGroup
- goroutine的调度模型:MPG模型
- channel
- 介绍
- 语法:
- 举例:
- channel遍历
- 基本使用
- 和协程一起使用
- 案例一
- 案例二
- select...case
- main.go 完整代码
- 文件
go往期文章笔记:
【Java转Go】快速上手学习笔记(一)之环境安装篇
【Java转Go】快速上手学习笔记(二)之基础篇一
【Java转Go】快速上手学习笔记(三)之基础篇二
这篇主要是泛型、接口、反射、协程、管道、文件操作的笔记,因为我的笔记都是按照视频说的来记的,可能大家光看的话会有些看不明白,所以建议大家可以把代码复制下来,配合里面的注释一起,自己去运行一遍,加深理解😘
泛型
定义泛型:func 函数名 [泛型参数类型] (函数参数) {}
内置泛型的使用
Go内置的两个泛型:any 和 comparable
- any:表示go里面所有的内置基本类型,等价于
interface{}
- comparable:表示go里面所有内置的可比较类型:
int、uint、float、booi.struct、指针
等一切可以比较的类型
func printArr[T any](arr []T) {for _, item := range arr {fmt.Println(item)}
}// 泛型的使用
func 泛型的基本使用() {strArr := []string{"白夜", "炽翎"}intArr := []int{1, 2}printArr(strArr)printArr(intArr)
}
切片泛型和泛型函数
// 自定义一个切片泛型
type mySlice[T int | float64] []T// 给自定义的切片泛型添加一个求和方法
func (s mySlice[T]) sum() T {var sum Tfor _, v := range s {sum += v}return sum
}// 定义一个泛型函数
func add[T int | float64 | float32 | string](a T, b T) T {return a + b
}// 泛型函数与方法
func 切片泛型的使用() {// 往自定义的切片泛型里,添加int类型的值var i mySlice[int] = []int{1, 2, 3, 4}fmt.Println(i.sum()) // 可以直接调用为切片泛型添加的一个求和方法// 往自定义的切片泛型里,添加float64类型的值var f mySlice[float64] = []float64{1.5, 2.7, 3.89, 4.55}fmt.Println(f.sum())//fmt.Println(add[int](1, 2))fmt.Println(add(1, 2)) // 调用时,可以自动推导传入的参数的类型//fmt.Println(add[string]("hh", "66"))fmt.Println(add("hh", "66"))//fmt.Println(add[float64](1.6, 2.8))fmt.Println(add(1.6, 2.8))
}
map泛型
// 泛型map
type myMap[K string | int, V any] map[K]V
type User struct {Name string
}func map泛型的使用() {m1 := myMap[string, string]{"key": "符华",}fmt.Println(m1)m2 := myMap[int, User]{0: User{"符华"},}fmt.Println(m2)
}
泛型约束
// 自定义一个类型别名(将int8类型设置一个别名)
type int8A int8// 自定义一个泛型约束
type myInt interface {// 当类型设置了别名,在使用的时候要么在后面把这个别名约束也加进去//int | int8 | int16 | int32 | int64 | int8A// 要么在这个类型前面,加一个 ~ 符号,因为类型的别名是这个类型的衍生类型,在类型前面加 ~ 号就可以适配这个类型的全部衍生类型了int | ~int8 | int16 | int32 | int64
}// 给泛型约束定义一个比较大小的泛型函数
func getMaxNum[T myInt](a, b T) T {if a > b {return a}return b
}func 泛型约束的使用() {//var a int = 10var a int8A = 10//var b int = 20var b int8A = 20fmt.Println(getMaxNum(a, b))
}
泛型完整代码
package mainimport "fmt"/*
泛型:内置的泛型类型 any 和 comparable
any:表示go里面所有的内置基本类型,等价于 interface{}
comparable:表示go里面所有内置的可比较类型:`int、uint、float、booi.struct、指针` 等一切可以比较的类型
*/
func printArr[T any](arr []T) {for _, item := range arr {fmt.Println(item)}
}// 自定义一个切片泛型
type mySlice[T int | float64] []T// 给自定义的切片泛型添加一个求和方法
func (s mySlice[T]) sum() T {var sum Tfor _, v := range s {sum += v}return sum
}// 泛型函数
func add[T int | float64 | float32 | string](a T, b T) T {return a + b
}// 泛型map
type myMap[K string | int, V any] map[K]V
type User struct {Name string
}// 自定义一个类型别名(将int8类型设置一个别名)
type int8A int8// 自定义一个泛型约束
type myInt interface {// 当类型设置了别名,在使用的时候要么在后面把这个别名约束也加进去//int | int8 | int16 | int32 | int64 | int8A// 要么在这个类型前面,加一个 ~ 符号,因为类型的别名是这个类型的衍生类型,在类型前面加 ~ 号就可以适配这个类型的全部衍生类型了int | ~int8 | int16 | int32 | int64
}// 给泛型约束定义一个比较大小的泛型函数
func getMaxNum[T myInt](a, b T) T {if a > b {return a}return b
}func main() {//泛型的基本使用()//切片泛型的使用()//map泛型的使用()泛型约束的使用()
}// 泛型的使用
func 泛型的基本使用() {strArr := []string{"白夜", "炽翎"}intArr := []int{1, 2}printArr(strArr)printArr(intArr)
}// 泛型函数与方法
func 切片泛型的使用() {// 往自定义的切片泛型里,添加int类型的值var i mySlice[int] = []int{1, 2, 3, 4}fmt.Println(i.sum()) // 可以直接调用为切片泛型添加的一个求和方法// 往自定义的切片泛型里,添加float64类型的值var f mySlice[float64] = []float64{1.5, 2.7, 3.89, 4.55}fmt.Println(f.sum())//fmt.Println(add[int](1, 2))fmt.Println(add(1, 2)) // 调用时,可以自动推导传入的参数的类型//fmt.Println(add[string]("hh", "66"))fmt.Println(add("hh", "66"))//fmt.Println(add[float64](1.6, 2.8))fmt.Println(add(1.6, 2.8))
}func map泛型的使用() {m1 := myMap[string, string]{"key": "符华",}fmt.Println(m1)m2 := myMap[int, User]{0: User{"符华"},}fmt.Println(m2)
}func 泛型约束的使用() {//var a int = 10var a int8A = 10//var b int = 20var b int8A = 20fmt.Println(getMaxNum(a, b))
}
接口
接口:用 type 和 interface 关键字定义
定义格式:
type 接口名 interface {接口方法1(参数1 参数类型.....) [返回类型]接口方法2() [返回类型]接口方法3()...接口方法n() [返回类型]
}
接口可以将不同的类型绑定到一组公共的方法上,从而实现多态。(提高代码的复用率)
Go中的接口是隐式实现的,也就是说,如果一个类型实现了一个接口定义的所有方法,那么它就自动地实现了该接口。(不用像Java一样,用implements关键字指定实现哪个接口)
因此,我们可以通过将接口作为参数来实现对不同类型的调用,从而实现多态。
// 定义一个 寸劲 接口
type 寸劲 interface {// 这个接口里面有这几个方法寸劲开天(days int) string // 有参数,有返回值的方法寸劲山崩() string // 无参数,有返回值的方法寸劲岩破() // 无参数,无返回值的方法
}// 定义一个 太虚剑气 接口
type 太虚剑气 interface {太虚剑神(days int) string
}// 定义一个函数,以空接口作为参数(可以传任何类型的参数)
func dataPrint(datas ...interface{}) {for i, x := range datas {switch x.(type) {case bool:fmt.Printf("参数 #%d 是一个bool类型,它的值是:%v\n", i, x)case string:fmt.Printf("参数 #%d 是一个string类型,它的值是:%v\n", i, x)case int:fmt.Printf("参数 #%d 是一个int类型,它的值是:%v\n", i, x)case float64:fmt.Printf("参数 #%d 是一个float64类型,它的值是:%v\n", i, x)case nil:fmt.Printf("参数 #%d 是一个nil类型,它的值是:%v\n", i, x)default:fmt.Printf("参数 #%d 是其他类型,它的值是:%v\n", i, x)}}
}// 定义一个用户学习结构体,来实现接口所有个方法(一个类型实现了接口的所有方法,即实现了该接口)
type 学习 struct {name string
}// 定义一个结构体特有的方法
func (x 学习) 开始学习() string {return fmt.Sprint(x.name, "现在要开始学习了.....")
}// 实现 寸劲开天 接口(这里也可以用指针 x *学习,用了指针后,那么赋值的时候也需要传指针类型:&学习{"符华"})
func (x *学习) 寸劲开天(days int) string {return fmt.Sprint(x.name, "学了", days, "天,学完了寸劲开天")
}// 实现 寸劲山崩 接口
func (x 学习) 寸劲山崩() string {return fmt.Sprint(x.name, "学完了寸劲山崩")
}// 实现 寸劲岩破 接口
func (x 学习) 寸劲岩破() {fmt.Println(x.name, "学完了寸劲岩破")
}// 实现 太虚剑神 接口
func (x *学习) 太虚剑神(days int) string {return fmt.Sprint(x.name, "学了", days, "天,学完了太虚剑神")
}func main() {接口的使用()
}func 接口的使用() {u := 学习{"符华"}var cj 寸劲//cj = u // 接口赋值为 学习 结构体,只有当实现了接口的全部方法才能赋值给接口,否则无法赋值cj = &u // 只要接口方法有一个指针实现,则此处必须是指针if u1, ok := cj.(*学习); ok { // 通过类型断言,来调用 结构体 独有的方法fmt.Println(u1.开始学习())}cj.寸劲岩破()fmt.Println(cj.寸劲山崩())fmt.Println(cj.寸劲开天(2))/*类型断言:由于接口是一般类型,不知道具体类型,如果要转成具体类型,就需要使用类型断言语法:接口.(类型),类型不是什么类型都可以传,必须要 接口 原先指向什么类型,那么就传什么类型返回两个值,可以通过返回的 true、false 来判断断言(转换)是否成功*///var jq 太虚剑气//jq, ok := cj.(太虚剑气)if jq, ok := cj.(太虚剑气); ok { // 如果转换成功,ok为truefmt.Println(jq.太虚剑神(10))} else {fmt.Println("转换失败")}var a interface{}a = u // 将 u 赋值给a,然后将 a 重新赋值给一个 学习 类型的变量,这就需要用 类型断言var u1 学习//u1 = a // 这里不可以直接赋值,需要使用类型断言u1 = a.(学习) // a 原先指向 学习 类型,所以传类型时也必须要传 学习 类型fmt.Println(u1)// 空接口dataPrint(u, "空接口", 123, 12.65, []int{1, 2, 3}, make(map[string]string, 2))
}
反射
Go中,使用反射需要导入 reflect
包
使用反射时,主要有两个很重要的方法:
reflect.TypeOf(变量名)
,获取变量的类型,返回reflect.Type
类型(是一个接口)reflect.ValueOf(变量名)
,获取变量的值,返回reflect.Value
类型(是一个结构体类型)
变量、interface{} 和 reflect.Value 是可以相互转换的,如下图:
package mainimport ("fmt""reflect"
)/*
反射:需要导入 reflect 包
主要有两个函数:reflect.TypeOf(变量名),获取变量的类型,返回 reflect.Type 类型(是一个接口)reflect.ValueOf(变量名),获取变量的值,返回 reflect.Value 类型(是一个结构体类型)变量、interface{}和 reflect.Value 是可以相互转换的
*/type student struct {Name string `json:"name"`Age int
}// 给 student 结构体绑定方法
func (s student) PrintStu() {fmt.Println(s)
}
func (s student) GetSum(a, b int) {fmt.Println(a + b)
}// 基本数据类型、interface{}、reflect.Value 相互转换
func reflectTest01(a interface{}) {// 通过反射获取传入的变量的 typerTyp := reflect.TypeOf(a)fmt.Println("rTyp=", rTyp)// 获取到 reflect.ValuerVal := reflect.ValueOf(a)n1 := 10 + rVal.Int() // 通过反射来获取变量的值,要求数据类型匹配:reflect.Value.Int()、reflect.Value.String()、reflect.Value.Float()......fmt.Println(n1)fmt.Printf("rVal=%v , rVal的类型=%T\n", rVal, rVal)// 将 reflect.Value 转回 interface{}iV := rVal.Interface()// 将 interface{} 通过断言转回 需要的类型n2 := iV.(int)fmt.Println(n2)
}// 对结构体的反射
func reflectTest02(a interface{}) {// 通过反射获取传入的变量的 typerTyp := reflect.TypeOf(a)fmt.Println("rTyp=", rTyp)// 获取到 reflect.ValuerVal := reflect.ValueOf(a)fmt.Printf("rVal=%v , rVal的类型=%T\n", rVal, rVal)fmt.Println("kind=", rVal.Kind(), rTyp.Kind())// 将 reflect.Value 转回 interface{}iV := rVal.Interface()// 通过反射来获取结构体的值,需要先断言// 将 interface{} 通过断言转回 需要的类型stu := iV.(student)fmt.Println(stu)
}// 通过反射改变值(必须传入指针,才能改变值)
func reflectTest03(a interface{}) {rTyp := reflect.TypeOf(a) // 通过反射获取传入的变量的 typefmt.Println("rTyp=", rTyp)rVal := reflect.ValueOf(a) // 获取到 reflect.Valueswitch a.(type) { // 判断传入的参数的类型case *int:n1 := 10 + rVal.Elem().Int() // 通过反射来获取变量的值,因为传入的是指针,所以要先用 Elem() 再获取值fmt.Println(n1)fmt.Printf("rVal=%v , rVal的类型=%T\n", rVal.Elem(), rVal)rVal.Elem().SetInt(200) // 通过反射改变值case *student:e := rVal.Elem()e.FieldByName("Name").SetString("白夜") // 给指定的字段名改变值}
}// 通过反射遍历结构体的方法和属性
func reflectTest04(a interface{}) {rTyp := reflect.TypeOf(a)if rTyp.Kind() != reflect.Struct { // 判断传入的参数是否是结构体return}rVal := reflect.ValueOf(a)// 遍历结构体字段numField := rTyp.NumField() // 获取结构体字段的数量fmt.Println("numField =", numField)for i := 0; i < numField; i++ {// 打印字段的类型、字段名、字段值、字段标签fmt.Println(rTyp.Field(i).Type, rTyp.Field(i).Name, "=", rVal.Field(i), rTyp.Field(i).Tag.Get("json"))}// 遍历结构体方法numMethod := rTyp.NumMethod() // 获取结构体方法的数量// 关于方法遍历时,方法的索引:是根据方法名称的ACSII码来排序的for i := 0; i < numMethod; i++ {// 打印方法的类型、方法名//fmt.Println(rTyp.Method(i).Type, rTyp.Method(i).Name)if i == 0 {var params []reflect.Valueparams = append(params, reflect.ValueOf(10))params = append(params, reflect.ValueOf(20))rVal.Method(i).Call(params)} else {rVal.Method(i).Call(nil)}}
}func main() {// 基本数据类型、interface{}、reflect.Value 相互转换//var num int = 100//reflectTest01(num)//reflectTest03(&num) // 修改值必须传指针//fmt.Println("通过反射改变num的值", num)stu := student{"符华", 20}//reflectTest02(stu)//reflectTest03(&stu) // 修改值必须传指针//fmt.Println("通过反射改变stu的值", stu)reflectTest04(stu)
}
协程
接下来我们讲协程
协程:一个进程有多个线程,一个线程可以起多个协程
特点
- 有独立的栈空间
- 共享程序堆空间
- 调度由用户控制
- 协程是轻量级的线程
主线程结束后,协程会被中断,这时需要一个有效的阻塞机制。
WaitGroup
如果主线程退出了,即使协程还没有执行完毕,也会退出。这时,我们可以使用WaitGroup,它用于等待一组协程的结束。
- 父线程调用Add方法来设定应等待的协程的数量。
- 每个被等待的协程在结束时应调用Done方法。
- 同时,主线程里可以调用Wait方法阻塞至所有协程结束。
goroutine的调度模型:MPG模型
- M:操作系统的主线程(是物理线程)
- P:协程执行需要的上下文
- G:协程
使用 goroutine 效率高,但是会出现并发/并行安全问题,需要加锁解决这个问题。
如果协程发生异常,可以用recover来捕获异常,进行除了。这样主函数不会受到影响,可以继续执行。
package mainimport ("fmt""strconv""sync""time"
)// 一个函数,每隔1秒输出
func goroutineTest01() {for i := 0; i < 10; i++ {fmt.Println("test() hello,world " + strconv.Itoa(i))time.Sleep(time.Second)}
}var (myMap = make(map[int]int, 10)// 定义一个全局的互斥锁lock sync.Mutex // sync 同步的意思,Mutex 互斥的意思wg sync.WaitGroup // 用于等待一组线程的结束
)func goroutineTest02(n int) {res := 1for i := 1; i <= n; i++ {res *= i}lock.Lock() // 写之前加锁myMap[n] = res // concurrent map writes 并发写入问题lock.Unlock() // 写完之后解锁//wg.Add()中有20个需要执行的协程,每执行完一个后调用wg.Done(),让协程数量-1,直到协程数量为0,表示全部协程执行完毕wg.Done() // 这里表示每执行完一个协程,wg.Add()里面的数量-1
}func main() {协程()
}func 协程() {//goroutineTest01() // 如果这样调用,这里是先执行完goroutineTest01,再执行main里面的打印//go goroutineTest01() // 开启了一个线程,这样goroutineTest01和main就是同时执行//for i := 0; i < 10; i++ {// fmt.Println("main() hello,world " + strconv.Itoa(i))// time.Sleep(time.Second)//}//cpuNum := runtime.NumCPU() //获取电脑的cpu数量//fmt.Println("cpu个数:", cpuNum)// 可以自己设置使用多少个cpu//runtime.GOMAXPROCS(cpuNum - 1) // 预留一个cpuwg.Add(20) // 这里表示有20个协程需要执行// 开启多个协程for i := 1; i <= 20; i++ {go goroutineTest02(i)}wg.Wait() // 告诉主线程要等一下,等协程全部执行完了载退出fmt.Println("全部协程执行完毕")// 遍历输出map结果for i, v := range myMap {fmt.Printf("map[%d]=%d\n", i, v)}
}func 协程异常捕获() {// 这里我们可以使用defer + recover来捕获异常defer func() {if err := recover(); err != nil {fmt.Println("发生错误,错误信息:", err)}}()var myMap map[int]stringmyMap[0] = "Go" // map没有make,出现error
}
channel
channel也就是管道,一般情况下,我们是配合协程一起使用的。
channel管道:本质就是一个数据结构——队列
介绍
- 数据是先进先出:FIFO:first in first out
- 线程安全,多goroutine访问时,不需要加锁,就是说channel本身就是线程安全的
- channel是有类型的,一个string的channel只能存放string类型数据
- channel必须是引用类型,必须初始化才能写入数据,也就是需要make后才能使用
语法:
var 变量名 chan 数据类型
,变量名 = make(chan 数据类型, 容量)
(使用make进行初始化)
举例:
var intChan chan int // 用于存放int数据
var mapChan chan map[int]string // 用于存放map[int]string数据
var perChan chan Pserson // 用于存放Pserson结构体数据
var perChan1 chan *Pserson //用于存放Pserson结构体指针数据
var perChan1 chan interface{} //可以存放任何类型数据,但是取的时候要注意用类型断言
channel遍历
-
通常使用for-range方式进行遍历,不用取长度的方式来遍历管道是因为管道每取一次,长度就会变。
-
在遍历时,如果管道没有关闭,会出现deadlock的错误;如果管道已经关闭,则正常遍历数据,遍历完后,退出遍历。
管道可以声明为只读或只写(默认情况下是双向的,也就是可读可写)
- 只写:
var intChan chan<- int
- 只读:
var intChan <-chan int
基本使用
func 管道() {// 创建一个可以存放3个int类型的管道var intChan chan int// 因为channel是引用类型,它的值其实是一个地址,然后这个地址指向的就是管道队列;然后intChan本身也有一个地址intChan = make(chan int, 3)fmt.Printf("intChan 的值=%v intChan 本身的地址=%p\n", intChan, &intChan) // intChan 的值=0xc00006e080 intChan 本身的地址=0xc00004c020// 向管道写入数据,写入、读取管道数据时,用 <- 表达式intChan <- 10num := 200intChan <- num// 设置的管道容量是3,最多只能往里面写入3条数据(长度不能超过容量)intChan <- 100// 管道的长度和cap(容量)fmt.Printf("管道 长度 = %v 容量 = %v\n", len(intChan), cap(intChan)) // 3,3// 读取管道的数据。从管道中取出了数据,可以再往里面放数据//<-intChan // 可以直接这么写,也是取出数据;不用变量接收,把取出的数据扔了不要n1 := <-intChan // 这里取出来的是最先写入到管道里的数据(先进先出)fmt.Println("n1=", n1) // 10fmt.Printf("管道 长度 = %v 容量 = %v\n", len(intChan), cap(intChan)) // 2,3// 取出了一条数据后再往里面放一条数据intChan <- 500close(intChan) // 关闭管道,这时就不能再往管道里面写入数据了,但是读取没问题fmt.Printf("管道 长度 = %v 容量 = %v\n", len(intChan), cap(intChan)) // 3,3// 在没有使用协程的情况下,如果管道数据已经全部取出,再取会报错n2 := <-intChann3 := <-intChanfmt.Printf("n2 = %v n3 = %v\n", n2, n3) // 200,100fmt.Printf("管道 长度 = %v 容量 = %v\n", len(intChan), cap(intChan)) // 1,3// 遍历管道intChan2 := make(chan int, 100)for i := 0; i < 100; i++ {intChan2 <- i * 2}close(intChan2) // 管道写完数据后,先将管道关闭,再进行遍历// 不能用取长度的方式来遍历管道,因为管道每取一次,长度就会变,要用 range 方式遍历for v := range intChan2 { // 这里只返回一个数据,管道里面没有下标fmt.Println("v =", v)}
}
和协程一起使用
案例一
package mainimport ("fmt""sync"
)// 全局 WaitGroup 变量
var wg sync.WaitGroup // 用于等待一组线程的结束// 管道写入数据
func writeData(intChan chan int) {for i := 0; i < 50; i++ {intChan <- ifmt.Printf("writeData 写入数据=%v\n", i)}close(intChan)wg.Done() // 执行完一个线程后,调用这个方法,主线程中需要等待执行的协程数量-1
}// 管道读取数据
func readData(intChan chan int) {for {v, ok := <-intChanif !ok {break}fmt.Printf("readData 读到数据=%v\n", v)}wg.Done() // 执行完一个线程后,调用这个方法,主线程中需要等待执行的协程数量-1
}func main() {协程和管道应用1()
}func 协程和管道应用1() {// 创建两个管道intChan := make(chan int, 10)wg.Add(2) // 说明开启了两个线程// 开启了两个协程,writeData和readData应该是交叉执行的go writeData(intChan) // 开启一个协程,往 intChan 中写入数据go readData(intChan) // 开启一个协程,读取 intChan 的数据wg.Wait() // 告诉主线程需要等待协程执行完毕fmt.Println("程序执行完毕!")
}
案例二
-
需求:要求统计1-8000的数字中,哪些是素数
-
将统计素数的任务,分配给4个协程去完成
// 判断是否为素数
func isPrime(intChan, primeChan chan int) {var isPrime bool // 标识是否是素数for v := range intChan {isPrime = truefor i := 2; i < v; i++ {if v%i == 0 {isPrime = falsebreak}}if isPrime { // 如果为素数,则往primeChan中写入数据primeChan <- v}}fmt.Println("isPrime 读取素数完毕")wg.Done() // 执行完一个线程后,调用这个方法,主线程中需要等待执行的协程数量-1
}func 协程和管道应用2() {// 需求:要求统计1-8000的数字中,哪些是素数// 将统计素数的任务,分配给4个协程去完成intChan := make(chan int, 1000) // 读写1-8000数字的管道primeChan := make(chan int, 2000) // 存储素数的管道wg.Add(5) // 下面开启了5个协程// 开启写入 1-8000 数字的协程go func() {for i := 1; i <= 8000; i++ {intChan <- i}close(intChan)wg.Done()}()// 开启4个读取 1-8000 数字,并统计素数的协程for i := 0; i < 4; i++ {go isPrime(intChan, primeChan)}wg.Wait() // 等待协程执行完毕close(primeChan) // 关闭 primeChan 管道// 遍历primeChan,把结果取出来for v := range primeChan {fmt.Printf("素数是 = %v\n", v)}
}
select…case
传统的方法在遍历管道时,如果不关闭会阻塞而导致 deadlock。
在实际开发中,可能不好确定什么时候关闭管道,这时可以使用select方式解决。
func 管道注意细节() {intChan := make(chan int, 10)for i := 0; i < 10; i++ {intChan <- i}stringChan := make(chan string, 5)for i := 0; i < 5; i++ {stringChan <- "hello" + fmt.Sprintf("%d", i)}// 传统的方法在遍历管道时,如果不关闭会阻塞而导致 deadlock// 在实际开发中,可能不好确定什么时候关闭管道,这时可以使用select方式解决for {select {// 这里如果intChan一直没有关闭,也不会一直阻塞而导致deadlock// 如果一个case取不到数据,会自动到下一个case中取case v := <-intChan:fmt.Println("从intChan读取的数据=", v)case v := <-stringChan:fmt.Println("从stringChan读取的数据=", v)default:fmt.Println("都取不到了")return}}
}
main.go 完整代码
package mainimport ("fmt""sync"
)var wg sync.WaitGroup // 用于等待一组线程的结束// 管道写入数据
func writeData(intChan chan int) {for i := 0; i < 50; i++ {intChan <- ifmt.Printf("writeData 写入数据=%v\n", i)}close(intChan)wg.Done() // 执行完一个线程后,调用这个方法,主线程中需要等待执行的协程数量-1
}// 管道读取数据
func readData(intChan chan int) {for {v, ok := <-intChanif !ok {break}fmt.Printf("readData 读到数据=%v\n", v)}wg.Done() // 执行完一个线程后,调用这个方法,主线程中需要等待执行的协程数量-1
}// 判断是否为素数
func isPrime(intChan, primeChan chan int) {var isPrime bool // 标识是否是素数for v := range intChan {isPrime = truefor i := 2; i < v; i++ {if v%i == 0 {isPrime = falsebreak}}if isPrime { // 如果为素数,则往primeChan中写入数据primeChan <- v}}fmt.Println("isPrime 读取素数完毕")wg.Done() // 执行完一个线程后,调用这个方法,主线程中需要等待执行的协程数量-1
}func main() {//管道()//协程和管道应用1()//协程和管道应用2()//管道注意细节()
}func 管道() {// 创建一个可以存放3个int类型的管道var intChan chan int// 因为channel是引用类型,它的值其实是一个地址,然后这个地址指向的就是管道队列;然后intChan本身也有一个地址intChan = make(chan int, 3)fmt.Printf("intChan 的值=%v intChan 本身的地址=%p\n", intChan, &intChan) // intChan 的值=0xc00006e080 intChan 本身的地址=0xc00004c020// 向管道写入数据,写入、读取管道数据时,用 <- 表达式intChan <- 10num := 200intChan <- num// 设置的管道容量是3,最多只能往里面写入3条数据(长度不能超过容量)intChan <- 100// 管道的长度和cap(容量)fmt.Printf("管道 长度 = %v 容量 = %v\n", len(intChan), cap(intChan)) // 3,3// 读取管道的数据。从管道中取出了数据,可以再往里面放数据//<-intChan // 可以直接这么写,也是取出数据;不用变量接收,把取出的数据扔了不要n1 := <-intChan // 这里取出来的是最先写入到管道里的数据(先进先出)fmt.Println("n1=", n1) // 10fmt.Printf("管道 长度 = %v 容量 = %v\n", len(intChan), cap(intChan)) // 2,3// 取出了一条数据后再往里面放一条数据intChan <- 500close(intChan) // 关闭管道,这时就不能再往管道里面写入数据了,但是读取没问题fmt.Printf("管道 长度 = %v 容量 = %v\n", len(intChan), cap(intChan)) // 3,3// 在没有使用协程的情况下,如果管道数据已经全部取出,再取会报错n2 := <-intChann3 := <-intChanfmt.Printf("n2 = %v n3 = %v\n", n2, n3) // 200,100fmt.Printf("管道 长度 = %v 容量 = %v\n", len(intChan), cap(intChan)) // 1,3// 遍历管道intChan2 := make(chan int, 100)for i := 0; i < 100; i++ {intChan2 <- i * 2}close(intChan2) // 管道写完数据后,先将管道关闭,再进行遍历// 不能用取长度的方式来遍历管道,因为管道每取一次,长度就会变,要用 range 方式遍历for v := range intChan2 { // 这里只返回一个数据,管道里面没有下标fmt.Println("v =", v)}
}func 协程和管道应用1() {// 创建两个管道intChan := make(chan int, 10)wg.Add(2) // 说明开启了两个线程// 开启了两个协程,writeData和readData应该是交叉执行的go writeData(intChan) // 开启一个协程,往 intChan 中写入数据go readData(intChan) // 开启一个协程,读取 intChan 的数据wg.Wait() // 告诉主线程需要等待协程执行完毕fmt.Println("程序执行完毕!")
}func 协程和管道应用2() {// 需求:要求统计1-8000的数字中,哪些是素数// 将统计素数的任务,分配给4个协程去完成intChan := make(chan int, 1000) // 读写1-8000数字的管道primeChan := make(chan int, 2000) // 存储素数的管道wg.Add(5) // 下面开启了5个协程// 开启写入 1-8000 数字的协程go func() {for i := 1; i <= 8000; i++ {intChan <- i}close(intChan)wg.Done()}()// 开启4个读取 1-8000 数字,并统计素数的协程for i := 0; i < 4; i++ {go isPrime(intChan, primeChan)}wg.Wait() // 等待协程执行完毕close(primeChan) // 关闭 primeChan 管道// 遍历primeChan,把结果取出来for v := range primeChan {fmt.Printf("素数是 = %v\n", v)}
}func 管道注意细节() {intChan := make(chan int, 10)for i := 0; i < 10; i++ {intChan <- i}stringChan := make(chan string, 5)for i := 0; i < 5; i++ {stringChan <- "hello" + fmt.Sprintf("%d", i)}// 传统的方法在遍历管道时,如果不关闭会阻塞而导致 deadlock// 在实际开发中,可能不好确定什么时候关闭管道,这时可以使用select方式解决for {select {// 这里如果intChan一直没有关闭,也不会一直阻塞而导致deadlock// 如果一个case取不到数据,会自动到下一个case中取case v := <-intChan:fmt.Println("从intChan读取的数据=", v)case v := <-stringChan:fmt.Println("从stringChan读取的数据=", v)default:fmt.Println("都取不到了")return}}
}
文件
文件这块没啥好说的,拿到函数直接用就行。需要注意一点就是文件file是一个指针类型。
package mainimport ("bufio""fmt""io""os"
)func main() {//基本使用读()基本使用写()
}func 基本使用读() {// 打开文件file, err := os.Open("C:\\Users\\Administrator\\Desktop\\1.txt")if err != nil {fmt.Println("文件打开错误:", err)}//fmt.Printf("file=%v", file) // 输出的是一个地址defer file.Close() // 当函数退出时,要关闭file,否则会有内存泄露// 创建一个Reader,是带缓冲,默认缓冲区为4096(这种方式比较适合大文件读取)reader := bufio.NewReader(file)for {str, err := reader.ReadString('\n')if err == io.EOF { // io.EOF表示读到了文件末尾,这时就可以退出循环了break}fmt.Print(str)}fmt.Println("文件读取完成")// ioutil.ReaderFile,一次性将文件读取到位 这种方法适合读取比较小的文件// 不过新版本 ioutil.ReadFile 已经弃用了,这个函数其实调用的就是 os.ReadFilecontent, err := os.ReadFile("C:\\Users\\Administrator\\Desktop\\学习计划.txt")if err != nil {fmt.Printf("文件读取失败:%v", err)}//fmt.Println(content) // content是一个 []bytefmt.Println(string(content)) // 所以要转成string}func 基本使用写() {filePath := "C:\\Users\\Administrator\\Desktop\\测试.txt"/*OpenFile 第二个参数:文件打开模式O_RDONLY int = syscall.O_RDONLY // 只读O_WRONLY int = syscall.O_WRONLY // 只写O_RDWR int = syscall.O_RDWR // 读写O_APPEND int = syscall.O_APPEND // 追加O_CREATE int = syscall.O_CREAT // 如果不存在就创建O_EXCL int = syscall.O_EXCL // 文件必须不存在O_SYNC int = syscall.O_SYNC // 同步ioO_TRUNC int = syscall.O_TRUNC // 打开时清空文件(一般用于覆盖写入)第三个参数只作用于linux系统,Windows系统不起作用*/file, err := os.OpenFile(filePath, os.O_WRONLY|os.O_CREATE, 0666)if err != nil {fmt.Printf("文件打开失败:%v", err)}defer file.Close()str := "hello,Golang\n"// NewWriter 带缓冲区的写入,写完之后要用flush刷新。writer := bufio.NewWriter(file)for i := 0; i < 5; i++ {writer.WriteString(str)}writer.Flush()
}
ok,以上就是本篇的全部内容了。下一篇可能是关于网络请求相关的,也有可能是Gorm相关的。
相关文章:
【Java转Go】快速上手学习笔记(四)之基础篇三
目录 泛型内置泛型的使用切片泛型和泛型函数map泛型泛型约束泛型完整代码 接口反射协程特点WaitGroupgoroutine的调度模型:MPG模型 channel介绍语法:举例:channel遍历基本使用和协程一起使用案例一案例二 select...casemain.go 完整代码 文件…...
vue中form和table标签过长
form标签过长 效果: 代码: <el-form-item v-for"(item,index) in ticketEditTable1" :label"item.fieldNameCn" :propitem.fieldName :key"item.fieldNameCn" overflow"":rules"form[item.fieldName…...
java基础复习(第七日)
java基础复习(七) 1.MQ如何避免消息重复投递或重复消费? 在消息生产时,MQ 内部针对每条生产者发送到消息生成一个 inner-msg-id,作为去重的依据(消息投递失败并重传),避免重复的消息进入队列;…...
day24 | 理论基础、77. 组合
目录: 解题及思路学习 理论基础 回溯的本质是穷举,穷举所有可能,然后选出我们想要的答案,如果想让回溯法高效一些,可以加一些剪枝的操作,但也改不了回溯法就是穷举的本质。 回溯法,一般可以…...
数据结构(1)
数据结构其实就是将数据按照一定的关系组织起来的集合,用于组织和存储数据。 1.数据结构分类 1.逻辑结构 逻辑结构是从具体问题中抽象出来的模型,是抽象意义的结构,按照对象中数据的相互关系进行分类。 1>集合结构:集合结构中…...
10个非常有用的Python库,你知道几个?
整理|TesterHome 这里给大家介绍10个不是最流行但非常有用的Python库,希望可以提供参考帮助。 PyO3 PyO3是一个Rust库,可以让你在Rust中编写Python模块。它可以利用 Rust 的速度和安全性编写高性能的 Python 模块。 https://github.com/PyO3…...
linux安装 MySQL8 并配置开机自启动
目录 1.下载 mysql 安装包 2.上传并解压 mysql 3.修改 mysql 文件夹名 4.创建mysql 用户和用户组 5.数据目录 (1)创建目录 (2)赋予权限 6.初始化mysql (1)配置参数 (2)配置环…...
MySQL视图
一、视图-介绍及基本语法 视图(View)是一种虚拟存在的表。视图中的数据并不在数据库中实际存在,行和列数据来自定义视图的查询中使用的表,并且是在使用视图时动态生成的。 通俗的讲,视图只保存了查询的SQL逻辑…...
Pytorch-day05-可视化-checkpoint
PyTorch 可视化 1、模型结构可视化2、训练过程可视化3、模型评估可视化 #导入常用包 import os import numpy as np import torch from torch import nn from torch.utils.data import Dataset, DataLoader from torchvision.transforms import transforms import torchvis…...
实训笔记8.23
8.23笔记 8.23笔记一、Hive中函数1.1 Hive中内置函数1.1.1 数学函数1.1.2 字符串函数1.1.3 日期函数1.1.4 条件函数1.1.5 特殊函数 1.2 Hive的自定义函数1.2.1 自定义UDF1.2.2 自定义UDTF 二、Hive的压缩机制三、数据同步工具Sqoop的安装和使用3.1 sqoop的概念3.2 sqoop的核心功…...
2023年菏泽市中职学校技能大赛“网络安全”赛项规程
2023年菏泽市中职学校技能大赛 “网络安全”赛项规程 一、赛项名称 赛项名称:网络安全 赛项所属专业大类:信息技术类 二、竞赛目的 通过竞赛,检验参赛选手对网络、服务器系统等网络空间中各个信息系统的安全防护能力,以及分析…...
Android 13 - Media框架(6)- NuPlayer
上一节我们通过 NuPlayerDriver 了解了 NuPlayer 的使用方式,这一节我们一起来学习 NuPlayer 的部分实现细节。 ps:之前用 NuPlayer 播放本地视频很多都无法播放,所以觉得它不太行,这两天重新阅读发现它的功能其实很全面ÿ…...
机器学习|DBSCAN 算法的数学原理及代码解析
机器学习|DBSCAN 算法的数学原理及代码解析 引言 聚类是机器学习领域中一项重要的任务,它可以将数据集中相似的样本归为一类。DBSCAN(Density-Based Spatial Clustering of Applications with Noise)是一种是一种经典的密度聚类…...
用NUXT.JS,轻松搞定SEO!
nuxt.js 是什么? 如果你正在准备开发一个SEO友好的新项目,而且准备用 vue 开发,那么恭喜你,用 nuxt 是一个成本和效率都比较优秀的方案。 官方文档 知识中心案例 简单介绍下背景,这是一个专门为氚云低代码平台引流…...
什么是电商RPA?电商RPA能解决什么问题?电商RPA实施难点在哪里?
RPA机器人可以应用于各个行业和领域,例如金融、保险、制造、物流、电商等。它可以减少人工错误和重复工作,提高效率和生产力。RPA还可以在处理大量数据时加快处理速度,提供更准确和可靠的结果。此外,RPA还可以为员工提供更有价值的…...
【BUG】Docker启动MySQL报错
个人主页:金鳞踏雨 个人简介:大家好,我是金鳞,一个初出茅庐的Java小白 目前状况:22届普通本科毕业生,几经波折了,现在任职于一家国内大型知名日化公司,从事Java开发工作 我的博客&am…...
Spring Boot通过企业邮箱发件被Gmail退回的解决方法
这两天给我们开发的Chrome插件:Youtube中文配音 增加了账户注册和登录功能,其中有一步是邮箱验证,所以这边会在Spring Boot后台给用户的邮箱发个验证信息。如何发邮件在之前的文章教程里就有,这里就不说了,着重说说这两…...
Windows使用MobaXterm远程访问ubuntu20.04桌面
参考ubuntu 2020.4 安装vnc 一、脚本文件 remote_setup.sh脚本文件内容: #! /bin/bash #参考链接:https://blog.csdn.net/hailangdeyingzi/article/details/124507304 sudo apt update sudo apt install x11vnc -y sudo x11vnc -storepasswd telpo.12…...
C++注释风格
1. 文件头注释 每个文件都应该开始于一个注释块,描述文件的目的、作者、创建日期和版权信息。 /** FileName: MyClass.cpp* Purpose: Provides functionality for XYZ operations.* Author: [Your Name]* Creation Date: YYYY-MM-DD* Last Updated: YYYY-MM-DD* C…...
Linux 编译内核模块出现--Unknown symbol mcount
文章目录 Linux suse: # cat /etc/os-release NAME"SLES" VERSION"12-SP2" VERSION_ID"12.2" PRETTY_NAME"SUSE Linux Enterprise Server 12 SP2" ID"sles" ANSI_COLOR"0;32" CPE_NAME"cpe:/o:s…...
Pywin32 Cookbook by Eric
Writing Prompt 现在你是一名专业的Python工程师,请你根据"Pywin32_Funtion"函数的功能,为其编写一个清晰的文档说明Functions win32gui.GetWindowDC(hwnd) 描述 win32gui.GetWindowDC()函数用于获取指定窗口的设备上下文(Devi…...
indexDB入门到精通
前言 由于开发3D可视化项目经常用到模型,而一个模型通常是几m甚至是几十m的大小对于一般的服务器来讲加载速度真的十分的慢,为了解决这个加载速度的问题,我想到了几个本地存储的。 首先是cookie,cookie肯定是不行的,因为最多以只…...
Ubuntu 20.04配置静态ip
ip配置文件 cd /etc/netplan配置 根据需求增加 # Let NetworkManager manage all devices on this system network:version: 2renderer: NetworkManager # 管理 不是必须ethernets:enp4s0: #网卡名dhcp4: no #关闭ipv4动态分配ip地址dhcp6: no #关闭ipv6动态分配…...
Tushare入门小册
Tushare入门小册 一、Tushare平台介绍 Pro版数据更稳定质量更好了,我们提供的不再是直接从互联网抓取,而是通过社区的采集和整理存入数据库经过质量控制后再提供给用户。但Pro依然是个开放的,免费的平台,不带任何商业性质和目的…...
<c++开发>通信工具 -之-SOME/IP移植部署 第一篇文章
<c开发>通信工具 -之-SOME/IP移植ubuntu部署 第一篇文章 一 前言 SOME/IP (Scalable service-Oriented MiddlewarE over IP) 是一种通信协议,主要用于嵌入式系统和车载网络中的服务导向通信。SOME/IP是AUTOSAR(AUTomotive Open …...
权威的软件测试服务供应商分享,怎么获得软件安全检测报告?
我们深知在如今的数字化时代,软件安全对于企业和个人来说具有极其重要的意义。然而,许多用户对于软件安全测试报告的概念还不够清晰,也不知道如何获得这样的报告。在本文中,小编将为您简析什么是安全测试报告以及如何获取这样的报…...
管理类联考——逻辑——真题篇——按知识分类——汇总篇——二、论证逻辑——假设——第二节——搭桥假设
文章目录 第二节 假设-分类1-搭桥假设-当题干推理存在明显断点,常见形式比如:“因为A→B,C→D,所以A→D”,则正确选项为“B→C”真题(2014-39)-假设-分类1-题干推理存在明显断点-搭桥假设-建模搭桥-“因为A→B,所以A→C”,搭桥假设为“B→C”真题(2019-44)-假设-分…...
百度云BOS云存储的图片如何在访问时,同时进行格式转换、缩放等处理
前言 之前做了一个图片格式转换和压缩的服务,结果太占内存。后来查到在访问图片链接时,支持进行图片压缩和格式转换,本来想着先格式转换、压缩图片再上传到BOS,现在变成了上传后,访问时进行压缩和格式转换。想了想&am…...
go生成文件md5、sha1摘要简单示例
备注 go官方文档 https://pkg.go.dev/crypto/md5 已经给出如何使用该package生成文件或者字节数组的摘要值, 参照即可。 摘要值不是对文内容的加密,它主要用来进行checksum,就是验证两个文件内容是否一致,是否被篡改或者变化了。…...
Docker容器:docker数据管理、镜像的创建及dockerfile案例
文章目录 一、docker数据管理1.为何需要docker数据管理2.数据管理类型3.数据卷4.数据卷容器5.容器的互联 二.docker镜像的三种创建方法1.基于现有镜像创建1.1 启动镜像1.2 生成新镜像 2.基于本地模板创建2.1 OPENVZ 下载模板2.2 导入容器生成镜像 3.基于dockerfile创建3.1 dock…...
建网站 铸品牌 做推广/百度竞价关键词查询
转载:https://blog.csdn.net/qingtiantianqing/article/details/72783952 原文: 使用handler发送消息时有两种方式,post(Runnable r)和post(Runnable r, long delayMillis)都是将指定Runnable(包装成PostMessage)加入…...
点击最高的模板网站/搜索引擎优化主要包括
先说下我这个有啥好处 主要是能够自定义设置裁剪图片的比例.比如 :今天产品 给你说裁剪 成16:9 的图片. 你做好了 OK 明天 产品又和你说 裁剪成10:7 的图片 ,你是不是要吐血, 我这里只要设置一行代码就可以改变裁剪的比例.OK 还是老规矩 先上效果图 看看啥样子好了 不扯其他的了…...
dwcs2018怎么做动态网站/高明公司搜索seo
EG:打印文件结果打印出一片空白 原因:使用了null的数据源而不是JREmptyDataSource 以下为正确代码 public <T> List<JasperPrint> createJasperPrint_1(List<T> list, Map<String, Object> imgMap, Map<String, Object>…...
北京做网站s/深圳网站优化排名
代码实现报表打印 //初始化报表信息 private void SetReportInfo(string reportPath,string sourceName,DataTable dataSource,bool isFengPi) {if (!File.Exists(reportPath)) { MessageBox.Show("报表文件:" reportPath " 不存在!","提示&…...
门户网站建设方案模板/搜索引擎广告的优缺点
[ 传 送 门 ] 目录前言思路code:前言 人在美国 , 刚下飞机, 还没睡醒 搁着看了半天题意没看懂什么意思(尤其是这个对称数组 (小声bb)) 思路 按照题目直接跑一遍 最小生成树 emm 还真过了 code: #include <bits/stdc.h> using namespace std; const int N 1e510; …...
其它区便宜营销型网站建设/宁波seo搜索优化费用
eclipse界面定制,让eclipse看着更清爽转载于:https://www.cnblogs.com/passer1991/p/3227755.html...