createdtime 20211105
updatedtime 20211105
author venki.chen
- 流:数据在数据源(文件)和程序(内存)之间经历的路径,输入流:数据从数据源(文件)到程序(内存)的路径,输出流:数据从程序(内存)到数据源(文件)的路径。
- 文件操作实例:
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("文件读取结束")
}
- 判断文件或者文件夹是否存在
// 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
}
- 文件迁移
// 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)
}
}
- 文件拷贝
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)
}
- 统计不同数据类型的个数
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)
}
- 结构体中字段的定义是大写才能在其他包被访问到,即使结构体变量是大写,但是里面的属性值不是大写,是不可以被其他包访问到的。
type Test1 struct {
Name string
Age int
}
type Test2 struct {
name string
Age int
}
- map反序列化时不需要进行make,因为unmarshal函数里面通过类型断言,进行了make操作。
- 反序列化时,如果结构体后面使用了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 运动}
- 序列化和反序列化前后的数据类型(字段)和结构要保持一致。
- 单元测试
- 注意事项:
- 测试用例文件名必须是以_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表示测试用例运行失败。
- 测试指定单元测试文件
go test -v 依赖文件 -test.run 方法名
,如果依赖文件过多可以go test -v ./ -test.run 方法名
参考链接 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协程的特点:
有独立的栈空间
共享程序堆空间
调度由用户控制
协程是轻量级的线程。
- 没有进程,程序跑不起来。
- 如果主线程退出了,则协程即使还没有执行完毕,也会退出;当然协程也可以在主线程没有退出前,就自己结束了。
- 主线程是一个物理线程,直接作用在CPU上的。是重量级的,非常耗费CPU资源;协程从主线程开启的,是轻量级的线程,是逻辑态,对资源消耗相对小;golang的协程机制是重要的特点,可以轻松的开启上万个协程,其他编程语言的并发机制一般是基于线程的,开启过多的线程,资源耗费大。
- go1.8后,默认程序运行在多个核上,无需设置,1.8之前,则需要设置。
fatal error: concurrent map writes
:多个协程同时向一个内存空间写入数据,并发操作。资源争夺导致,解决方案:加互斥锁。- 利用
goroutine
使用全局变量加锁同步解决协程通讯,但不是完美:【为什么用channel】
- 主线程在等待所有goroutine全部完成的时间很难确定。
- 如果使主线程休眠时间过长,会加长等待时间,如果等待时间过短,可能还有goroutine处于工作状态,这时也会随主线程的退出而销毁。
- 通过全局变量加锁同步来实现通讯,也不利于用多个协程对全局变量的读写操作。
channel
是引用类型,必须初始化才能写入数据,即make后才能使用,只能存放指定的数据类型。
- channel本质就是一个数据结构-队列。
- 数据是先进先出【FIFO】。
- 线程安全,多goroutine访问时,不需要加锁,就是说channel本身就是线程安全的。
- channel有类型的,一个string的channel只能存放string类型数据。
- 写入管道的数据,不能超过管道的容量。
- 读取管道的数据,数据读取完毕之后,继续读取,就会报错。
- channel关闭之后,只能读取不能写入。
- 在遍历时,如果channel没有关闭,则会出现deadlock的错误。
- 在遍历时,如果channel已经关闭,则会出现正常遍历数据,遍历完成后,就会退出遍历。
- 如果只是向管道写入数据,而没有读取数据,就会出现阻塞而deadlock,比如:管道初始化容量是10,但是代码写入的数据量是50个,这样就会出现问题。即如果编译器发现一个管道只有写,没有读,则该管道会阻塞,写管道和读管道的频率不一致无所谓。
- 在默认情况下,管道是双向的,也可以声明为只读,也可以声明为只写。
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
}
}
}
- 开启多个协程时,如果其中一个协程发生panic,会阻断其他协程正常执行,为了不妨碍其他协程继续执行,那么可以使用
defer recover
进行处理。