go语言进阶知识点(待补充)

文件操作

1.文件是数据源的一种,最主要的作用是保存数据,在程序中是以流的形式操作的
2.流是数据源(文件)和程序(内存)之间经历的路径
3.os.File封装所有文件相关操作,File是一个结构体
4.打开一个文件进行读操作:
file , err :=os.Open(name string)括号里为文件路径,盘符小写
5.在打开一个文件并进行操作之后,必须关闭文件
err = file.Close() (可以用defer压入栈中,函数结束时自动调用)
5.判断文件或文件夹是否存在的方法为使用os.Stat()函数返回的错误值进行判断
如果返回nil,说明文件或文件夹存在
如果返回的错误类型使用os.IsNotExist()判断为true,说明文件或文件夹不存在
如果返回的错误为其他类型,则不确定是否存在
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,err
}

读文件操作

1.多次读取文件内容(带缓存)
reader := bufio.NewReader(file)
for{
str,err := reader.ReadString(‘\n’)//读取一个换行符就结束
if err == io.EOF{//表示文件的末尾
break
}
fmt.Print(str)
2.一次性将整个文件读入内存中,适用于文件不大的情况
content, err := ioutil.ReadFile(file)//因为open和close都封装在此函数中,所以不需要额外写
fmt.Printf(%v,content)

写文件操作

1.读取文件的另一种形式
func OpenFile(name string(路径名),flag int(文件打开模式具有多种,可以组合,中间用 | 隔开),perm FileMode(文件模式与权限,在windows中没用,主要用在linux中,是一串数字)(file文件变量,err错误)
文件打开模式具体种类
const (
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_CREATE配合使用,文件必须不存在
O_SYNC int = syscall.O_SYNC // 打开文件用于同步I/O
O_TRUNC int = syscall.O_TRUNC // 如果可能,打开时清空文件
)
2.对于换行符,有些识别的是\r ,有些识别的是\n 为了保险可以两者都写
3.格式为
file, err := os.OpenFile(路径名,文件打开模式,文件权限)
错误判断语句
//及时关闭file变量
defer file.Close()
//准备要写入的内容
str := “”
//使用带缓存的*Writer写入
writer := bufio.NewWriter(file)
writer.WriterString(str)
//因为writer是带缓存的,因此在调用WriterString方法时,内容是先写入缓存的,所以需要调用
//Flush方法将缓冲的数据真正写入到文件中,否则文件中会没有数据
writer.Flush()
4.一次性将整个文件写入文件中,适用于文件不大的情况
err := ioutil.WriteFile(file(要写入的文件路径),data(要写入的内容),perm(文件权限))//因为open和close都封装在此函数中,所以不需要额外写

命令行参数

1.希望能够获取到命令行输入的各种参数,用os.Args进行处理,它是一个string的切片,用来储存所有的命令行参数
for i ,v := range os.Args{
fmt.Printf(“args[%v]=\n”,i,v)
}
2.用flag包来解析命令行参数,不受参数顺序影响
var user string
var pwd string
// &uesr 就是接收用户命令行中输入的-u 后面的参数值
// u,就是-u指定参数
// “” ,默认值
// ”用户名,默认为空“说明
flag.StringVar(&user,“u”,“”,“用户名,默认为空”)
flag.StringVar(&pwd,“pwd”,“”,“密码,默认为空”)
//这里有一个非常重要的操作,转换,必须调用该方法
flag.Parse

json

1.json是一种轻量级的数据交换格式,易于人阅读和编写,同时也易于机器解析和生成,在ecdoding/json包下
2.在js语言中,一切都是对象,任何的数据类型都可以通过json来表示
3.json依靠键值对来保存数据,键值对组合中的键名写在前面并用""包裹,使用:分割,然后紧接着值。键值对之间用,隔开
4.序列化是指将数据类型序列化成对应的json字符串的操作要用到json包下的Marshal函数
序列化完成的对象, err := json.Marshal(要序列化的对象,如果不是引用类型要加&)
5.对于结构体的序列化,如果我们希望序列化后的key的名字重新制定,那么可以给结构体指定一个tag标签,利用反射做到的
Name string ‘json:“name”’
序列化后对应的Name会变成name
6.反序列化是指将json字符串反序列化成对应的数据类型的操作需要用到json包下的Unmarshal函数
err := json.Unmarshal(要反序列化的对象,反序列化成功后的对象(如果不是引用类型要加&))
7.反序列化map时,不需要make,会自动make
8.在反序列化一个json字符串时,要确保反序列化后的数据类型和原来序列化前的数据类型一致(对结构体来说,主要指的是字段完全一样)
9.如果json字符串是通过程序获得的,则不需要再对"转义处理

单元测试

1.在我们工作中,会遇到需要确认结果是否正确的情况 这时候就需要进行正确性测试
2.传统方法是在main函数中调用addUpper函数,看看实际输出的结果是否和预期的结果一致,但是不方便,需要在main函数中去调用,这样就需要去修改main函数,如果项目正在运行,就可能导致错误。同时也不利于管理,因为当我们测试多个函数或者多个模块时,都需要写在main函数,不利于我们管理和清晰我们思路。
3.利用测试框架testing和go test命令来实现单元测试和性能测试 可以基于这个框架写针对相应函数的测试用例,也可以基于该框架写相应的压力测试用例,可以解决如下问题:
确保每个函数是可运行的,并且运行结果是正确的
确保写出来的代码性能是好的
单元测试能及时发现程序设计或实现的逻辑错误,便于问题的定位解决
性能测试能发现程序在设计上的一些问题,让程序能够在高并发的情况下还能保持稳定
4.基本流程为
import “testing”
func TestXxx(Xxx为任意数字字母字符串,但开头不能是小写字母)(t *testing.T){
创建实例并调用要测试的函数
if 结果不等于预期值{
t.Fatalf(“要测试的函数执行错误,预期值=%v 实际值= %v\n”, 预期值,实例)
}
如果正确,输出日志
t.Logf(“要测试的函数执行正确”)
5.testing框架能将 xxx_test.go的文件引入main函数,之后就会调用此文件中以Test为开头的测试函数
6.测试用例文件名必须以_test.go结尾,测试用例函数必须以Test开头,测试用例函数形参类型必须是*testing.T,一个测试用例文件中,可以有多个测试用例函数
7.运行测试用例指令
cmd>go test [如果运行正确无日志,错误时会输出日志]
cmd>go test -v [无论运行正确或是错误,都输出日志]
8.PASS表示测试用例运行成功,FAIL表示测试用例运行失败
9.测试单个文件一定要带上被测试的源文件 go test -v cal_test.go cal.go
10.测试单个方法 go test -v -test.run TestAddUpper

goroutine协程和channel管道

进程和线程

1.进程就是程序在操作系统中的一次执行过程,是系统进行资源分配和调度的基本单位
2.线程是进程的一个执行实例,是程序执行的最小单元,它是比进程更小的能独立运行的基本单位
3.一个进程可以创建和销毁多个线程,同一个进程中的多个线程可以并发执行
4.一个程序至少有一个进程,一个进程至少有一个线程

并发和并行

1.多线程程序在单核上运行,就是并发。在多核上运行,就是并行
2.并发因为是在一个cpu上,从人的角度看多个线程都在运行,但是从微观上看,其实只有一个线程在执行
3.并行因为是在多个cpu上运行,从人的角度看多个线程在运行,微观上同样是这样

协程和主线程

1.主线程是一个物理线程,直接作用在cpu上,是重量级的,非常耗费cpu资源。
2.协程从主线程开启,是轻量级的线程,是逻辑态,对资源消耗相对小
3.go主线程上,可以起多个协程。线程的特点有:有独立的栈空间,共享程序堆空间,调度由用户控制,协程是轻量级的线程。
4.golang的协程机制是非常重要的特点,可以轻松的开启上万个协程。其他编程语言的开发机制是一般基于线程的,过多开启线程资源耗费大,这里就突显golang在并发上的优势了
5.在一个代码块前加go 即可开启一个协程
6.如果主线程退出了,则协程即使还没有执行完毕,也会退出。协程可以在主线没有退出前,自己就结束了
7.获取自己可用的cpu
cpuNum := runtime.NumCPU()
8.设置使用的cpu个数
runtime.GOMAXPROCS(数量,最多为cpuNum)
go1.8之后,默认让程序运行在多个核上
9.recover 是一个内建函数,它用于捕获和处理协程(goroutine)中的恐慌。当一个协程执行panic时,它会立即停止当前函数的执行,并运行所有defer的函数。如果在延迟的函数中调用了recover,则可以捕获到panic的值,并且可以阻止恐慌继续传递,使得程序可以从恐慌中恢复并继续执行。
defer func() {
if err := recover(); err!= nil(
错误输出语句
}
}()

解决不同goroutine之间如何通讯

全局变量枷锁同步

//声明一个全局的互斥锁, lock是一个全局的互斥锁 sync是包,Mutex是互斥
lock := sync.Mutex
//在要操作的区域之前加锁,之后解锁
lock.Lock()
lock.Unlock()

管道

1)为什么需要channel?前面使用全局加锁同步解决协程的通讯并不完美,主线程在等待所有协程全部完成的时间很难确定,也不利于多个协程对全局变量的读写操作
2)channle本质就是一个队列的数据结构
3)数据是先进先出的
4)线程安全,多协程访问时,不需要加锁
5)管道有类型时,一个string的管道只能放string类型数据,管道是引用类型
6)管道声明 var 变量名 chan 数据类型
变量名 = make(chan 数据类型 , 容量)此变量指向的是一个地址,是管道的地址,此变量本身也有个地址。管道的容量不可改变,容量满了就不能再放入了。
7)管道必须初始化才能写入数据,即make后才能使用
8)向管道写入数据
管道变量< - 要写入的数据
9)从管道中读取数据
接收变量 = < - 管道变量
10)在没有使用协程的情况下,如果管道数据取完了,再取会报dead lock
11)管道关闭close(管道变量) 管道遍历for-range 只返回一个值。
12)管道可以声明为只读或只写性质
只读
var chan1 <- chan int
只写
var chan2 chan<- int
13)使用select可以解决从管道取数据的阻塞问题。传统的方法在遍历管道时,如果不关闭会阻塞而导致deadlock。但是在实际开发中,可能我们不好确定何时该关闭管道,可以使用select方式解决
for{
select{
//如果chan1没有关闭也不会阻塞而deadlock,而是会自动到下一个case匹配
case v:= <-chan1:
执行语句1
case v:= <- chan2
执行语句2
default :
都取不到,执行语句3
return
}
}
14)判断管道里是否还有元素
num,ok := <- intChan
if !ok {
没有元素了,break
}

