以下引用自左耳听风专栏。
第一,语言简单,上手快。Go 语言的语法特性简直是太简单了,简单到你几乎玩不出什么花招,直来直去的,学习难度很低,容易上手。
第二,并行和异步编程几乎无痛点。Go 语言的 Goroutine 和 Channel 这两个神器简直就是并发和异步编程的巨大福音。像 C、C++、Java、Python 和 JavaScript 这些语言的并发和异步的编程方式控制起来就比较复杂了,并且容易出错,但 Go 语言却用非常优雅和流畅的方式解决了这个问题。这对于编程多年受尽并发和异步折磨的我来说,完全就是眼前一亮的感觉。
(图片来自 Medium:Why should you learn Go?)
第三,Go 语言的 lib 库 “麻雀虽小,五脏俱全”。Go 语言的 lib 库中基本上有绝大多数常用的库,虽然有些库还不是很好,但我觉得这都不是主要问题,因为随着技术的发展和成熟,这些问题肯定也都会随之解决。
第四,C 语言的理念和 Python 的姿态。C 语言的理念是信任程序员,保持语言的小巧,不屏蔽底层且对底层友好,关注语言的执行效率和性能。而 Python 的姿态是用尽量少的代码完成尽量多的事。于是我能够感觉到,Go 语言是想要把 C 和 Python 统一起来,这是多棒的一件事。
(图片来自 Medium:Why should you learn Go?)
所以,即便 Go 语言存在诸多的问题,比如垃圾回收、异常处理、泛型编程等,但相较于上面这几个优势,我认为这些问题都是些小问题。于是就毫不犹豫地入坑了。
资源合集:https://github.com/developer-learning/learning-golang
入门
首首推,通过 TDD 学习 Go https://studygolang.gitbook.io/learn-go-with-tests/
首推 Go by Example 作为你的入门教程。然后,Go 101 也是一个很不错的在线电子书。
The Go Programming Language 中译本:Go 语言圣经
Go 语言官方的 Effective Go 是必读的,这篇文章告诉你如何更好地使用 Go 语言,以及 Go 语言中的一些原理。
web 开发:https://github.com/astaxie/build-web-application-with-golang/ 33.7k 星
进阶
查询 Go 项目,https://gowalker.org/,感觉是 GitHub advance search 的封装。
Go 语言高级编程 https://github.com/chai2010/advanced-go-programming-book
Go 语言原本 https://changkun.de/golang/ 学习源码
如何写出优雅的 Go https://draveness.me/golang-101
https://github.com/qcrao/Go-Questions
博客
只收录有深度的博客,请享用!
博客名 | 简介 | 地址 |
---|---|---|
码农桃花源 | 本项目作者博客园 | https://www.cnblogs.com/qcrao-2018 |
欧神开源书《Go 语言原本》 | Golang committers | https://changkun.de/golang |
No Headback | 滴滴技术大神曹春晖 | http://xargin.com |
面向信仰编程 | 给 kubernetes 提交 pr 的大神 | https://draveness.me |
煎鱼的迷之博客 | 知其然,知其所以然 | https://github.com/EDDYCJY/blog |
Go 语言最突出之处是并发编程,Unix 老牌黑客罗勃・派克(Rob Pike)在 Google I/O 上的两个分享,可以让你学习到一些并发编程的模式。
然后,Go 在 GitHub 的 wiki 上有好多不错的学习资源,你可以从中学习到多。比如:
此外,还有个内容丰富的 Go 资源列表 Awesome Go,推荐看看。
类似 awesome-go https://github.com/hackstoic/golang-open-source-projects
Go roadmap https://github.com/Alikhll/golang-developer-roadmap
Uber Go Style Guide https://github.com/uber-go/guide/blob/master/style.md
官方规范 https://github.com/golang/go/wiki/CodeReviewComments
GoLand Tips & Tricks https://www.bilibili.com/video/av57075824
Goroutine Leak 检测器,Uber出品。https://github.com/uber-go/goleak
库
代码安全
安全工具
扫描工具
网络工具
每个类型都有默认的初值,比如 0,“”,false
定义的变量必须要用到,实在不用的可以用 “_”
var a, b, c int = 1, 2, 3
var a, b, c = 1, 'a', false
var (
home = os.Getenv("HOME")
user = os.Getenv("USER")
)
a, b, c := 1, 'a', false // 只能在函数内使用
必须显式强制类型转换
布尔:bool
整型:int、(u)int8、(u)int16、(u)int32、(u)int64、uintptr
浮点:float32、float64、原生复数:complex64、complex128
字符串:string
字符:rune(int32 别名)
byte:(int8 别名)
派生:
可用 type 设置别名
type S string
Go operator precedence:
1. * / % << >> & &^
2. + - | ^
3. == != < <= > >=
4. &&
5. ||
Go 提供了两种分配原语,即 new 和 make。
new 是分配一个内存,返回一个内存地址,它不会初始化内存,只会将内存置零。
type SyncedBuffer struct {
lock sync.Mutex
buffer bytes.Buffer
}
p := new(SyncedBuffer) // type *SyncedBuffer
make 只用于创建 slice、map 和 channel,并返回一个“已初始化”的值。
未指定类型的常量类似替换,不需要关心类型转换
Go 里的大写有其他含义,常量不再大写
const b string = "string" // 指定后需要显式类型转换
const a = "string"
// 枚举
const(
a = 1
b = 2
c = 3
)
const(
cpp = ioat
python
java
)
const(
_ = iota // 通过赋予空白标识符来忽略第一个值
b = 1 << (10 * iota)
kb
mb
gb
tb
)
if contents, err := ioutil.ReadFile(filename); err != nil {
}
// 默认添加了 break,不 break 需指定
switch i {
case 1:
case 2:
case 3, 4, 5:
default:
}
g := ""
switch {
case g:
}
whatAmI := func(i interface{}) {
switch t := i.(type) {
case bool:
case int:
default:
}
}
whatAmI(true)
i := 1
for i <= 3 {
fmt.Println(i)
i
}
for j := 1; j < 3; j++ {
}
for {
fmt.Println("loop")
break
}
Go 只有值传递,每次传值都拷贝了一个副本,只是这副本里的某个部分可能指着同一块内存,比如 slice。
可传递指针
返回一个局部变量的地址没有问题,该局部变量对应的数据在函数返回后依然有效,编译器采用逃逸分析技术。
// 返回多个值
func eval(a, b int, opt string) (int, error) {
return -1, fmt.Errorf("error: %s", err)
}
// 返回值命名
func test() t string {
t = "test"
return
}
// 函数参数
func apply(op func(int, int) int, a, b int) int {
p := reflect.ValueOf(op).Pointer()
opName := runtime.FuncForPC(p).Name()
fmt.Printf("Calling function %s with args %d, %d", opName, a, b)
return op(a, b)
}
// 匿名函数
apply(func(a, b int) int {
return a + b
}, 1, 2)
// 匿名函数递归,不直接支持。
var f func(int) int
f = func(int) int {
f()
}
// 可变参数列表,无默认参数
func sumArgs(members ...int) int {
s := 0
for i := range numbers {
s += numbers[i]
}
return s
}
//
arr := [...]int{1, 2, 3}
sumArgs(arr...)
指针不能运算
var a int = 2
var pa *int = &a
*pa = 3
Go 一般不用数组,用切片
var arr [5]int
b := [5]int{1, 2, 3, 4, 5}
b := [...]int{1, 2, 3, 4, 5}
var grid [2][3]int
var grid [][]int = [][]int{{1, 2, 3}, {4, 5, 6}}
grid := [2][3]int{{1, 2, 3}, {4, 5, 6}}
for k, v := range grid {
fmt.Println(k, v)
}
// [10]int,[5]int 是不同类型,数组传递是值传递,会拷贝
func printArray(arr [10]int) {
fmt.Println(arr)
}
切片本身没有数据,是对底层数组的一个 view,是功能强悍的”动态数组“。
切片通过对数组进行封装,为数据序列提供了更通用、强大的接口。
除了矩阵变换这类需要明确维度的情况外, Go 中大部分数组编程都是通过切片来实现的。
切片保存了对底层数组的引用,若将某个切片赋值给另一个切片,它们将引用同一个数组。
len(s)
用来获取长度,当前有多少个值,用了多少
cap(s)
切片总容量
append(s, tg)
添加元素
copy(dst, src)
拷贝切片
// 初始化
slice := []int
slice := []int{1, 2, 3, 4}
slice := make([]type, len) // 创建空切片,可不指定 cap
slice := make([]type, len, cap)
fmt.Println(arr[:])
fmt.Println(arr[:3])
fmt.Println(arr[2:])
fmt.Println(arr[3:5])
// 多次切片,可以超过上次切片范围,不可超过 cap(s),即向后扩展
s := arr[3:5]
rs := s[4:6]
// 扩展,由于是值传递,必须用个变量来接收,底层将创建一个新的 array 来支撑切片
b = append(b, 10)
b = append(b, s...) // 不能 +,但可变参数能直接添加切片
fmt.Println(b)
intList := []int{2, 4, 3, 5, 7, 6, 9, 8, 1, 0}
float8List := []float64{4.2, 5.9, 12.3, 10.0, 50.4, 99.9, 31.4, 27.81828, 3.14}
stringList := []string{"a", "c", "b", "d", "f", "i", "z", "x", "w", "y"}
sort.Ints(intList)
sort.Sort(sort.IntSlice(intList))
sort.Sort(sort.Reverse(sort.IntSlice(intList)))
sort.Sort(sort.Reverse(sort.Float64Slice(float8List)))
sort.Sort(sort.Reverse(sort.StringSlice(stringList)))
// 为什么好好的 []int 类型还要类型转换一次呢?答案在下面,只有这个别名类型才实现了这些接口。
// 是不是还与 Go 的显式类型转换有关?
type IntSlice []int
func (p IntSlice) Len() int { return len(p) }
func (p IntSlice) Less(i, j int) bool { return p[i] < p[j] }
func (p IntSlice) Swap(i, j int) { p[i], p[j] = p[j], p[i] }
// Sort is a convenience method.
func (p IntSlice) Sort() { Sort(p) }
// 自定义排序
sort.Slice(arr, func(i, j int) bool {
return arr[i][0] < arr[j][0]
})
// 创建一个为内置 `[]string` 类型的别名的 `ByLength` 类型
type ByLength []string
// 实现 `sort.Interface` 的 `Len`,`Less` 和 `Swap` 方法
func (s ByLength) Len() int {
return len(s)
}
func (s ByLength) Swap(i, j int) {
s[i], s[j] = s[j], s[i]
}
func (s ByLength) Less(i, j int) bool {
return len(s[i]) < len(s[j])
}
fruits := []string{"peach", "banana", "kiwi"}
sort.Sort(ByLength(fruits))
import "sort"
in := []int{3,2,1,4,3,2,1,4,1} // any item can be sorted
sort.Ints(in)
j := 0
for i := 1; i < len(in); i++ {
if in[j] == in[i] {
continue
}
j++
// preserve the original data
// in[i], in[j] = in[j], in[i]
// only set what is required
in[j] = in[i]
}
result := in[:j+1]
fmt.Println(result) // [1 2 3 4]
a = append(a[:i], a[i+1:]...)
// or
a = a[:i+copy(a[i:], a[i+1:])]
// 快版本,改变了相对顺序
a := []string{"A", "B", "C", "D", "E"}
i := 2
// Remove the element at index i from a.
a[i] = a[len(a)-1] // Copy last element to index i.
a[len(a)-1] = "" // Erase last element (write zero value).
a = a[:len(a)-1] // Truncate slice.
fmt.Println(a) // [A B E D]
// 慢版本,保持相对顺序
copy(a[i:], a[i+1:]) // Shift a[i+1:] left one index.
a[len(a)-1] = "" // Erase last element (write zero value).
a = a[:len(a)-1] // Truncate slice.
reflect.DeepEqual(s1, s2)
// 还可以用来比较 map
To replace the contents of a slice with the same elements but in reverse order:
for i := len(a)/2-1; i >= 0; i-- {
opp := len(a)-1-i
a[i], a[opp] = a[opp], a[i]
}
The same thing, except with two indices:
for left, right := 0, len(a)-1; left < right; left, right = left+1, right-1 {
a[left], a[right] = a[right], a[left]
}
https://github.com/golang/go/blob/440f7d64048cd94cba669e16fe92137ce6b84073/src/runtime/slice.go
加深理解:https://www.calhoun.io/why-are-slices-sometimes-altered-when-passed-by-value-in-go/
所以传值的时候,slice 对应的变量是拷贝的,但里面指向的 array 没变,除非 append 等操作改变了这个指针。
type slice struct {
array unsafe.Pointer
len int
cap int
}
+--------+
| |
| ptr |+------------+-------+-----------+
| | | |
+--------+ | |
| | | |
| | | |
| len 5 | | |
| | | |
+--------+ v v
| | +-----+-----+-----+-----+----+
| | | | | | | |
| cap 5 | [5]int | 0 | 1 | 2 | 3 | 4 |
| | +-----+-----+-----+-----+----+
+--------+
slice := arr[1:4] arr := [5]int{0,1,2,3,4}
package main
import (
"fmt"
)
func main() {
s := make([]int, 0, 7)
for i := 1; i <= 3; i++ {
s = append(s, i)
}
reverse(s)
fmt.Println(len(s), cap(s))
fmt.Println(s)
}
func reverse(s []int) {
newElem := 999
for len(s) < cap(s) {
fmt.Println("Adding an element:", newElem, "cap:", cap(s), "len:", len(s))
s = append(s, newElem)
newElem++
}
fmt.Println(len(s), cap(s))
fmt.Println(s)
for i, j := 0, len(s)-1; i < j; i++ {
j = len(s) - (i + 1)
s[i], s[j] = s[j], s[i]
}
}
// 这样的结果就很容易理解了
Adding an element: 999 cap: 7 len: 3
Adding an element: 1000 cap: 7 len: 4
Adding an element: 1001 cap: 7 len: 5
Adding an element: 1002 cap: 7 len: 6
7 7
[1 2 3 999 1000 1001 1002]
3 7
[1002 1001 1000]
b = make([]T, len(a))
copy(b, a)
// or
b = append([]T(nil), a...)
// or
b = append(a[:0:0], a...) // See https://github.com/go101/go101/wiki
a = append(a[:i], a[j:]...)
a[i] = a[len(a)-1]
a = a[:len(a)-1]
NOTE If the type of the element is a pointer or a struct with pointer fields, which need to be garbage collected, the above implementations of Cut
and Delete
have a potential memory leak problem: some elements with values are still referenced by slice a
and thus can not be collected. The following code can fix this problem:
Cut
copy(a[i:], a[j:])
for k, n := len(a)-j+i, len(a); k < n; k++ {
a[k] = nil // or the zero value of T
}
a = a[:len(a)-j+i]
Delete
if i < len(a)-1 {
copy(a[i:], a[i+1:])
}
a[len(a)-1] = nil // or the zero value of T
a = a[:len(a)-1]
Delete without preserving order
a[i] = a[len(a)-1]
a[len(a)-1] = nil
a = a[:len(a)-1]
a = append(a[:i], append(make([]T, j), a[i:]...)...)
a = append(a, make([]T, j)...)
n := 0
for _, x := range a {
if keep(x) {
a[n] = x
n++
}
}
a = a[:n]
a = append(a[:i], append([]T{x}, a[i:]...)...)
NOTE The second append
creates a new slice with its own underlying storage and copies elements in a[i:]
to that slice, and these elements are then copied back to slice a
(by the first append
). The creation of the new slice (and thus memory garbage) and the second copy can be avoided by using an alternative way:
Insert
s = append(s, 0 /* use the zero value of the element type */)
copy(s[i+1:], s[i:])
s[i] = x
a = append(a[:i], append(b, a[i:]...)...)
a = append([]T{x}, a...)
x, a = a[0], a[1:]
This trick uses the fact that a slice shares the same backing array and capacity as the original, so the storage is reused for the filtered slice. Of course, the original contents are modified.
b := a[:0]
for _, x := range a {
if f(x) {
b = append(b, x)
}
}
For elements which must be garbage collected, the following code can be included afterwards:
for i := len(b); i < len(a); i++ {
a[i] = nil // or the zero value of T
}
Fisher–Yates algorithm:
Since go1.10, this is available at math/rand.Shuffle
for i := len(a) - 1; i > 0; i-- {
j := rand.Intn(i + 1)
a[i], a[j] = a[j], a[i]
}
Useful if you want to do batch processing on large slices.
actions := []int{0, 1, 2, 3, 4, 5, 6, 7, 8, 9}
batchSize := 3
batches := make([][]int, 0, (len(actions) + batchSize - 1) / batchSize)
for batchSize < len(actions) {
actions, batches = actions[batchSize:], append(batches, actions[0:batchSize:batchSize])
}
batches = append(batches, actions)
Yields the following:
[[0 1 2] [3 4 5] [6 7 8] [9]]
哈希表实现,除了 slice、map、function 的内建类型都可以做 key,不含这些字段的 Struct 也可
m := make(map[string]int) // 创建空map
m["one"] = 1
m["two"] = 2
m := map[string]int {
"one": 1,
"two": 2,
}
var m map[string]int
// 访问,不存在放回 nil
one := m["one"]
// 判断是否存在,为啥能选择返回值?
one, ok := m["one"]
// 遍历
for k, v := range m {
fmt.Println(k, v)
}
delete(m, "one")
项目要用的话直接 import “github.com/golang-collections/collections/stack” 用 list 实现 stack
package stack
import "container/list"
type Stack struct {
list *list.List
}
func NewStack() *Stack {
list := list.New()
return &Stack{list}
}
func (stack *Stack) Push(value interface{}) {
stack.list.PushBack(value)
}
func (stack *Stack) Pop() interface{} {
e := stack.list.Back()
if e != nil {
stack.list.Remove(e)
return e.Value
}
return nil
}
func (stack *Stack) Peak() interface{} {
e := stack.list.Back()
if e != nil {
return e.Value
}
return nil
}
func (stack *Stack) Len() int {
return stack.list.Len()
}
func (stack *Stack) Empty() bool {
return stack.list.Len() == 0
}
Go 特意做了优化,设计了 rune,可以完美的支持多语言。
'a'
为字符 rune、"abc"
为字符串,`` 可包含复杂的字符串。
s := "我爱Go语言!"
for i, ch := range []rune(s) {
fmt.Printf("(%d %c)", i, ch)
}
// (0 我)(1 爱)(2 G)(3 o)(4 语)(5 言)(6 !)
s[i] 并不是字符串,而是 uint8,即 ASCII 码,需要转一下 str := string(s[i])
fmt.Printf("%t\n", 1==2)
fmt.Printf("%t\n", true) // 格式化布尔值是简单的。
fmt.Printf("二进制:%b\n", 255)
fmt.Printf("八进制:%o\n", 255)
fmt.Printf("十进制:%d\n", 255)
fmt.Printf("十六进制:%X\n", 255)
fmt.Printf("%c\n", 33) // 这个输出给定整数的对应字符。
fmt.Printf("浮点数:%f\n", math.Pi)
fmt.Printf("字符串:%s\n", "hello world")
fmt.Printf("%q\n", "\"string\"") // 带双引号的字符串
fmt.Printf("%p\n", &p) // 指针
fmt.Printf("类型:%T\n", "hello world")
fmt.Printf("字段在内的实例的完整信息:%+v\n", "hello world")
fmt.Printf("字段和限定类型名称在内的实例的完整信息:%#v\n", "hello world")
// Go 为常规 Go 值的格式化设计提供了多种打印方式。例如,这里打印了 `point` 结构体的一个实例。
p := point{1, 2}
fmt.Printf("%v\n", p)
// 如果值是一个结构体,`%+v` 的格式化输出内容将包括结构体的字段名。
fmt.Printf("%+v\n", p)
// `%#v` 形式则输出这个值的 Go 语法表示。例如,值的运行源代码片段。
fmt.Printf("%#v\n", p)
// 需要打印值的类型,使用 `%T`。
fmt.Printf("%T\n", p)
// `%e` 和 `%E` 将浮点型格式化为(稍微有一点不同的)科学记数法表示形式。
fmt.Printf("%e\n", 123400000.0)
fmt.Printf("%E\n", 123400000.0)
// 和上面的整型数一样,`%x` 输出使用 base-16 编码的字符串,每个字节使用 2 个字符表示。
fmt.Printf("%x\n", "hex this")
// 当输出数字的时候,你将经常想要控制输出结果的宽度和精度,可以使用在 `%` 后面使用数字来控制输出宽度。
// 默认结果使用右对齐并且通过空格来填充空白部分。
fmt.Printf("|%6d|%6d|\n", 12, 345)
// 你也可以指定浮点型的输出宽度,同时也可以通过 宽度.精度 的语法来指定输出的精度。
fmt.Printf("|%6.2f|%6.2f|\n", 1.2, 3.45)
// 要左对齐,使用 `-` 标志。
fmt.Printf("|%-6.2f|%-6.2f|\n", 1.2, 3.45)
// 你也许也想控制字符串输出时的宽度,特别是要确保他们在类表格输出时的对齐。这是基本的右对齐宽度表示。
fmt.Printf("|%6s|%6s|\n", "foo", "b")
// 要左对齐,和数字一样,使用 `-` 标志。
fmt.Printf("|%-6s|%-6s|\n", "foo", "b")
// 到目前为止,我们已经看过 `Printf`了,它通过 `os.Stdout`输出格式化的字符串。`Sprintf` 则格式化并返回一个字符串而不带任何输出。
s := fmt.Sprintf("a %s", "string")
fmt.Println(s)
// 你可以使用 `Fprintf` 来格式化并输出到 `io.Writers`而不是 `os.Stdout`。
fmt.Fprintf(os.Stderr, "an %s\n", "error")
还有对应的 strings、strconv 包。
s := "This is an example of a string.中文"
// 前后缀
println(strings.HasPrefix(s, "This"))
println(strings.HasSuffix(s, "string"))
// 包含子串
println(strings.Contains(s, "a "))
// 子串在父串中的索引
println(strings.Index(s, "is"))
println(strings.LastIndex(s, "i"))
// println(strings.IndexRune(s, []rune("string"))) // 非 ASCII 用这个
strings.Replace()
strings.ToLower(s) string
// 数字转 string,不能 string(),这样等于 chr()
strconv.Itoa(int(item)) // 正确操作
// os.Open
f, err := os.Open('/etc/passwd')
defer f.Close()
buf := make([]byte, 10)
f.Read(buf)
// os.OpenFile
// bufio 缓冲
r := bufio.NewReader(f)
for {
str, err := r.ReadString('\n')
if err == io.EOF {
break
}
fmt.Printf(str)
}
// ioutil 一次读完
content, err := ioutil.ReadFile('/etc/passwd') // content 为 []byte
// 目录
ioutil.ReadDir(".")
cmd := exec.Command("id")
stdoutStderr, err := cmd.CombinedOutput()
if err != nil {
log.Fatal(err)
}
package main
import "strings"
import "fmt"
// 返回目标字符串 `t` 出现的的第一个索引位置,或者在没有匹配值时返回 -1。
func Index(vs []string, t string) int {
for i, v := range vs {
if v == t {
return i
}
}
return -1
}
// 如果目标字符串 `t` 在这个切片中则返回 `true`。
func Include(vs []string, t string) bool {
return Index(vs, t) >= 0
}
// 如果这些切片中的字符串有一个满足条件 `f` 则返回 `true`。
func Any(vs []string, f func(string) bool) bool {
for _, v := range vs {
if f(v) {
return true
}
}
return false
}
// 如果切片中的所有字符串都满足条件 `f` 则返回 `true`。
func All(vs []string, f func(string) bool) bool {
for _, v := range vs {
if !f(v) {
return false
}
}
return true
}
// 返回一个包含所有切片中满足条件 `f` 的字符串的新切片。
func Filter(vs []string, f func(string) bool) []string {
vsf := make([]string, 0)
for _, v := range vs {
if f(v) {
vsf = append(vsf, v)
}
}
return vsf
}
// 返回一个对原始切片中所有字符串执行函数 `f` 后的新切片。
func Map(vs []string, f func(string) string) []string {
vsm := make([]string, len(vs))
for i, v := range vs {
vsm[i] = f(v)
}
return vsm
}
func main() {
// 这里试试这些组合函数。
var strs = []string{"peach", "apple", "pear", "plum"}
fmt.Println(Index(strs, "pear"))
fmt.Println(Include(strs, "grape"))
fmt.Println(Any(strs, func(v string) bool {
return strings.HasPrefix(v, "p")
}))
fmt.Println(All(strs, func(v string) bool {
return strings.HasPrefix(v, "p")
}))
fmt.Println(Filter(strs, func(v string) bool {
return strings.Contains(v, "e")
}))
// 上面的例子都是用的匿名函数,但是你也可以使用类型正确的命名函数
fmt.Println(Map(strs, strings.ToUpper))
}
仅支持封装,不支持继承和多态
用大小写来区分,大写开头 public、小写开头 private,private 只能在当前包内使用(使用工厂模式解决,即自行实现(大写开头)构造函数)
type Books struct {
title string
author string
book_id string
}
books := Books{
title: "Go",
author: "Tim",
book_id: "1",
}
// tag
type Member struct {
Id int `json:"id,-"`
Name string `json:"name"`
Email string `json:"email"`
Gender int `json:"gender,"`
Age int `json:"age"`
}
books = Books{"Go", "Tim", "2"}
// 传值,注意一致性,如果有指针接收,最好全用指针接收
func (book Books) print() {
}
// 传指针,nil 指针也可以调用方法,某些情景下需要加下判断
func (book *Books) print() {
if book == nil {
}
}
// 继承
type Edu struct {
Books // 写个匿名结构体就行
name string
}
同一个目录下只能有一个包,main 包下为主入口
为结构定义的方法必须放在同一个包内
可以是不同文件
import 中可以使用相对路径 ./
、../
引用包,如果没有用相对路径,go 会去 $GOPATH/src/ 目录找
gopath 和 path 一样,可以接受多个路径,路径之间用冒号分隔
go: cannot find main module; see ‘go help modules’
go env -w GO111MODULE=on
go env -w GOPROXY=https://mirrors.aliyun.com/goproxy/,direct
go mod 子命令
go mod init module_name
go list -m -u all # 检查可以升级的包
go get -u need-upgrade-package # 升级
download
edit
graph
init
tidy
vendor
verify
why
Python 中的鸭子
// 运行时才知道传入的 retriever 有没有 get 方法
// 需要注释来说明接口
def download(retriever):
return retriever.get("http://qq.com")
C++ 中的鸭子
// 同 Python
template <class R>
string download(const R& retriver) {
return retriver.get("http://qq.com")
}
Java 中的类似代码
// 传入的参数必须实现 Retriever 接口
// 不是 duck typing
// 同时需要 Readable、Appendable 怎么办?(Apache polygene)
<R extends Retriver>
String download(R r) {
return r.get("http://qq.com")
}
Go
type Retriver interface {
Get(source string) string
}
func download(retriver Retriver) string {
return retriver.Get("http://qq.com")
}
接口由使用者定义
接口的实现时隐式的,只要实现里面的方法(不太理解这句话
接口本身不能创建实例,但可以指向一个实现了该接口的(自定义)类型的变量。
一个自定义类型需要将某个接口的所有方法都实现,才说这个自定义类型实现了该接口,否则编译不通过。
一个自定义类型可以实现多个接口
一个接口可以继承多个接口
interface 类型默认是一个指针
空接口 interface{}
没有任何方法,所以所有类型都实现了空接口,即可以把任何一个变量赋值给空接口。
类型断言
var x interface{}
var f float32 = 1.1
x = f
y := x.(float32) // 这里改成 float64 将 panic
// 加入检测
if y, ok := x.(float32); ok {
// xx
}
继承与接口
当 A 结构体继承了 B 结构体,那么 A 就有了 B 的所有字段和方法,并可以直接调用。
当 A 结构体需要扩展功能,同时不希望破坏继承关系,实现某个接口即可。
因此,实现接口可以看做是对继承机制的补充。
继承的价值:解决代码的复用性和可维护性。
接口的价值:设计,设计好各种规范(方法),让其它自定义类型去实现这种方法。
接口比继承更加灵活,继承是满足 is-a 的关系,而接口只需满足 like-a 的关系,在一定程度上实现了解耦。
实际上就是实现了多态,同样的方法在不同对象调用时表现不同的意义? 还有个典型的列子,即
sort()
,需要实现三个方法就可以给自定义类型排序
// 几何学接口
type geometry interface {
area() float64
perim() float64
}
type rect struct {
width, height float64
}
type circle struct {
radius float64
}
func (r rect) area float64 {
return r.width * r.height
}
func (r rect) perim() float64 {
return (r.width + r.height) * 2
}
func (c circle) area() float64 {
return math.Pi * c.radius * c.radius
}
func (c circle) perim() float64 {
return 2 * math.Pi * c.radius
}
// 统一
func measure(g geometry) {
fmt.Println(g)
fmt.Println(g.area())
fmt.Println(g.perim())
}
func main() {
r := rect{width: 3, height: 4}
c := circle{radius: 5}
measure(r)
measure(c)
}
函数内的局部变量 + 匿名函数构成闭包
func adder() func(int) int {
sum := 0
return func(value int) int {
sum += value
return sum
}
}
为函数实现接口
使用反射遍历结构体字段,调用结构体的方法,并获取结构体标签的值。
定义多个函数,再定义一个适配器函数用作统一处理接口。
使用反射创建并操作结构体。
延时机制,在 return 后再调用,先 defer 的后调用。
常常需要创建资源(数据库连接、文件句柄、锁),使用 defer 来关闭更省心。
panic 一旦出错直接终止程序。
recover 可对接收到的错误自定义处理。
defer func() {
if err := recover(); err {
println(err)
}
}()
哪些不能 Recover:
Go 的 err 过于简单,需要增加上下文信息。
package main
import (
"fmt"
)
type Handler interface {
Filter(err error, r interface{}) error
}
type Logger interface {
Ef(format string, a ...interface{})
}
// Handle panic by hdr, which filter the error.
// Finally log err with logger.
func HandlePanic(hdr Handler, logger Logger) error {
return handlePanic(recover(), hdr, logger)
}
type hdrFunc func(err error, r interface{}) error
func (v hdrFunc) Filter(err error, r interface{}) error {
return v(err, r)
}
type loggerFunc func(format string, a ...interface{})
func (v loggerFunc) Ef(format string, a ...interface{}) {
v(format, a...)
}
// Handle panic by hdr, which filter the error.
// Finally log err with logger.
func HandlePanicFunc(hdr func(err error, r interface{}) error,
logger func(format string, a ...interface{}),
) error {
var f Handler
if hdr != nil {
f = hdrFunc(hdr)
}
var l Logger
if logger != nil {
l = loggerFunc(logger)
}
return handlePanic(recover(), f, l)
}
func handlePanic(r interface{}, hdr Handler, logger Logger) error {
if r != nil {
err, ok := r.(error)
if !ok {
err = fmt.Errorf("r is %v", r)
}
if hdr != nil {
err = hdr.Filter(err, r)
}
if err != nil && logger != nil {
logger.Ef("panic err %+v", err)
}
return err
}
return nil
}
func main() {
func() {
defer HandlePanicFunc(nil, func(format string, a ...interface{}) {
fmt.Println(fmt.Sprintf(format, a...))
})
panic("ok")
}()
logger := func(format string, a ...interface{}) {
fmt.Println(fmt.Sprintf(format, a...))
}
func() {
defer HandlePanicFunc(nil, logger)
panic("ok")
}()
}
先写测试 => 尝试运行测试 => 写少量代码跑起来 => 补充完整通过测试 => 重构 => 跳到第二步
先写测试代码,编写足够的代码来使编译通过,仅此而已 。
请记住,我们要查看的是,测试是否因为合理的原因失败。
迭代!
尽你所能拆分需求是一项很重要的技能,这样你就能拥有可以工作的软件。
在测试的支持下,将功能切分成小的功能点,并使其首尾相连顺利的运行。
敏捷开发:让它运作,使它正确,使它快速
过早的优化是万恶之源 —— Donald Knuth
单元测试:保证项目工程质量的最有效办法。
suite
或者 BDD 的风格对单元测试进行合理组织;命名约定
_test.go
结尾。
Test
开头
t *testing.T
参数。// hello_test.go
import "testing"
func TestHello(t *testing.T) {
assertCorrectMessage := func(t *testing.T, got string, want string) {
t.Helper() // 声明是辅助函数
if got != want {
t.Errorf("want %q got %q", want, got)
}
}
t.Run("saying hello to people", func(t *testing.T) {
got := Hello("wywwzj")
want := "Hello, wywwzjj"
assertCorrectMessage(t, got, want)
})
t.Run("empty string defaults to 'world'", func(t *testing.T) {
want := "Hello, world"
got := Hello("")
assertCorrectMessage(t, got, want)
})
}
// 表格式测试(Goland 可直接生成)
func TestFib(t *testing.T) {
var fibTests = []struct {
in int // input
expected int // expected result
}{
{1, 1},
{2, 1},
{3, 2},
{4, 3},
{5, 5},
{6, 8},
{7, 13},
}
for _, tt := range fibTests {
actual := Fib(tt.in)
if actual != tt.expected {
t.Errorf("Fib(%d) = %d; expected %d", tt.in, actual, tt.expected)
}
}
}
测试并发中的条件竞争
go test -race
既能生成文档,也是做了一次测试。
func Example_GetScore() {
score := getScore(100, 100, 100, 2.1)
fmt.Println(score)
// Output:
// 31.1
}
gotest 的变量有这些:
T 结构内部是继承自 common 结构,common 结构提供集中方法,是我们经常会用到的:
当我们遇到一个断言错误的时候,我们就会判断这个测试用例失败,就会使用到:
Fail : case 失败,测试用例继续
FailedNow : case 失败,测试用例中断
当我们遇到一个断言错误,只希望跳过这个错误,但是不希望标示测试用例失败,会使用到:
SkipNow : case 跳过,测试用例不继续
当我们只希望在一个地方打印出信息,我们会用到 :
Log : 输出信息
Logf : 输出有 format 的信息
当我们希望跳过这个用例,并且打印出信息 :
Skip : Log + SkipNow
Skipf : Logf + SkipNow
当我们希望断言失败的时候,测试用例失败,打印出必要的信息,但是测试用例继续:
Error : Log + Fail
Errorf : Logf + Fail
当我们希望断言失败的时候,测试用例失败,打印出必要的信息,测试用例中断:
Fatal : Log + FailNow
Fatalf : Logf + FailNow
// 基准测试,go test -bench=.
func BenchmarkHello(t testing.B) {
b.ResetTimer() // 如果在运行前基准测试需要一些耗时的配置,则可以先重置定时器
for i := 0; i < b.N; i++ {
Hello();
}
}
// BenchmarkHello 10000000 282 ns/op
// 意思是执行 10000000 次,每次执行需要 282 纳秒。
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusOK)
}))
server.URL
####
runtime/pprof
net/http/pprof
runtime/trace
只需在程序执行前加上环境变量 GODEBUG=gctrace=1
GODEBUG=gctrace=1 go test -bench=.
GODEBUG=gctrace=1 go run main.go
字符串是不可变对象,已经生成不能改变,每次改变都是产生了一个新的字符串。
string.Builder 性能最好
var builder strings.Builder
for i := 0; i < b.N; i++ {
builder.WriteString(strconv.Itoa(i))
}
str := builder.String()
bytes.Buffer 次之
var buf bytes.Buffer
for i := 0; i < b.N; i++ {
buf.WriteString(strconv.Itoa(i))
}
str := buf.String()
+
sprintf 最慢
var s string
for i := 0; i < b.N; i++ {
s = fmt.Sprintf("%v%v", s, i)
}
Go 实现了 CSP(通信顺序进程,Communicaing Sequential Process)模型来作为 goroutine 间的推荐通信方式
MPG模式
M:操作系统的主线程(物理线程)
P:协程执行需要的上下文
G:协程
如果协程中出现了 panic,则整个程序都会崩溃。
设置运行 CPU 数量
num := runtime.NumCPU() // 获取系统的逻辑CPU个数
runtime.GOMAXPROCS(233) // 指定数量,go 1.8 后默认运行在多核上
不要通过共享内存来通信,而应通过通信来共享内存。
类似 Unix 下的双向管道,可指定单向,用于goroutine 之间通信,可传送任意数据类型。符号为 <- chan <-
。
线程安全,多 goroutine 访问时,不需要加锁,即本身就是线程安全的。
// goroutine 与 main 通信
func main() {
channel := make(chan string)
go func() {
channel <- "Hello"
}
}
main 函数所处的是一个主 goroutine,由 runtime.main 启动。
main 函数一旦运行结束退出,其他的 goroutine 会被杀掉。
channel := make(chan int, 3) // 3 buffer
channel 默认是阻塞的,满了阻塞写,空了阻塞读。
获取 channel 内容可使用 range 遍历,但发送方 channel 要手动 close 一下。
// 我们将遍历在 `queue` 通道中的两个值。
queue := make(chan string, 2)
queue <- "one"
queue <- "two"
close(queue)
// 这个 `range` 迭代从 `queue` 中得到的每个值。因为我们在前面 `close` 了这个通道,这个迭代会在接收完 2 个值之后结束。
// 如果我们没有 `close` 它,我们将在这个循环中继续阻塞执行,等待接收第三个值
for elem := range queue {
fmt.Println(elem)
}
Go 中的 select
// for 指定次数
for i := 0; i < 2; i++ {
select {
case msg1 := <- c1:
// xx
case msg2 := <- c2:
// xx
}
}
// 设定 timeout
for {
timeout_cnt := 0
select {
case msg1 := <- c1:
// xx
case msg2 := <- c2:
// xx
case <- time.After(time.Second * 30):
timeout_cnt++
}
if time_cnt > 3 {
break
}
}
// 无阻塞 channel
for {
select {
case msg1 := <- c1:
// xx
case msg2 := <- c2:
// xx
default: // 加入 default 后无阻塞
// xx
}
}
// 关闭 channel,channel 会返回两个值,一个是内容,一个是还有没有内容
close(channel)
more := true
for more {
select {
case msg, more = <- channel:
if more {
// xx
} else {
// xx
}
}
}
var sem = make(chan int, MaxOutstanding)
func handle(r *Request) {
sem <- 1 // 等待活动队列清空。
process(r) // 可能需要很长时间。
<-sem // 完成;使下一个请求可以运行。
}
func Serve(queue chan *Request) {
for {
req := <-queue
go handle(req) // 无需等待 handle 结束。
}
}
// 队列满了,依然开了 goroutine,改进
func Serve(queue chan *Request) {
for {
req := <-queue
sem <- 1
go func() {
process(req)
<- sem
}()
}
}
// req 在所有 goroutine 中共享,一旦改变就容易出错
func Serve(queue chan *Request) {
for {
req := <-queue
sem <- 1
go func(req *Request) {
process(req)
<- sem
}(req)
}
}
// 利用闭包
func Serve(queue chan *Request) {
for req := range queue {
req := req // 用相同的名字获得了该变量的一个新的版本, 以此来局部地刻意屏蔽循环变量,使它对每个 Go 协程保持唯一
sem <- 1
go func() {
process(req)
<- sem
}()
}
}
package main
import "fmt"
import "time"
// 这是我们将要在多个并发实例中支持的任务了。
// 这些执行者将从 `jobs` 通道接收任务,并且通过 `results` 发送对应的结果。
// 我们将让每个任务间隔 1s 来模仿一个耗时的任务。
func worker(id int, jobs <-chan int, results chan<- int) {
for j := range jobs {
fmt.Println("worker", id, "processing job", j)
time.Sleep(time.Second)
results <- j * 2
}
}
func main() {
// 为了使用 worker 线程池并且收集他们的结果,我们需要 2 个通道。
jobs := make(chan int, 100)
results := make(chan int, 100)
// 这里启动了 3 个 worker,初始是阻塞的,因为还没有传递任务。
for w := 1; w <= 3; w++ {
go worker(w, jobs, results)
}
// 这里我们发送 9 个 `jobs`,然后 `close` 这些通道来表示这些就是所有的任务了。
for j := 1; j <= 9; j++ {
jobs <- j
}
close(jobs)
// 最后,我们收集所有这些任务的返回值。
for a := 1; a <= 9; a++ {
<-results
}
}
runtime.Gosched() // 运行其他协程执行
这样的函数还有很多,参看 go 的 atomic 包文档
imort "sync/atomic"
var cnt uint32 = 0
atomic.AddUint32(&cnt, 1)
cntFinal := atomic.LoadUint32(&cnt) // 取数据
var memoryAccess sync.Mutex
var value int
go func() {
memoryAccess.Lock()
value++
memoryAccess.Unlock()
}
memoryAccess.Lock()
if value == 0 {
println(value)
}
memoryAccess.Unlock()
func main() {
// 在我们的例子中,`state` 是一个 map。
var state = make(map[int]int)
// 这里的 `mutex` 将同步对 `state` 的访问。
var mutex = &sync.Mutex{}
// we'll see later, `ops` will count how many
// operations we perform against the state.
// 为了比较基于互斥锁的处理方式和我们后面将要看到的其他
// 方式,`ops` 将记录我们对 state 的操作次数。
var ops int64 = 0
// 这里我们运行 100 个 Go 协程来重复读取 state。
for r := 0; r < 100; r++ {
go func() {
total := 0
for {
// 每次循环读取,我们使用一个键来进行访问,
// `Lock()` 这个 `mutex` 来确保对 `state` 的
// 独占访问,读取选定的键的值,`Unlock()` 这个
// mutex,并且 `ops` 值加 1。
key := rand.Intn(5)
mutex.Lock()
total += state[key]
mutex.Unlock()
atomic.AddInt64(&ops, 1)
// 为了确保这个 Go 协程不会在调度中饿死,我们
// 在每次操作后明确的使用 `runtime.Gosched()`
// 进行释放。这个释放一般是自动处理的,像例如
// 每个通道操作后或者 `time.Sleep` 的阻塞调用后
// 相似,但是在这个例子中我们需要手动的处理。
runtime.Gosched()
}
}()
}
// 同样的,我们运行 10 个 Go 协程来模拟写入操作,使用
// 和读取相同的模式。
for w := 0; w < 10; w++ {
go func() {
for {
key := rand.Intn(5)
val := rand.Intn(100)
mutex.Lock()
state[key] = val
mutex.Unlock()
atomic.AddInt64(&ops, 1)
runtime.Gosched()
}
}()
}
// 让这 10 个 Go 协程对 `state` 和 `mutex` 的操作
// 运行 1 s。
time.Sleep(time.Second)
// 获取并输出最终的操作计数。
opsFinal := atomic.LoadInt64(&ops)
fmt.Println("ops:", opsFinal)
// 对 `state` 使用一个最终的锁,显示它是如何结束的。
mutex.Lock()
fmt.Println("state:", state)
mutex.Unlock()
}
调用无数次也只执行一次。
var once sync.Once
onceBody := func() {
fmt.Println(time.Now())
}
done := make(chan bool)
for i := 0; i < 10; i++ {
go func() {
once.Do(onceBody)
done <- true
}()
}
for i := 0; i < 10; i++ {
fmt.Println(<- done)
}
// A WaitGroup must not be copied after first use.
// 传给 goroutine 时需要传指针
wg := sync.WaitGroup{}
wg.Add(10) // 创建 goroutine 时添加
wg.Done() // goroutine 对应的函数运行结束后调用 Done
wg.Wait()
go build,go run 或者 go test 命令后面加上 -race
尽量多用 os 提供的方法,这里的方法都是跨平台的,少用 syscall。
package main
import (
"fmt"
"time"
)
func daysBetweenDates(date1 string, date2 string) int {
d1, _ := time.Parse("2006-01-02", date1)
d2, _ := time.Parse("2006-01-02", date2)
delta := d1.Sub(d2)
return abs(int(delta.Hours()) / 24)
}
func main() {
timeString := time.Now().Format("2006-01-02 15:04:05") // 这是一个神奇的时间,改了就错
fmt.Println(timeString)
fmt.Println(time.Now().Format("2017-09-07 18:05:32"))
p := fmt.Println
// 得到当前时间。
now := time.Now()
p(now)
// 通过提供年月日等信息,你可以构建一个 `time`。时间总是关联着位置信息,例如时区。
then := time.Date(2009, 11, 17, 20, 34, 58, 651387237, time.UTC)
p(then)
// 你可以提取出时间的各个组成部分。
p(then.Year())
p(then.Month())
p(then.Day())
p(then.Hour())
p(then.Minute())
p(then.Second())
p(then.Nanosecond())
p(then.Location())
// 输出是星期一到日的 `Weekday` 也是支持的。
p(then.Weekday())
// 这些方法来比较两个时间,分别测试一下是否是之前,之后或者是同一时刻,精确到秒。
p(then.Before(now))
p(then.After(now))
p(then.Equal(now))
// 方法 `Sub` 返回一个 `Duration` 来表示两个时间点的间隔时间。
diff := now.Sub(then)
p(diff)
// 我们计算出不同单位下的时间长度值。
p(diff.Hours())
p(diff.Minutes())
p(diff.Seconds())
p(diff.Nanoseconds())
// 你可以使用 `Add` 将时间后移一个时间间隔,或者使用一个 `-` 来将时间前移一个时间间隔。
p(then.Add(diff))
p(then.Add(-diff))
}
timer = time.NewTimer(time.Second * 2)
go func() {
<-timer.C // 等待信号
// xxx
}()
timer.Stop() // 关掉定时器
间隔一段时间发一个信号。
ticker := time.NewTicker(time.Millisecond * 500)
go func() {
for t := range.ticker.C {
fmt.Println("Tick at ", t)
}
}()
ticker.Stop()
func main() {
// 首先我们将看一下基本的速率限制。假设我们想限制我们
// 接收请求的处理,我们将这些请求发送给一个相同的通道。
requests := make(chan int, 5)
for i := 1; i <= 5; i++ {
requests <- i
}
close(requests)
// 这个 `limiter` 通道将每 200ms 接收一个值。这个是
// 速率限制任务中的管理器。
limiter := time.Tick(time.Millisecond * 200)
// 通过在每次请求前阻塞 `limiter` 通道的一个接收,我们限制
// 自己每 200ms 执行一次请求。
for req := range requests {
<-limiter
fmt.Println("request", req, time.Now())
}
// 有时候我们想临时进行速率限制,并且不影响整体的速率控制,
// 我们可以通过[通道缓冲](channel-buffering.html)来实现。
// 这个 `burstyLimiter` 通道用来进行 3 次临时的脉冲型速率限制。
burstyLimiter := make(chan time.Time, 3)
// 想将通道填充需要临时改变3次的值,做好准备。
for i := 0; i < 3; i++ {
burstyLimiter <- time.Now()
}
// 每 200 ms 我们将添加一个新的值到 `burstyLimiter`中,
// 直到达到 3 个的限制。
go func() {
for t := range time.Tick(time.Millisecond * 200) {
burstyLimiter <- t
}
}()
// 现在模拟超过 5 个的接入请求。它们中刚开始的 3 个将
// 由于受 `burstyLimiter` 的“脉冲”影响。
burstyRequests := make(chan int, 5)
for i := 1; i <= 5; i++ {
burstyRequests <- i
}
close(burstyRequests)
for req := range burstyRequests {
<-burstyLimiter
fmt.Println("request", req, time.Now())
}
}
import b64 "encoding/base64"
import "fmt"
func main() {
// 这是将要编解码的字符串。
data := "abc123!?$*&()'-=@~"
// Go 同时支持标准的和 URL 兼容的 base64 格式。编码需要
// 使用 `[]byte` 类型的参数,所以要将字符串转成此类型。
sEnc := b64.StdEncoding.EncodeToString([]byte(data))
fmt.Println(sEnc)
// 解码可能会返回错误,如果不确定输入信息格式是否正确,
// 那么,你就需要进行错误检查了。
sDec, _ := b64.StdEncoding.DecodeString(sEnc)
fmt.Println(string(sDec))
fmt.Println()
// 使用 URL 兼容的 base64 格式进行编解码。
uEnc := b64.URLEncoding.EncodeToString([]byte(data))
fmt.Println(uEnc)
uDec, _ := b64.URLEncoding.DecodeString(uEnc)
fmt.Println(string(uDec))
}
type IntHeap []int
func (h IntHeap) Len() int { return len(h) }
func (h IntHeap) Less(i, j int) bool { return h[i] < h[j] }
func (h IntHeap) Swap(i, j int) { h[i], h[j] = h[j], h[i] }
func (h *IntHeap) Push(x interface{}) {
*h = append(*h, x.(int))
}
func (h *IntHeap) Pop() interface{} {
old := *h
n := len(old)
x := old[n-1]
*h = old[0 : n-1]
return x
}
h := &IntHeap{2, 1, 5}
heap.Init(h)
heap.Push(h, 3)
heap.Pop(h)
type Element struct {
next, prev *Element // 上一个元素和下一个元素
list *List // 元素所在链表
Value interface{} // 元素
}
type List struct {
root Element // 链表的根元素
len int // 链表的长度
}
package main
import (
"container/list"
"fmt"
)
func main() {
list := list.New()
list.PushBack(1)
list.PushBack(2)
fmt.Printf("len: %v\n", list.Len())
fmt.Printf("first: %#v\n", list.Front())
fmt.Printf("second: %#v\n", list.Front().Next())
}
type Element
func (e *Element) Next() *Element
func (e *Element) Prev() *Element
type List
func New() *List
func (l *List) Back() *Element // 最后一个元素
func (l *List) Front() *Element // 第一个元素
func (l *List) Init() *List // 链表初始化
func (l *List) InsertAfter(v interface{}, mark *Element) *Element // 在某个元素后插入
func (l *List) InsertBefore(v interface{}, mark *Element) *Element // 在某个元素前插入
func (l *List) Len() int // 在链表长度
func (l *List) MoveAfter(e, mark *Element) // 把 e 元素移动到 mark 之后
func (l *List) MoveBefore(e, mark *Element) // 把 e 元素移动到 mark 之前
func (l *List) MoveToBack(e *Element) // 把 e 元素移动到队列最后
func (l *List) MoveToFront(e *Element) // 把 e 元素移动到队列最头部
func (l *List) PushBack(v interface{}) *Element // 在队列最后插入元素
func (l *List) PushBackList(other *List) // 在队列最后插入接上新队列
func (l *List) PushFront(v interface{}) *Element // 在队列头部插入元素
func (l *List) PushFrontList(other *List) // 在队列头部插入接上新队列
func (l *List) Remove(e *Element) interface{} // 删除某个元素
type Ring struct {
next, prev *Ring
Value interface{}
}
package main
import (
"container/ring"
"fmt"
)
func main() {
ring := ring.New(3)
for i := 1; i <= 3; i++ {
ring.Value = i
ring = ring.Next()
}
// 计算 1+2+3
s := 0
ring.Do(func(p interface{}){
s += p.(int)
})
fmt.Println("sum is", s)
}
type Ring
func New(n int) *Ring // 初始化环
func (r *Ring) Do(f func(interface{})) // 循环环进行操作
func (r *Ring) Len() int // 环长度
func (r *Ring) Link(s *Ring) *Ring // 连接两个环
func (r *Ring) Move(n int) *Ring // 指针从当前元素开始向后移动或者向前(n 可以为负数)
func (r *Ring) Next() *Ring // 当前元素的下个元素
func (r *Ring) Prev() *Ring // 当前元素的上个元素
func (r *Ring) Unlink(n int) *Ring // 从当前元素开始,删除 n 个元素
package main
import "bytes"
import "fmt"
import "regexp"
func main() {
// 这个测试一个字符串是否符合一个表达式。
match, _ := regexp.MatchString("p([a-z]+)ch", "peach")
fmt.Println(match)
// 上面我们是直接使用字符串,但是对于一些其他的正则任
// 务,你需要 `Compile` 一个优化的 `Regexp` 结构体。
r, _ := regexp.Compile("p([a-z]+)ch")
// 这个结构体有很多方法。这里是类似我们前面看到的一个
// 匹配测试。
fmt.Println(r.MatchString("peach"))
// 这是查找匹配字符串的。
fmt.Println(r.FindString("peach punch"))
// 这个也是查找第一次匹配的字符串的,但是返回的匹配开
// 始和结束位置索引,而不是匹配的内容。
fmt.Println(r.FindStringIndex("peach punch"))
// `Submatch` 返回完全匹配和局部匹配的字符串。例如,
// 这里会返回 `p([a-z]+)ch` 和 `([a-z]+) 的信息。
fmt.Println(r.FindStringSubmatch("peach punch"))
// 类似的,这个会返回完全匹配和局部匹配的索引位置。
fmt.Println(r.FindStringSubmatchIndex("peach punch"))
// 带 `All` 的这个函数返回所有的匹配项,而不仅仅是首
// 次匹配项。例如查找匹配表达式的所有项。
fmt.Println(r.FindAllString("peach punch pinch", -1))
// `All` 同样可以对应到上面的所有函数。
fmt.Println(r.FindAllStringSubmatchIndex(
"peach punch pinch", -1))
// 这个函数提供一个正整数来限制匹配次数。
fmt.Println(r.FindAllString("peach punch pinch", 2))
// 上面的例子中,我们使用了字符串作为参数,并使用了
// 如 `MatchString` 这样的方法。我们也可以提供 `[]byte`
// 参数并将 `String` 从函数命中去掉。
fmt.Println(r.Match([]byte("peach")))
// 创建正则表达式常量时,可以使用 `Compile` 的变体
// `MustCompile` 。因为 `Compile` 返回两个值,不能用于常量。
r = regexp.MustCompile("p([a-z]+)ch")
fmt.Println(r)
// `regexp` 包也可以用来替换部分字符串为其他值。
fmt.Println(r.ReplaceAllString("a peach", "<fruit>"))
// `Func` 变量允许传递匹配内容到一个给定的函数中,
in := []byte("a peach")
out := r.ReplaceAllFunc(in, bytes.ToUpper)
fmt.Println(string(out))
}
func Compare(a, b string) int
func Contains(s, substr string) bool
func ContainsAny(s, chars string) bool
func ContainsRune(s string, r rune) bool
func Count(s, substr string) int
func EqualFold(s, t string) bool
func Fields(s string) []string
func FieldsFunc(s string, f func(rune) bool) []string
func HasPrefix(s, prefix string) bool
func HasSuffix(s, suffix string) bool
func Index(s, substr string) int
func IndexAny(s, chars string) int
func IndexByte(s string, c byte) int
func IndexFunc(s string, f func(rune) bool) int
func IndexRune(s string, r rune) int
func Join(a []string, sep string) string
func LastIndex(s, substr string) int
func LastIndexAny(s, chars string) int
func LastIndexByte(s string, c byte) int
func LastIndexFunc(s string, f func(rune) bool) int
func Map(mapping func(rune) rune, s string) string
func Repeat(s string, count int) string
func Replace(s, old, new string, n int) string
func ReplaceAll(s, old, new string) string
func Split(s, sep string) []string
func SplitAfter(s, sep string) []string
func SplitAfterN(s, sep string, n int) []string
func SplitN(s, sep string, n int) []string
func Title(s string) string
func ToLower(s string) string
func ToLowerSpecial(c unicode.SpecialCase, s string) string
func ToTitle(s string) string
func ToTitleSpecial(c unicode.SpecialCase, s string) string
func ToUpper(s string) string
func ToUpperSpecial(c unicode.SpecialCase, s string) string
func ToValidUTF8(s, replacement string) string
func Trim(s string, cutset string) string
func TrimFunc(s string, f func(rune) bool) string
func TrimLeft(s string, cutset string) string
func TrimLeftFunc(s string, f func(rune) bool) string
func TrimPrefix(s, prefix string) string
func TrimRight(s string, cutset string) string
func TrimRightFunc(s string, f func(rune) bool) string
func TrimSpace(s string) string
func TrimSuffix(s, suffix string) string
type Builder
func (b *Builder) Cap() int
func (b *Builder) Grow(n int)
func (b *Builder) Len() int
func (b *Builder) Reset()
func (b *Builder) String() string
func (b *Builder) Write(p []byte) (int, error)
func (b *Builder) WriteByte(c byte) error
func (b *Builder) WriteRune(r rune) (int, error)
func (b *Builder) WriteString(s string) (int, error)
type Reader
func NewReader(s string) *Reader
func (r *Reader) Len() int
func (r *Reader) Read(b []byte) (n int, err error)
func (r *Reader) ReadAt(b []byte, off int64) (n int, err error)
func (r *Reader) ReadByte() (byte, error)
func (r *Reader) ReadRune() (ch rune, size int, err error)
func (r *Reader) Reset(s string)
func (r *Reader) Seek(offset int64, whence int) (int64, error)
func (r *Reader) Size() int64
func (r *Reader) UnreadByte() error
func (r *Reader) UnreadRune() error
func (r *Reader) WriteTo(w io.Writer) (n int64, err error)
type Replacer
func NewReplacer(oldnew ...string) *Replacer
func (r *Replacer) Replace(s string) string
func (r *Replacer) WriteString(w io.Writer, s string) (n int, err error)
操作 byte slice
func Compare(a, b []byte) int
func Contains(b, subslice []byte) bool
func ContainsAny(b []byte, chars string) bool
func ContainsRune(b []byte, r rune) bool
func Count(s, sep []byte) int
func Equal(a, b []byte) bool
func EqualFold(s, t []byte) bool
func Fields(s []byte) [][]byte
func FieldsFunc(s []byte, f func(rune) bool) [][]byte
func HasPrefix(s, prefix []byte) bool
func HasSuffix(s, suffix []byte) bool
func Index(s, sep []byte) int
func IndexAny(s []byte, chars string) int
func IndexByte(b []byte, c byte) int
func IndexFunc(s []byte, f func(r rune) bool) int
func IndexRune(s []byte, r rune) int
func Join(s [][]byte, sep []byte) []byte
func LastIndex(s, sep []byte) int
func LastIndexAny(s []byte, chars string) int
func LastIndexByte(s []byte, c byte) int
func LastIndexFunc(s []byte, f func(r rune) bool) int
func Map(mapping func(r rune) rune, s []byte) []byte
func Repeat(b []byte, count int) []byte
func Replace(s, old, new []byte, n int) []byte
func ReplaceAll(s, old, new []byte) []byte
func Runes(s []byte) []rune
func Split(s, sep []byte) [][]byte
func SplitAfter(s, sep []byte) [][]byte
func SplitAfterN(s, sep []byte, n int) [][]byte
func SplitN(s, sep []byte, n int) [][]byte
func Title(s []byte) []byte
func ToLower(s []byte) []byte
func ToLowerSpecial(c unicode.SpecialCase, s []byte) []byte
func ToTitle(s []byte) []byte
func ToTitleSpecial(c unicode.SpecialCase, s []byte) []byte
func ToUpper(s []byte) []byte
func ToUpperSpecial(c unicode.SpecialCase, s []byte) []byte
func ToValidUTF8(s, replacement []byte) []byte
func Trim(s []byte, cutset string) []byte
func TrimFunc(s []byte, f func(r rune) bool) []byte
func TrimLeft(s []byte, cutset string) []byte
func TrimLeftFunc(s []byte, f func(r rune) bool) []byte
func TrimPrefix(s, prefix []byte) []byte
func TrimRight(s []byte, cutset string) []byte
func TrimRightFunc(s []byte, f func(r rune) bool) []byte
func TrimSpace(s []byte) []byte
func TrimSuffix(s, suffix []byte) []byte
type Buffer
func NewBuffer(buf []byte) *Buffer
func NewBufferString(s string) *Buffer
func (b *Buffer) Bytes() []byte
func (b *Buffer) Cap() int
func (b *Buffer) Grow(n int)
func (b *Buffer) Len() int
func (b *Buffer) Next(n int) []byte
func (b *Buffer) Read(p []byte) (n int, err error)
func (b *Buffer) ReadByte() (byte, error)
func (b *Buffer) ReadBytes(delim byte) (line []byte, err error)
func (b *Buffer) ReadFrom(r io.Reader) (n int64, err error)
func (b *Buffer) ReadRune() (r rune, size int, err error)
func (b *Buffer) ReadString(delim byte) (line string, err error)
func (b *Buffer) Reset()
func (b *Buffer) String() string
func (b *Buffer) Truncate(n int)
func (b *Buffer) UnreadByte() error
func (b *Buffer) UnreadRune() error
func (b *Buffer) Write(p []byte) (n int, err error)
func (b *Buffer) WriteByte(c byte) error
func (b *Buffer) WriteRune(r rune) (n int, err error)
func (b *Buffer) WriteString(s string) (n int, err error)
func (b *Buffer) WriteTo(w io.Writer) (n int64, err error)
type Reader
func NewReader(b []byte) *Reader
func (r *Reader) Len() int
func (r *Reader) Read(b []byte) (n int, err error)
func (r *Reader) ReadAt(b []byte, off int64) (n int, err error)
func (r *Reader) ReadByte() (byte, error)
func (r *Reader) ReadRune() (ch rune, size int, err error)
func (r *Reader) Reset(b []byte)
func (r *Reader) Seek(offset int64, whence int) (int64, error)
func (r *Reader) Size() int64
func (r *Reader) UnreadByte() error
func (r *Reader) UnreadRune() error
func (r *Reader) WriteTo(w io.Writer) (n int64, err error)
func AppendBool(dst []byte, b bool) []byte
func AppendFloat(dst []byte, f float64, fmt byte, prec, bitSize int) []byte
func AppendInt(dst []byte, i int64, base int) []byte
func AppendQuote(dst []byte, s string) []byte
func AppendQuoteRune(dst []byte, r rune) []byte
func AppendQuoteRuneToASCII(dst []byte, r rune) []byte
func AppendQuoteRuneToGraphic(dst []byte, r rune) []byte
func AppendQuoteToASCII(dst []byte, s string) []byte
func AppendQuoteToGraphic(dst []byte, s string) []byte
func AppendUint(dst []byte, i uint64, base int) []byte
func Atoi(s string) (int, error)
func CanBackquote(s string) bool
func FormatBool(b bool) string
func FormatFloat(f float64, fmt byte, prec, bitSize int) string
func FormatInt(i int64, base int) string
func FormatUint(i uint64, base int) string
func IsGraphic(r rune) bool
func IsPrint(r rune) bool
func Itoa(i int) string
func ParseBool(str string) (bool, error)
func ParseFloat(s string, bitSize int) (float64, error)
func ParseInt(s string, base int, bitSize int) (i int64, err error)
func ParseUint(s string, base int, bitSize int) (uint64, error)
func Quote(s string) string
func QuoteRune(r rune) string
func QuoteRuneToASCII(r rune) string
func QuoteRuneToGraphic(r rune) string
func QuoteToASCII(s string) string
func QuoteToGraphic(s string) string
func Unquote(s string) (string, error)
func UnquoteChar(s string, quote byte) (value rune, multibyte bool, tail string, err error)
type NumError
func (e *NumError) Error() string
简单分布式爬虫,爬取相亲网站资料
1)sql2go 用于将 sql 语句转换为 golang 的 struct. 使用 ddl 语句即可。 例如对于创建表的语句: show create table xxx. 将输出的语句,直接粘贴进去就行。 http://stming.cn/tool/sql2go.html
2)toml2go 用于将编码后的 toml 文本转换问 golang 的 struct. https://xuri.me/toml-to-go/
3)curl2go 用来将 curl 命令转化为具体的 golang 代码. https://mholt.github.io/curl-to-go/
4)json2go 用于将 json 文本转换为 struct. https://mholt.github.io/json-to-go/
5) mysql 转 ES 工具 http://www.ischoolbar.com/EsParser/
6)golang 模拟模板的工具,在支持泛型之前,可以考虑使用。 https://github.com/cheekybits/genny
7) 查看某一个库的依赖情况,类似于 go list 功能 https://github.com/KyleBanks/depth
8) 一个好用的文件压缩和解压工具,集成了 zip,tar 等多种功能,主要还有跨平台。 https://github.com/mholt/archiver
9) go 内置命令 go list 可以查看某一个包的依赖关系. go vet 可以检查代码不符合 golang 规范的地方。
10) 热编译工具 https://github.com/silenceper/gowatch
11)revive golang 代码质量检测工具 https://github.com/mgechev/revive
12)Go Callvis golang 的代码调用链图工具 https://github.com/TrueFurby/go-callvis
13)Realize 开发流程改进工具 https://github.com/oxequa/realize
14)Gotests 自动生成测试用例工具 https://github.com/cweill/gotests
1)perf 代理工具,支持内存,cpu,堆栈查看,并支持火焰图. perf 工具和 go-torch 工具,快捷定位程序问题. https://github.com/uber-archive/go-torch https://github.com/google/gops
2) dlv 远程调试 基于 goland+dlv 可以实现远程调式的能力. https://github.com/go-delve/delve 提供了对 golang 原生的支持,相比 gdb 调试,简单太多。
3) 网络代理工具 goproxy 代理,支持多种协议,支持 ssh 穿透和 kcp 协议. https://github.com/snail007/goproxy
4) 抓包工具 go-sniffer 工具,可扩展的抓包工具,可以开发自定义协议的工具包。现在只支持了 http,mysql,redis,mongodb. 基于这个工具,我们开发了 qapp 协议的抓包。 https://github.com/40t/go-sniffer
5) 反向代理工具,快捷开放内网端口供外部使用。 ngrok 可以让内网服务外部调用 https://ngrok.com/ https://github.com/inconshreveable/ngrok
6) 配置化生成证书 从根证书,到业务侧证书一键生成. https://github.com/cloudflare/cfssl
7) 免费的证书获取工具 基于 acme 协议,从 letsencrypt 生成免费的证书,有效期 1 年,可自动续期。 https://github.com/Neilpang/acme.sh
8) 开发环境管理工具,单机搭建可移植工具的利器。支持多种虚拟机后端。 vagrant 常被拿来同 docker 相比,值得拥有。 https://github.com/hashicorp/vagrant
9) 轻量级容器调度工具 nomad 可以非常方便的管理容器和传统应用,相比 k8s 来说,简单不要太多. https://github.com/hashicorp/nomad
10) 敏感信息和密钥管理工具 https://github.com/hashicorp/vault
11) 高度可配置化的 http 转发工具,基于 etcd 配置。 https://github.com/gojek/weaver
12) 进程监控工具 supervisor https://www.jianshu.com/p/39b476e808d8
13) 基于 procFile 进程管理工具。相比 supervisor 更加简单。 https://github.com/ddollar/foreman
14) 基于 http,https,websocket 的调试代理工具,配置功能丰富。在线教育的 nohost web 调试工具,基于此开发. https://github.com/avwo/whistle
15) 分布式调度工具 https://github.com/shunfei/cronsun/blob/master/README_ZH.md https://github.com/ouqiang/gocron
16) 自动化运维平台 Gaia https://github.com/gaia-pipeline/gaia
go 百科全书: https://awesome-go.com/
json 解析: https://www.json.cn/
出口 IP: https://ipinfo.io/
redis 命令: http://doc.redisfans.com/
ES 命令首页: https://www.elastic.co/guide/cn/elasticsearch/guide/current/index.html
UrlEncode: http://tool.chinaz.com/Tools/urlencode.aspx
Base64: https://tool.oschina.net/encrypt?type=3
Guid: https://www.guidgen.com/
常用工具: http://www.ofmonkey.com/
日志 https://github.com/Sirupsen/logrus https://github.com/uber-go/zap
配置 兼容 json,toml,yaml,hcl 等格式的日志库. https://github.com/spf13/viper
存储 mysql: https://github.com/go-xorm/xorm es: https://github.com/elastic/elasticsearch redis: https://github.com/gomodule/redigo mongo: https://github.com/mongodb/mongo-go-driver kafka: https://github.com/Shopify/sarama
数据结构 https://github.com/emirpasic/gods
命令行 https://github.com/spf13/cobra
框架 https://github.com/grpc/grpc-go https://github.com/gin-gonic/gin
并发 https://github.com/Jeffail/tunny https://github.com/benmanns/goworker 现在我们框架在用的,虽然 star 不多,但是确实好用,当然还可以更好用. https://github.com/rafaeldias/async
工具 定义了实用的判定类,以及针对结构体的校验逻辑,避免业务侧写复杂的代码. https://github.com/asaskevich/govalidator https://github.com/bytedance/go-tagexpr
protobuf 文件动态解析的接口,可以实现反射相关的能力。 https://github.com/jhump/protoreflect
表达式引擎工具 https://github.com/Knetic/govaluate https://github.com/google/cel-go
字符串处理 https://github.com/huandu/xstrings
ratelimit 工具 https://github.com/uber-go/ratelimit https://blog.csdn.net/chenchongg/article/details/85342086 https://github.com/juju/ratelimit
golang 熔断的库 熔断除了考虑频率限制,还要考虑 qps,出错率等其他东西. https://github.com/afex/hystrix-go https://github.com/sony/gobreaker
表格 https://github.com/chenjiandongx/go-echarts
tail 工具库 https://github.com/hpcloud/taglshi
Effective Go ,Go 的语法不复杂,所以,Go 语言的最佳实践只需要看这篇官方文档就够了。
Go 语言比较常见并且使用广泛的代码规范就是官方提供的 Go Code Review Comments,无论你是短期还是长期使用 Go 语言编程,都应该至少完整地阅读一遍这个官方的代码规范指南,它既是我们在写代码时应该遵守的规则,也是在代码审查时需要注意的规范。
goimports 是 Go 语言官方提供的工具,它能够为我们自动格式化 Go 语言代码并对所有引入的包进行管理,包括自动增删依赖的包引用、将依赖包按字母序排序并分类。相信很多人使用的 IDE 都会将另一个官方提供的工具 gofmt 对代码进行格式化,而 goimports
就是等于 gofmt
加上依赖包管理。
在基础库或者框架中使用 golint
进行静态检查(或者同时使用 golint
和 golangci-lint),在其他的项目中使用可定制化的 golangci-lint
来进行静态检查,因为在基础库和框架中施加强限制对于整体的代码质量有着更大的收益。
https://github.com/golang-standards/project-layout
├── LICENSE.md
├── Makefile
├── README.md
├── api
├── assets
├── build
├── cmd
├── configs
├── deployments
├── docs
├── examples
├── githooks
├── init
├── internal
├── pkg
├── scripts
├── test
├── third_party
├── tools
├── vendor
├── web
└── website
go doc json // 输出 json 包对应的文档
GOOS=darwin GOARCH=amd64 go build
GOOS=windows GOARCH=amd64 go build
export http_proxy=socks5://127.0.0.1:1080
GitHub 上有官方镜像库,如 https://github.com/golang/net 即 https://golang.org/x/net
git clone https://github.com/golang/tools.git
# 然后进对应目录 go install,bin 目录就有了。
go install github/xxx/xxx
# go mod 优先使用镜像
go env -w GOPROXY=https://mirrors.aliyun.com/goproxy/
package main
import "time"
import "fmt"
import "math/rand"
func main() {
// 例如,`rand.Intn` 返回一个随机的整数 n,`0 <= n <= 100`。
fmt.Print(rand.Intn(100), ",")
fmt.Print(rand.Intn(100))
fmt.Println()
// `rand.Float64` 返回一个64位浮点数 `f`,`0.0 <= f <= 1.0`。
fmt.Println(rand.Float64())
// 这个技巧可以用来生成其他范围的随机浮点数,例如 `5.0 <= f <= 10.0`
fmt.Print((rand.Float64()*5)+5, ",")
fmt.Print((rand.Float64() * 5) + 5)
fmt.Println()
// 默认情况下,给定的种子是确定的,每次都会产生相同的随机数数字序列。要产生变化的序列,需要给定一个变化的种子。
// 需要注意的是,如果你出于加密目的,需要使用随机数的话,请使用 `crypto/rand` 包,此方法不够安全。
s1 := rand.NewSource(time.Now().UnixNano())
r1 := rand.New(s1)
// 调用上面返回的 `rand.Source` 的函数和调用 `rand` 包中函数是相同的。
fmt.Print(r1.Intn(100), ",")
fmt.Print(r1.Intn(100))
fmt.Println()
// 如果使用相同的种子生成的随机数生成器,将会产生相同的随机数序列。
s2 := rand.NewSource(42)
r2 := rand.New(s2)
fmt.Print(r2.Intn(100), ",")
fmt.Print(r2.Intn(100))
fmt.Println()
s3 := rand.NewSource(42)
r3 := rand.New(s3)
fmt.Print(r3.Intn(100), ",")
fmt.Print(r3.Intn(100))
}
const MaxUint = ^uint(0)
const MaxInt = int(^uint(0) >> 1)
这个技巧和Go无关。
const Is64bitArch = ^uint(0) >> 63 == 1
const Is32bitArch = ^uint(0) >> 63 == 0
const WordBits = 32 << (^uint(0) >> 63) // 64或32
当您在分析和调试复杂的程序时,无数个函数在不同的代码文件中相互调用,如果这时候能够准确地知道哪个文件中的具体哪个函数正在执行,对于调试是十分有帮助的。您可以使用 runtime
或 log
包中的特殊函数来实现这样的功能。包 runtime
中的函数 Caller()
提供了相应的信息,因此可以在需要的时候实现一个 where()
闭包函数来打印函数执行的位置:
where := func() {
_, file, line, _ := runtime.Caller(1)
log.Printf("%s:%d", file, line)
}
where()
// some code
where()
// some more code
where()
log.SetFlags(log.Llongfile)
log.Print("")
var where = log.Print
func func1() {
where()
... some code
where()
... some code
where()
}