GoLang语言学习记录(五)


createdtime 20211105

updatedtime 20211105

author venki.chen


  1. 流:数据在数据源(文件)和程序(内存)之间经历的路径,输入流:数据从数据源(文件)到程序(内存)的路径,输出流:数据从程序(内存)到数据源(文件)的路径。
  2. 文件操作实例:
func fileOperator02() {
	file, err := os.Open("../other/file.txt")
	if err != nil {
		fmt.Println("open file err:", err)
	}

	// 程序执行结束,关闭文件句柄
	defer file.Close()

	/*创建一个*Reader并且是带缓存区的
	const (
		defaultBufSize = 4096 // 缓存区默认4096kb
	)
	*/
	reader := bufio.NewReader(file)

	// 循环读取文件内容
	for {
		str, err := reader.ReadString('\n') // 读到一个换行符那么结束该行的循环,开始下一行
		if err == io.EOF {                  // io.EOF表示文件的末尾
			break
		}
		fmt.Print(str)
	}

	fmt.Println("文件读取结束")
}
  1. 判断文件或者文件夹是否存在
// PathExists 判断文件或者目录是否存在
func PathExists(path string) (bool, error) {
	_, err := os.Stat(path)
	if err == nil {
		return true, nil // 文件或者文件夹存在
	}
	if os.IsNotExist(err) {
		return false, nil // 文件或者文件夹不存在
	}
	return false, nil
}
  1. 文件迁移
// FileMove 文件迁移
func FileMove() {
	file1Path := "../other/venki.txt"
	file2Path := "../another/chen.txt"
	data, err := ioutil.ReadFile(file1Path)
	if err != nil {
		fmt.Printf("when pc are reading file1 ,err=%v\n", err)
	}

	err = ioutil.WriteFile(file2Path, data, 0666)

	if err != nil {
		fmt.Printf("when pc are writing file2 ,err=%v\n", err)
	}
}
  1. 文件拷贝
func CopyFile(dstFileName string, srcFileName string) (written int64, err error) {
	srcfile, err := os.Open(srcFileName)
	if err != nil {
		fmt.Printf("when pc are opening srcfile err=%v\n", err)
	}
	defer srcfile.Close()
	reader := bufio.NewReader(srcfile)

	dstFile, err := os.OpenFile(dstFileName, os.O_WRONLY|os.O_CREATE, 0666)
	if err != nil {
		fmt.Printf("when pc are opening dstFile err=%v\n", err)
		return
	}
	defer dstFile.Close()
	writer := bufio.NewWriter(dstFile)
	return io.Copy(writer, reader)
}
  1. 统计不同数据类型的个数
type CharCount struct {
	ChCount    int // 记录英文个数
	NumCount   int // 记录数字个数
	SpaceCount int // 记录空格个数
	OtherCount int // 记录其他个数
}

// CalcTypeStringNumber 统计文件中不同字符的数量
func CalcTypeStringNumber(filePath string) {
	file, err := os.Open(filePath)
	if err != nil {
		fmt.Printf("when pc are reading file err=%v\n", err)
		return
	}
	defer file.Close()
	var count CharCount
	reader := bufio.NewReader(file)
	for {
		str, err := reader.ReadString('\n')
		if err == io.EOF { // 督导读到文件末尾结束循环
			break
		}
		// 遍历str,进行统计
		for _, v := range str {
			// fmt.Printf("统计每一行的字符数量:%v\n", v)
			switch {
			case v >= 'a' && v <= 'z':
				fallthrough
			case v >= 'A' && v <= 'Z':
				count.ChCount++
			case v == ' ' || v == '\t':
				count.SpaceCount++
			case v >= '0' && v <= '9':
				count.NumCount++
			default:
				count.OtherCount++
			}
		}
	}
	fmt.Printf("字符的个数为:%v,数字个数为:%v,空格字符为:%v,其他字符个数为:%v",
		count.ChCount, count.NumCount, count.SpaceCount, count.OtherCount)
}
  1. 结构体中字段的定义是大写才能在其他包被访问到,即使结构体变量是大写,但是里面的属性值不是大写,是不可以被其他包访问到的。
type Test1 struct {
	Name string
	Age  int
}

type Test2 struct {
	name string
	Age  int
}
  1. map反序列化时不需要进行make,因为unmarshal函数里面通过类型断言,进行了make操作。
  2. 反序列化时,如果结构体后面使用了json那么,在反序列化时,也要用json对应的字段属性。
type Monster struct {
	Name     string  `json:"monster_name"`
	Age      int     `json:"monster_age"`
	Birthday string  `json:"monster_birthday"`
	Sal      float64 `json:"monster_sal"`
	Skill    string  `json:"monster_skill"`
}

func UnserializeLearn() {
	// str := "{\"Name\":\"陈文小超\",\"Age\":28,\"Birthday\":\"1998-08-15\",\"Sal\":10,\"Skill\":\"运动\"}"
	str := "{\"monster_name\":\"陈文小超\",\"monster_age\":28,\"monster_birthday\":\"1998-08-15\",\"monster_sal\":10,\"monster_skill\":\"运动\"}"
	var monster Monster

	err := json.Unmarshal([]byte(str), &monster)

	if err != nil {
		fmt.Printf("unmarshaler err=%v\n", err)
	}

	fmt.Printf("反序列化后:%v\n", monster)
}