反射

1.反射可以在运行时动态获取变量的各种信息,比如变量的类型(type),类别(kind)如果是结构体变量,还可以获取到结构体本身的信息(字段,方法)
2.通过反射,可以修改变量的值,可以调用关联的方法
3.反射需要reflect包
4.reflect.TypeOf(变量名),获取变量的类型,返回reflect.Type类型
5.reflect.ValueOf(变量),获取变量的值,返回reflect.Value类型。reflect.Value是一个结构体类型。
6.变量,interface{}和reflect.Value是可以相互转换的
func test(b interface{}) {
//将interface{}转成reflect.Value
rVal := reflect.ValueOf(b)
//将reflect.Value转成interface{}
iVal := rVal.Interface()
//将interface{}转成变量类型,使用类型断言,虽然在上一步将reflect.Value转为interface{}的时候类型已经变成变量类型,但编译器无法识别,因此需要类型断言转换
v := iVal.(变量类型)
7. kd := rVal.Kind ,获取变量的类别,返回的是一个常量,零值代表非法分类
8.Type是类型,Kind是类别,两者可能相同也可以不同
var num int =10 两者就是相同的,都是int
var stu Student Type就是 包名.Student ,Kind是struct
9.使用反射的方式来获取变量的值并返回对应的类型,要求数据类型匹配,比如x是int,那么就应该使用reflect.Value(x).Int(),而不能使用其他的,否则报panic
10.通过反射来修改变量,注意当使用SetXxx方法来设置需要通过对应的指针类型来完成,这样才能改变传入的变量的值,需要使用到reflect.Value.Elem()方法。
var num int =10
rVal := reflect.ValueOf(&num)
rVal.Elem()SetInt(修改的值)

反射与结构体

1.获取结构体的方法,顺序按照方法名第一个字母ASCII码顺序进行排列
//v类型为reflect.Value,i为反射的v持有的结构体的第i个方法
func (v Value) Method(i int) Value
2.调用结构体的方法
//v类型为reflect.Value 后两者类型为 []reflect.Value
func (v Value) Call(in []Value) []Value
3.获取结构体字段数量
num := val.NumField()
4.返回第i个字段
//val为reflect.value类型,返回的字段也是balue类型
val.Field(i)
5.获取struct标签,主要需要通过reflect.Type来获取tag的值。
//typ.Field(i)返回的是StructField的结构体其中一个字段是Tag,而Tag字段对应的值有一个方法为Get,会返回标签字符串中key对应的值
tagVal := typ.Field(i).Tag.Get(“json”)
6.获取到该结构体有多少个方法
numOfMethod := val.NumMethod()
7.调用该结构体的第i个方法,并传入参数a(a的类型为[]reflect.Value,这种切片可以通过定义并往里边加reflect.Value的数据类型的数据来获得)
val.Method(i).Call(a)

网络编程

1.网络编程分为两种:
1)TCP socket编程,是网络编程的主流,名称来由是底层是基于TCP/ip协议的,比如qq
2)b/s结构的http编程,使用浏览器去访问服务器时所使用的,底层依旧是用tcp socket实现,比如京东商城(属于go web开发范畴)
2.ip地址 每个internet上的主机和路由器都有一个ip地址,它包括网络号和主机号,有ipv4(32位)和ipv6(128味)两种可以通过ipconfig查看
3.端口port 这里所指的端口不是物理意义上的端口,而是特指TCP/IP协议中的端口,是逻辑意义上的端口。一个ip地址的端口可以有65536(即256*256)个。端口通过端口号来标记,范围是0-65535。4.只要是做服务程序,都必须监听一个端口,该端口就是其他程序和该服务通讯的通道
5.一旦一个端口被某个程序监听(占用),其他程序就不能在该端口监听
6.端口的分类
1)0是保留端口
2)1-1024是固定端口,又叫有名端口,即被某些程序固定使用,一般程序员不使用。22:SSH远程登陆协议,23:telnet使用,21:ftp使用,25:smtp服务使用,80:lis使用,7:echo服务
3)1025-65535是动态端口,程序员可以使用
7.端口的注意事项
1)在计算机(尤其是做服务器)要尽可能地少开端口
2)使用netstat-an可以查看本机有哪些端口在监听
3)使用netstat-anb来查看监听端口的pid,再结合任务管理器关闭不安全的端口

