尚硅谷Go学习-4
文件
输入流 读文件 从数据源到程序
输出流 写文件 从程序的内存到文件数据源
读文件
os.File 对文件相关的操作
File 代表一个打开的文件对象 文件句柄。
File.open
close
bufio.NewReader
reader.ReadString 带缓冲区的读取 适合文件大的情况。
ioutil.ReadFile :适合于文件不太大的情况一次性将整个文件存入到内存。
写文件:
文件名 +打开方式+权限控制。
打开方式 只读 只写 读写 追加 权限控制:Linux下有效。
writer.WriteString Flush 刷到磁盘。否则文件会丢失数据,
“\r\n” 表示换行 有些编辑器是"\r" 有一些是"\n"
打开方式可以组合
写文件的四种方式
- 打开已经存在的文件 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协程的特点
- 有独立的栈空间
- 共享程序堆空间
- 调度由用户控制
- 协程是轻量级线程
入门:
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"
}