Go 语言学习-4

尚硅谷Go学习-4

文件

输入流 读文件 从数据源到程序
输出流 写文件 从程序的内存到文件数据源

读文件

os.File 对文件相关的操作
File 代表一个打开的文件对象 文件句柄。
File.open
close
bufio.NewReader
reader.ReadString 带缓冲区的读取 适合文件大的情况。
ioutil.ReadFile :适合于文件不太大的情况一次性将整个文件存入到内存。

写文件:

文件名 +打开方式+权限控制。
打开方式 只读 只写 读写 追加 权限控制:Linux下有效。
writer.WriteString Flush 刷到磁盘。否则文件会丢失数据,
“\r\n” 表示换行 有些编辑器是"\r" 有一些是"\n"
打开方式可以组合

写文件的四种方式

  1. 打开已经存在的文件 os.o_RDWR| os.o_APPEND

os.Stat 判断文件或文件夹是否存在
Copy 函数copy 文件
io.Copy(writer,reader) 把文件句柄及时关闭。
为了兼容中文
str=[]rune(str)

命令行参数
os.Args 端口号 等 是一个切片。
flag 包解析命令行参数。
-p -u 这种形式的解析。
IntVar StringVar
flag.StringVar(&user,“u”,"",“用户名,默认值为空”)
parse 解析。

Json 格式

任何一种形式都可以转为JSON 格式 字符串 数字 对象 数组 Map 结构体等。
结构体 Map 和切片的序列化 基本数据类型序列化 意义不大。

import (
	"encoding/json"
	"fmt"
)

/**
  JSON 的使用 转为JSON
*/
func main() {
	testStruct()
	testMap()
}

type Customer struct {
	Id   int
	Name string
}

func testStruct() {
	m := Customer{
		Id:   10,
		Name: "zhangqiges",
	}
	data, err := json.Marshal(&m)
	if err != nil {
		fmt.Printf("序列好错误err=%v\n", err)
	}
	fmt.Printf("序列化后%v\n", string(data))

}

func testMap() {
	var a map[string]interface{}
	a = make(map[string]interface{}, 10)
	a["name"] = "honghaier"
	a["age"] = 20
	a["skill"] = "bushi"
	a["address"] = "huoyundong"

	data, err := json.Marshal(a)
	if err != nil {
		fmt.Printf("序列好错误err=%v\n", err)
	}
	fmt.Printf("序列化后%v\n", string(data))
}

负数和正数异或 需要转成 补码 后再进行计算 ,最后再转成原码。

序列化struct 时 tag 使用

序列化后的Json 串按照指定的key来显示
在结构体定义时 修改。 利用的是反射机制。

type Customer struct {
	Id   int `json:"id"`
	Name string  `json:"name"`
}

反序列化

json 串 反序列化成对应的数据类型 结构体 Map 切片等

err = json.Unmarshal(data, &lf)

反序列化底层会自动为 map分配内存。无需手动make。 Unmarshal 中有封装 。 slice 也同理 无需make
JSON 字符串是通过程序获取的 无需通过转义处理。直接写 的原生的字串需要转义处理。

单元测试

确保可运行 ,运行结果是正确的
确保性能是好的
cal.go

package main

func addUpper2(n int) int {
	res := 0
	for i := 0; i <= n; i++ {
		res += i
	}
	return res
}

cal_test.go

package main

import (
	"fmt"
	"testing"
)

func TestAddUpper2(t *testing.T) {
	res := addUpper2(10)
	if res != 55 {
		fmt.Println("addUpper执行错误")
		t.Fatalf("addUpper执行结果%v", res)
	}
	fmt.Println("正确")

}

go test -v

单元测试的细节说明

1. 测试用例文件必须以_test.go结尾 比如cal_test.go cal 不是固定的
2. 测试用例函数必须以Test开头 一般来说就是Test+被测函数名 比如TestAddUpper
3. TestAddUpper(t *testing.T)的形参类型必须是testing.T 查看手册
4. 一个测试用例文件中 可以有多个测试用例函数 比如TestAddUpper 
5. 运行测试用例指令
    go test 如果运行正确无日志 错误时 会输出日志
    go test -v  运行正确或是错误 都会输出日志
6.  当出现错误时 可以用 t.Fatalf 来格式化 输出错误信息 并退出程序
7. t.Logf 方法可以输出相应的日志
8. 测试用例函数没有放在main方法里也执行了这就是测试用例的方便之处
9. PASS表示测试用例成功运行 FAIL失败
10. 测试单个用例 一定要带上被测试源文件。go test -v cal_test.go cal.go
11. 测试单个方法。
     go  test -v -test.run TestAddUpper 

单元测试综合案例

GoRoutine 引入 和channel 协程 和管道。

goroutine 看哪些是素数 1-20000
进程和线程/
进程: 操作系统执行的基本单位
线程: 独立执行的基本单元 进程的执行实例。
并发和并行:
并发:多线程程序在单核上运行 一个时间点只有一个在运行 。
并行:多线程程序在多核上运行 同一个时间多任务同时执行。

Go协程和Go 主线程

在Go中函数可以作为一个形参 进行传递。

Go协程的特点

  1. 有独立的栈空间
  2. 共享程序堆空间
  3. 调度由用户控制
  4. 协程是轻量级线程
    入门:
func main() {
	go test()
	for i := 0; i < 10; i++ {
		fmt.Println("main hello" + strconv.Itoa(i))
		time.Sleep(time.Second)
	}

}
func test() {
	for i := 0; i < 10; i++ {
		fmt.Println("test hello" + strconv.Itoa(i))
		time.Sleep(time.Second)

	}
}