# 结果
反序列化后:{陈文小超 28 1998-08-15 10 运动}
  1. 序列化和反序列化前后的数据类型(字段)和结构要保持一致。
  2. 单元测试
  • 注意事项:
    • 测试用例文件名必须是以_test.go结尾。比如:cal_test;
    • 测试用例函数必须以Test开头,一般来说就是Test+被测函数的函数名,比如:TestGetSum;
    • TestGetSum(t *testing.T)的形参类型必须是 *testing.T【见手册】;
    • 一个测试用例文件中,可以有多个测试用例函数,比如TestGetSum、TestGetSub;
    • 运行测试用例指令:
      • (1)cmd > go test [如果运行正确,无日志,错误时,会输出日志];
      • (2)cmd > go test -v [运行正确或是错误,均输出日志]。
    • 当出现错误时,可以使用t.Fatalf来格式化输出错误信息,并退出程序;
    • t.Logf方法可以输出相应的日志;
    • 测试用例函数,并没有放在main函数中,同样可以执行,这就是测试用例的方便之处;
    • PASS表示测试用例运行成功,FALL表示测试用例运行失败。
  1. 测试指定单元测试文件go test -v 依赖文件 -test.run 方法名,如果依赖文件过多可以go test -v ./ -test.run 方法名参考链接
  2. goroutine
# 进程和线程
1. 进程就是程序在操作系统中的一次执行过程,是系统进行资源分配和调度的基本单位。
2. 线程是进程的一个执行实例,是程序执行的最小单元,它是比进程更小的能独立运行的基本单位。
3. 一个进程可以创建和销毁多个线程,同一个程序至少有一个进程,一个进程至少有一个线程。

# 并发和并行
1. 多线程程序在单核上运行,就是并发。
2. 多线程程序在多核上运行,就是并行。

并发:因为是在一个CPU上,比如有1-个线程,每个线程执行10毫秒(进行轮训操作),从人的角度看,好像这10个线程都在运行,但是从微观角度看,在某一个时间点看,其实只有一个线程在执行,这就是并发。
并行:因为是在多个CPU上(比如有10个CPU),比如有10个线程,每个线程执行10毫秒(各自在不同的CPU上执行),从人的角度看,这10个线程都在运行,但是从微观上看,在某一个时间点看,也同时有10个线程在执行,这就是并行。

# Go携程和Go主线程
1. Go主线程(有程序员直接称为线程/也可以理解成进程):一个Go线程上,可以起多个协程,协程是轻量级的线程(编译器做优化)。

2. Go协程的特点:
有独立的栈空间
共享程序堆空间
调度由用户控制
协程是轻量级的线程。
  1. 没有进程,程序跑不起来。
  2. 如果主线程退出了,则协程即使还没有执行完毕,也会退出;当然协程也可以在主线程没有退出前,就自己结束了。
  3. 主线程是一个物理线程,直接作用在CPU上的。是重量级的,非常耗费CPU资源;协程从主线程开启的,是轻量级的线程,是逻辑态,对资源消耗相对小;golang的协程机制是重要的特点,可以轻松的开启上万个协程,其他编程语言的并发机制一般是基于线程的,开启过多的线程,资源耗费大。
  4. go1.8后,默认程序运行在多个核上,无需设置,1.8之前,则需要设置。
  5. fatal error: concurrent map writes:多个协程同时向一个内存空间写入数据,并发操作。资源争夺导致,解决方案:加互斥锁。
  6. 利用goroutine使用全局变量加锁同步解决协程通讯,但不是完美:【为什么用channel】
  • 主线程在等待所有goroutine全部完成的时间很难确定。
  • 如果使主线程休眠时间过长,会加长等待时间,如果等待时间过短,可能还有goroutine处于工作状态,这时也会随主线程的退出而销毁。
  • 通过全局变量加锁同步来实现通讯,也不利于用多个协程对全局变量的读写操作。
  1. channel是引用类型,必须初始化才能写入数据,即make后才能使用,只能存放指定的数据类型。
  • channel本质就是一个数据结构-队列。
  • 数据是先进先出【FIFO】。
  • 线程安全,多goroutine访问时,不需要加锁,就是说channel本身就是线程安全的。
  • channel有类型的,一个string的channel只能存放string类型数据。
  • 写入管道的数据,不能超过管道的容量。
  • 读取管道的数据,数据读取完毕之后,继续读取,就会报错。
  • channel关闭之后,只能读取不能写入。
  • 在遍历时,如果channel没有关闭,则会出现deadlock的错误。
  • 在遍历时,如果channel已经关闭,则会出现正常遍历数据,遍历完成后,就会退出遍历。
  1. 如果只是向管道写入数据,而没有读取数据,就会出现阻塞而deadlock,比如:管道初始化容量是10,但是代码写入的数据量是50个,这样就会出现问题。即如果编译器发现一个管道只有写,没有读,则该管道会阻塞,写管道和读管道的频率不一致无所谓。
  2. 在默认情况下,管道是双向的,也可以声明为只读,也可以声明为只写。
  3. select可以解决从管道取数据的阻塞问题。
func ChannelSelect() {
	intChan := make(chan int, 10)
	for i := 0; i < 10; i++ {
		intChan <- i
	}

	strChan := make(chan string, 5)
	for i := 0; i < 5; i++ {
		strChan <- "hello" + fmt.Sprintf("%d", i)
	}

	// 传统的方法循环遍历管道时,如果不关闭管道,导致deadlock
	// 使用select进行解决
	for {
		select {
		case v := <-intChan: // 如果intChan一直没有关闭,不会一直阻塞而deadlock
			fmt.Printf("从intChan读取数据%d\n", v)
			time.Sleep(time.Second)
		case v := <-strChan:
			fmt.Printf("strChan读取数据%s\n", v)
			time.Sleep(time.Second)
		default:
			fmt.Printf("找不到管道了,不管了,直接结束!\n")
			return
		}
	}
}
  1. 开启多个协程时,如果其中一个协程发生panic,会阻断其他协程正常执行,为了不妨碍其他协程继续执行,那么可以使用defer recover进行处理。
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

陈文小超_自律

努力自己,幸福他人

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值