服务器端的处理流程

1.监听端口
//tcp表示使用的网络协议是tcp,0.0.0.0:8888 表示在本地监听8888端口,listen是一个实现了Listen接口的变量
listen,err := net.Listen(“tcp”,“0.0.0.0:8888”)
错误判断语句
defer listen.Close()//延时关闭listen
2.接收客户端的tcp链接,建立客户端和服务器端的链接
//循环等待客户端来链接我
for{
conn,err :=listen.Accept()
if err != nil {
错误处理
} else {
fmt.Printf(“Accept() suc con =%v 客户端ip = %v\n”,conn,conn.RemoteAddr())
}
3.创建协程,处理该链接的请求(通常客户端会通过链接发送请求包)
go process(conn)
)
4.循环接收客户端发送的数据
func process(conn net.Conn){
defer conn.Close()//关闭conn
for{
buf := make([]byte,1024)//因为read方法接收的是一个切片,所以需要新建一个切片
n,err := conn.Read(buf)//等待客户端通过conn发送消息,如果客户端没有write[发送],那么协程就阻塞在这里
fmt.Printf(“服务器在等待客户端%s发送消息\n”, conn.RemoteAddr().String())
if err != nil{
fmt.Printlen(“服务器的Read err =”,err)
return
}
//显示客户端发送的内容到服务器的终端,长度为从0到n,不会把后续的空字节也复制过去
fmt.Print(string(buf[:n]))
}
}

客户端的处理流程

1.建立与服务端的链接
conn,err :=net.Dial(“tcp”,“要连的ip.端口”)
错误判断语句
2.发送请求数据,接收服务器端返回的结果数据
//客户端可以发送单行数据,然后退出。
reader := bufio.NewReader(os.Stdin)//os.Stdin 代表标准输入[终端]
line,err := reader.ReadString(‘\n’)//从终端读取一行用户输入,并准备发给服务器
错误判断语句
_,err := conn.Write([]byte(line))//再将line 发送给服务器
错误判断语句
3.关闭链接

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值