穿插执行。主线程执行完毕后 协程即使还没有执行完毕 也会退出。

MPG 模式介绍
M 操作系统的主线程 物理线程
P 协程需要的上下文
G 协程

Go 设置运行CPU数目
go1.8之后 默认多核 1.8之前 设置一下 runtime.NumCPU()
runtime.COMAXPROCS(num) 设置可同时执行的最大CPU

channel 管道
编译时 知道是否存在资源竞争的问题:编译时加上-race 即可。

全局互斥锁解决资源竞争。
sync /channel
从程序设计上 可以知道10s可以执行完所有 协程 但是主线程不知到 因此底层仍会出现资源争夺。

func main() {
	for i := 0; i < 20; i++ {
		go jiecheng(i)
	}
	time.Sleep(time.Second * 5)
	lock.Lock()
	for k, v := range myMap {
		fmt.Println(k, v)
	}
	lock.Unlock()
}

var (
	myMap = make(map[int]int, 10)
	//全局变量的互斥锁
	lock sync.Mutex
)

func jiecheng(n int) {
	res := 1
	for i := 1; i <= n; i++ {
		res *= i
	}
	lock.Lock()
	myMap[n] = res
	lock.Unlock()
}
管道解决 线程资源竞争问题
时间设置 不好控制 长短 都会有问题     不利于多线程对全局变量的读写 。
这时会用channel 

channel 的本质是队列 先进先出 本身就是线程安全的
一个string类型的channel 只能存放string类型数据。
len cap 管道满了之后继续写入会报错 取出一个后还可以继续写入。
取完了之后再取 就会报告 异常 。死锁。

func useChannel() {
	var intChan chan int
	intChan = make(chan int, 3)
	//allChan:=make(chan interface{},2)
	//fmt.Println(intChan, &intChan)
	num := 211
	intChan <- 10
	intChan <- num
	intChan <- 50
	//管道不可以扩容 加多了报错。 写入数据时 不能超过 容量。
	// 满了之后去一个 可以再继续加
	fmt.Println(intChan, &intChan, len(intChan), cap(intChan))
	var num2 int
	num2 = <-intChan
	fmt.Println(num2)

}

管道的细节:
管道可以存放结构体 Map 和基本数据类型。
管道放任意数据类型。定义为空接口。 想要找到指定的结构体的某个字段需要结合类型断言。
想要管道中的第三个元素需要 推出前两个
channel 的关闭:关闭之后 只能读 不能写。
遍历循环:
for range 不能用 for i
遍历时 如果channel 没有关闭 则会出现deadlock的错误
如果已经关闭 则会正常遍历数据 遍历完后退出 遍历
练习

func main() {
	//useChannel()
	intChan := make(chan int, 50)
	exitChan := make(chan bool, 1)
	go writeData(intChan)
	go readData(intChan, exitChan)
	for{
		_,ok:=<-exitChan
		if!ok{
			break
		}
	}
}
func writeData(intChan chan int) {
	for i := 0; i < 50; i++ {
		intChan <- i
		fmt.Println("writeData",i)
	}
	close(intChan)
}
func readData(intChan chan int, exitChan chan bool) {
	for {
		v, ok := <-intChan
		if !ok {
			break
		}
		fmt.Printf("读到数据%v", v)
	}
	exitChan <- true
	close(exitChan)
}

管道的阻塞机制
如果写得很快 读的很慢 不会死锁会异步完成 读写频率不一致 不会影响
但是不读就会死锁 。

协程统计素数

func main() {
	intChan := make(chan int, 1000)
	primeChan := make(chan int, 1000)
	exitChan := make(chan bool, 4)
	go writeNumber(intChan)
	for i := 0; i < 4; i++ {
		go readNumber(intChan, primeChan, exitChan)
	}

	//主线程进行处理// 取出四个
	go func() {
		for i := 0; i < 4; i++ {
			<-exitChan
		}
		close(primeChan)
	}()
	//遍历primechan
	for {
		res, ok := <-primeChan
		if !ok {
			break
		}
		fmt.Printf("素数有%v\n", res)
	}
	fmt.Print("主线程退出")
}
func writeNumber(intChan chan int) {
	for i := 0; i < 20000; i++ {
		intChan <- i
	}
	close(intChan)
}
func readNumber(intChan chan int, primeChan chan int, exitChan chan bool) {

	var flag bool
	for {
		num, ok := <-intChan
		if !ok {
			break
		}
		flag = true
		for i := 2; i <= num-1; i++ {
			if num%i == 0 {
				flag = false
				break
			}
		}
		if flag {
			primeChan <- num
		}

	}
	fmt.Printf("一个协程完成工作了")
	exitChan <- true
}

channel的使用细节和注意事项
1.channel 可以声明为只读 或者只写性质
默认双向 可读可写
只写 chan<- int
只读 <-chan int

select 可以解决从管道去数据的阻塞问题。
select +case
goroutine 使用recover 解决协程中出现panic 导致程序崩溃的问题。
defer + recover 来解决
尽量不要用label 标签 break label

func main() {
	go sayHello()
	go test()
	for i := 0; i < 10; i++ {
		fmt.Println("main ok")
		time.Sleep(time.Second)
	}
}
func sayHello() {
	for i := 0; i < 10; i++ {
		time.Sleep(time.Second)
		fmt.Println("hello")
	}

}
func test() {
	defer func() {
		if err := recover(); err != nil {
			fmt.Println("test 发生错误", err)
		}
	}()
	var myMap map[int]string
	myMap[0] = "golang"
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值