- 字符串长度
ascii字符长度使用len(),每个中文长度为3
unicode字符串长度用utf8.RuneCountInString()函数 - 字符串遍历
ascii字符串遍历使用下标
unicode字符串遍历使用for range
- 字符串索引
strings.Index:正向搜索子字符串
strings.LastIndex:反向搜索子字符串
搜索的起始位置可以通过切片偏移制作 - 修改字符串内容
字符串不可变
修改字符串时,可以将字符串转换成[]byte进行修改
[]byte和string可以通过强制类型转换互换 - 连接字符串
获取bytes.buffer
var sb bytes.buffer
调用WriteString写入缓冲
sb.WriteString(str)
调用String获取字符串
sb.String()
GO语言学习
基本语法
变量
- 变量定义
- var a int = 5
- var a,b,c = 5,“ads”,true
- a:=5,注意:=用于定义变量,后面赋值时不能再次使用否则就是重复定义。函数外定义变量不能使用
- var (a=3 b=4 c=“aa”)
- 内建变量类型
- bool、string
- (u)int、 (u)int8、 (u)int16、 (u)int32、 (u)int64、uintptr
- byte、rune(字符类型32位)
- float32、float64、complex64、complex128
- 类型转换是强制的
- 常量的定义
常量的名称不要大写
const filename = “abc.txt”
const数值可作为各种类型使用 - 枚举类型
- 普通枚举类型
const(
a = 0
b = 1
c = 2
) - 自增值枚举类型
const(
a = iota(从0开始)
b
c
)
- 普通枚举类型
- 要点
- 变量类型写在变量名后面
- 编译器可推测变量类型
- 没有char,只有rune
- 原生支持复数类型
程序结构
- if
if的条件里可以赋值
if的条件里赋值的变量作用域就在这个if语句里if content, err:=ioutil.ReadFile(filename); err == nil { fmt.Println(string(contents)) } else { fmt.Println("cannot print file contents:", err) }
- switch
会自动break,除非fallthrough
switch后可以没有表达式,在case后面使用条件即可func score(score int) string { var g string switch { case score < 0 || score > 100: panic(fmt.Sprintf("error score:%d", score)) case score <= 60: g = "F" case score < 80: g="B" case score < 90: g="A" case score <= 100: g="S" } return g }
- for
for的条件里不需要括号
for的条件里可以省略初始条件,结束条件,递增表达式 - 要点回顾
- for、if后面的条件没有括号
- if条件里也可以定义变量
- 没有while
- switch不需要break,也可以不需要表达式可以在case后面跟条件
函数与指针
-
函数
- 函数可返回多个值
func div(a,b int) (int, int) { return a/b, a%b }
- 返回多个值时可以起名字(仅用于非常简单的函数),对于调用者而言没有区别
func div(a,b int) (q, r int) { q = a/b r = a%b return }
- 函数作为参数
func apply(op func(int, int) int, a, b int) int { fmt.Printf("Calling %s with %d, %d\n", runtime.FuncForPC(reflect.ValueOf(op).Pointer()).Name(), a, b) return op(a, b) }
- 可变参数列表
func sumArgs(values ...int) int { sum := 0 for i := range values { sum += values[i] } return sum }
- 要点回顾
- 返回值类型写在最后面
- 可返回多个值
- 函数作为参数
- 没有默认参数,可选参数
-
指针
- 参数传递
go语言只有值传递,也就是将变量的值复制一份后传递给方法参数。
值传递可以和指针配合进行传参
- 参数传递
容器
- 数组
- 定义
var array1 [5]int array2 := [3]int{1,2,3] arr3 := [...]int{1,2,3,4,} var arr4 [4][5]int
- 遍历
for i,v:= range numbers { }
- 数组是值类型,传递参数时会将数组整体拷贝一份
- [10]int 和 [20]int是不同类型
- 在go语言中一般不直接使用数组
- 切片
- 定义
arr := […]int{1,2,3,4}
s := arr[2:]
s[0]=10
slice本身没有数据,是对底层array的一个view
arr的值变为[1,2,10,4]
参数传递时[]int类型代表切片
通过slice可以替换数组中的值 - 实现
slice中有三个变量分为别ptr、len、cap,ptr指向数组中slice的第一个元素、len代表slice中元素的个数、cap代表从slice第一个元素到数组最后一个元素中间的元素个数。 - slice的扩展
可以向后扩展,不可以向前扩展
s[i]不可以超越len(s),向后扩展不可以超越底层数组cap(s) - 向slice添加元素
添加元素时如果超越cap,系统会重新分配更大的底层数组
由于值传递的关系,必须接收append的返回值
s=append(s, val) - slice相关操作
创建:make([]int, 10, 32)
copy: copy(des, src),将src中的元素copy到des中
删除某个元素:append(s2[:3], s2[4:]…)
- 定义
- map
- 定义
s := map[key类型]value类型 {
“name”: “ccc”
}
复合map:map[K1]map[K2]V2
s := make(map[K]V) - 遍历
for k,v := range m {
}
使用range遍历key或者遍历key、value对
不保证遍历顺序,如需顺序,需手动对key排序
使用len获取元素个数 - key不存在时,value为zero value
- 用value, ok := m[key]来判断key是否存在
- 删除一个key,delete(m, key)
- map的key
map使用哈希表,key必须可以比较相等
除了slice,map,function都可以作为key
struct类型不包含上述字段,也可以作为key,在编译时校验
- 定义
- 寻找最长不含有重复字符的子串
- 字符和字符串处理
- rune相当于go的char
- 使用range遍历pos、rune对
- 使用utf8.RuneCountInString获得字符数量
- 使用len获得字节长度
- 使用[]byte获得字节
- 其他字符串操作
Fields,split,join
Contains,Index
ToLower,ToUpper
Trim,TrimRight,TrimLeft
面向对象
- go语言仅支持封装,不支持继承和多态
- go语言没有class,只有struct
- 不论是地址还是结构本身,一律使用.来访问成员
- 使用自定义工厂函数
注意返回了局部变量的地址func createTreeNode(value int) *TreeNode { return &TreeNode{Value: value} } root.left.Right = createTreeNode(2)
- 定义结构体的方法
本质和普通函数没有区别,接收者也相当于参数
使用指针作为方法的接收者func (node TreeNode) print(){ fmt.Print(node.value) }
只有使用指针才能改变结构体的内容func (node *TreeNode) setValue(value int) { node.value = value }
nil指针也可以调用方法 - 写一个树的左根右遍历
- 值接收者 vs 指针接收者
要改变内容必须使用指针接收者
结构过大也考虑使用指针接收者
一致性:如果有指针接收者,最好都是指针接收者 - 封装
名字一般使用CamelCase
首字母大写:public
首字母小写:private - 包
每个目录一个包
main包包含可执行入口(main方法),只能有一个main方法
为结构定义的方法必须放在同一个包内,但可以是不同文件 - 如何扩展系统类型或者别人的类型
- 组合
type Node struct{ node *TreeNode }
- 别名
type Queue []int
- GOPATH环境变量
默认在~/go(linux),%USERPROFILE%\go(windows)
官方推荐:所有项目和第三方库都放在同一个GOPATH下
也可以将每个项目放在不同的GOPATH - 安装gopm,再通过gopm安装其他的go依赖包
go get github.com/gpmgo/gopm
使用gopm
gopm get --g -v golang.org/x/tools/cmd/goimports - GOPATH下的目录结构
go build 来编译,得到可执行文件
go install 产生pkg文件和可执行文件,先build然后将可执行文件放在bin目录下
go run直接编译运行
接口
- 定义
type Retriver interface{ Get(url string) string }
- 实现者不需要实现某个接口,只需要实现接口中的某个方法
使用者可以根据自己的需求实现接口中的方法进行调用type Retriver struct{ content string } func (r Retriver) Get(url string) string { return r.content }
- 接口变量有什么
指针接收者实现只能以指针方式使用,值接收者都可以
接口变量中有实现者的类型和实现者的值或者指针
例如
r.(type)
r.(mock.Retriver) - interface{}表示任何类型
interface{}强转类型,interface{}.(int) - 接口的组合
可以将多个接口组合成一个接口,实现者实现多个接口中的方法 - 常用系统接口
- Stringer
里面的string方法相当于Java中的toString()
- Stringer
函数式编程
- 闭包
返回一个函数,该函数可以操作外面函数的变量,外面函数的变量称为该函数的自由变量 - 斐波那契数字
func fibonacci() func() int{ a, b := 0, 1 return func() int { a,b = b, a+b return a } }
- 使用函数遍历二叉树
错误处理与资源管理
- defer调用
使用defer修饰方法调用,当外部方法调用完成后defer修饰的方法调用才会执行(哪怕出错或者return),defer修饰的方法调用顺序是先进后出
确保调用在函数结束时发生
参数在defer语句时计算
defer列表为后进先出 - 错误处理
file, err := os.Open("abc.txt") if err!= nil{ if pathError, ok := err.(*os.PathError); ok{ fmt.Println(pathError.Err) }else{ fmt.Println("unknown error", err) } }
- panic
停止当前函数执行
一直向上返回,执行每一层的defer
如果没有遇见recover,程序退出 - recover
仅在defer中使用
获取panic的值
如果无法处理,可重新panic
测试
- 表格驱动测试
分离的测试数据和测试逻辑
明确的出错信息tests := []struct{ h string code int message string }{ {'haha', 500, 'heihiei'} }
可以部分失败 - 单元测试
测试文件以_test结尾
测试方法以Test开头,并且参数为 t *testing.T
t.Errorf(“expect:,actual:”) - 命令行测试
执行go test . 会对当前目录下所有的test文件进行测试 - 批量测试
测试方法参数为b *testing.B
循环b.N次
go test -bench .- 查看cpu执行情况
需要安装graphviz,www.graphviz.org
go test -bench . -cpuprofile cpu.out
go tool pprof cpu.out
web - b.ResetTimer()重置计算时间,去除数据准备时间
- 查看cpu执行情况
- http测试
httptest.NewRecorder() 创建httpReponseWriter
httptest.NewRequest() 创建*httpRequest - 生成文档
用注释写文档
godoc -http 6060 生成文档
go doc查看文档 - 文档中生成示例代码
使用Example既做测试又能生成示例
Excample类名_方法名func ExampleQueue_Pop() { q := Queue{1} q.Push(2) q.Push(3) fmt.Println(q.Pop()) # Println不需要也可以 // Output: // 1 }
goroutine
- 协程
轻量级线程
非抢占式多任务处理,由协程主动交出控制权。线程就是抢占式,某个线程可能一条语句刚执行一半就被CPU切走了,抢占式需要保存资源比较耗时
编译器/解释器/虚拟机层面的多任务
多个协程可能在一个或多个线程上运行 - 查看数据竞争情况
go run -race a.go - goroutine
go语言程序底层有一个调度器,会负责调度协程,有些协程会放在一个线程中,有些则不会
任何函数调用只需加上go就能送给调度器运行
不需要在定义函数时区分是否是异步函数
调度器在合适的点进行切换
使用-race来检测数据访问冲突 - goroutine可能的切换点
IO,select
channel
等待锁
函数调用(有时)
runtime.Gosched
只是参考,不能保证切换,不能保证在其他地方不切换
Channel
- channel可以作为参数、返回值
- 创建channel
make(chan int) - channel中有数据时必须有goroutine来收,否则会出现dead-lock问题,因此调度器会切换到收取channel数据的goroutine
- 带缓冲的channel可以缓冲一些数据,不需要立即切换goroutine可以提高性能
- 发送方可以close channel,channel关闭后只是数据为默认值,接收方可以使用range遍历或者使用两个返回值来确定channel是否关闭
- channel的使用
c := make(chan int)
c <- 1 往channel发数据
n := <-c 从channel收数据 - sync.WaitGroup
初始时指定任务数量
调用Done方法表示完成任务,任务数减1
调用Wait方法,当任务数不为0时等待,当任务数为0时继续执行 - channel的阻塞
发送者角度:对于同一个通道,发送操作(协程或者函数中的),在接收者准备好之前是阻塞的。如果chan中的数据无人接收,就无法再给通道传入其他数据。
接收者角度:对于同一个通道,接收操作是阻塞的(协程或函数中的),直到发送者可用:如果通道中没有数据,接收者就阻塞了。 - select调度
使用select+defatult可以实现无阻塞接收
select会忽略nil channelselect { case n := <-c1: values = append(values, n) case n := <-c2: values = append(values, n) case activeWorker <- n: printf default: }
- 计时器
time.After(800 * time.Millisecond)会返回一个channel,800毫秒后会往该channel发送一个数据
time.Tick(time.second)会返回一个channel,每秒都会往该channel发送一个数据 - 传统同步机制
- sync.WaitGroup
- sync.Mutex
Lock()
UnLock() - sync.Con
http标准库
- http客户端
- 简单版
res, err := http.Get("http://www.imooc.com") defer res.Body.Close() s, err := httputil.DumpResponse(res, true)
- 复杂版
request, err := http.NewRequest(http.MethodGet, "http://www.imooc.com", nil) request.Header.Add(key, value) client := http.Client{ CheckRedirect: func(req, via) error{ # req就是目标请求,该方法每次请求都会调用不止重定向时调用 return nil }, } response , err := client.Do(request)
- 简单版
- http服务器的性能分析
- 在http服务器启动的文件中,import _ “net/http/pprof”
- 访问/debug/pprof
- 使用go tool pprof分析性能,在pprof包中的pprof.go文件中可以看到分析对应性能的地址
- 其他标准库
bufio
log
encoding/json
regexp
time
strings/math/rand - 学习标准库
studygolang.com/pkgdoc - 广度优先搜索
用循环创建二维slice,首先创建最外维的slice然后在循环创建内部的slice
使用slice实现队列
用Fscanf读取文件
对Point的抽象
单任务爬虫
- 获取响应体的内容
ioutil.readAll(res.Body) - 下载第三方包解决页面乱码
gopm get -g -v golang.org/x/text
transform.NewReader(res.Body, simplefiedchinese.GBK.NewDecoder())
gopm get -g -v golang.org/x/net/html——判断内容的编码 - 正则表达式
- go语言提供了
` ` 这个符号为正则表达式使用,这样不需要使用转义字符
- 创建正则表达式
reg := regexp.MustCompile([a-zA-Z0-9]+@
)
reg.FindAllString(text, -1) # 查找所有与正则表达式匹配的子串 - 提取正则表达式
reg := regexp.MustCompile(([a-zA-Z0-9]+)@
)
reg.FindAllStringSubmatch(text, -1) # 查找所有与正则表达式匹配的子串并将要提取的部门提取出来
- go语言提供了
- 决定编码方式
bytes, err := bufio.NewReader(r).Peek(1024) e, _, _ := charset.DetermineEncoding(bytes, "")
分布式爬虫
- jsonrpc
- 方法规范
返回的结果以指针的形式放在参数中,真正的方法只返回err
只能有一个输入一个输出一个err
- 方法规范