├── .gitignore
├── Go 学习路线(2022).md
├── LICENSE
├── README.md
├── advanced
├── 000-开始读 Go 源码了.md
├── 001-如何在 Go 中将 []byte 转换为 io.Reader?.md
├── 002-为什么要避免在 Go 中使用 ioutil.ReadAll?.md
├── 003-Go Error 嵌套到底是怎么实现的?.md
├── 004-读 Go 源码,可以试试这个工具.md
├── example
│ ├── 001_byteslice_to_ioreader.go
│ ├── 001_io_reader.go
│ ├── 001_io_writer.go
│ ├── 001_ioreader_to_byteslice.go
│ ├── 002_nopcloser.go
│ ├── 002_readdir.go
│ ├── 002_readfile.go
│ ├── 002_tempdir.go
│ ├── 002_tempfile.go
│ ├── 002_writefile.go
│ ├── 003_errors.go
│ ├── 003_fmt_error.go
│ ├── 003_wrap_error.go
│ ├── 003_wrap_error_1.go
│ ├── go_ping.go
│ ├── icmp_ping.go
│ └── text.txt
├── pic
│ ├── 001-byte-slice-to-io-reader.png
│ ├── 001-io-bytes-strings.png
│ ├── 001-io-reader-writer.png
│ ├── 001-io-reader.png
│ ├── 001-io-writer.png
│ ├── 002-ioutil-readall-github.png
│ ├── 003-errors.png
│ ├── 004-dumels-1.png
│ ├── 004-dumels-2.png
│ └── 004-dumels.png
└── uml
│ ├── errors.drawio
│ ├── io-bytes-strings.drawio
│ ├── io-reader-writer.drawio
│ ├── io-reader.drawio
│ └── io-writer.drawio
├── alipay.jpg
├── alwaysbeta.JPG
├── blog
├── .DS_Store
├── 01-推荐三个实用的 Go 开发工具.md
├── 02-使用 grpcurl 通过命令行访问 gRPC 服务.md
├── 03-gRPC,爆赞.md
├── 04-测试小姐姐问我 gRPC 怎么用,我直接把这篇文章甩给了她.md
├── 05-开源项目|Go 开发的一款分布式唯一 ID 生成系统.md
├── example
│ └── grpc-hello
│ │ ├── go.mod
│ │ ├── go.sum
│ │ ├── main.go
│ │ └── proto
│ │ ├── helloworld.pb.go
│ │ ├── helloworld.proto
│ │ └── make.sh
├── pic
│ ├── 01-json-to-struct.png
│ ├── 01-yaml-to-struct.png
│ ├── 01-实用工具.png
│ ├── 02-grpc-gui-client.png
│ ├── 03-grpc.png
│ ├── 05-go-clean-template-1.png
│ ├── 05-go-clean-template-2.png
│ └── 05-id-maker.png
└── 使用 Go 语言实现二叉搜索树.md
├── example
├── go_set_test.go
└── result.txt
├── go-zero
├── go-zero 是如何做路由管理的?.md
├── go-zero 是如何实现令牌桶限流的?.md
└── go-zero 是如何实现计数器限流的?.md
├── go.mod
├── go.sum
├── interview
├── Go 语言 context 都能做什么?.md
├── Go 语言 map 如何顺序读取?.md
├── Go 语言 map 是并发安全的吗?.md
├── Go 语言 new 和 make 关键字的区别.md
├── Go 语言 select 都能做什么?.md
├── Go 语言切片是如何扩容的?.md
├── Go 语言数组和切片的区别.md
├── go-gpm.md
├── go-scheduler-base.md
├── go-scheduler.md
├── q001.md
├── q002.md
├── q003.md
├── q004.md
├── q005.md
├── q006.md
├── q007.md
├── q008.md
├── q009.md
├── q010.md
├── q011.md
├── q012.md
├── q013.md
├── q014.md
├── q015.md
├── q016.md
├── q017.md
├── q018.md
├── q019.md
├── q020.md
├── q021.md
└── q022.md
├── pic
├── Go Web 开发.png
├── Go 书籍推荐.png
├── Go 入门教程.png
├── Go 学习路线.png
├── Go 实用工具.png
├── Go 技术社区.png
├── Go 练手项目.png
├── Go 进阶教程.png
├── biancheng.png
├── draveness.png
├── eddycjy.png
├── gobook-1.png
├── gobook-2.png
├── gobook-3.png
├── gobook-4.png
├── gobook-5.png
├── gobook-6.png
├── gobook-7.png
├── gobook-8.png
├── gobyexample.png
├── mojitv.png
├── playground.png
├── rego.png
├── runoob.png
├── topgoer.png
└── w3c.png
├── sc
├── 00-开发环境搭建以及开发工具 VS Code 配置.md
├── 01-变量和常量的声明与赋值.md
├── 02-基础数据类型:整数、浮点数、复数、布尔值和字符串.md
├── 03-复合数据类型:数组和切片 slice.md
├── 04-复合数据类型:字典 map 和 结构体 struct.md
├── 05-流程控制,一网打尽.md
├── 06-函数那些事.md
├── 07-错误处理:defer,panic 和 recover.md
├── 08-说说方法.md
├── 09-接口 interface.md
├── 10-并发编程:goroutine,channel 和 sync.md
├── 11-听说,99% 的 Go 程序员都被 defer 坑过.md
├── example
│ ├── 00_hello.go
│ ├── 01_constant.go
│ ├── 01_iota.go
│ ├── 01_variables.go
│ ├── 02_basic_type.go
│ ├── 03_array.go
│ ├── 03_slice.go
│ ├── 04_map.go
│ ├── 04_struct.go
│ ├── 05_for.go
│ ├── 05_goto.go
│ ├── 05_if_else.go
│ ├── 05_switch.go
│ ├── 06_func.go
│ ├── 07_defer.go
│ ├── 07_error.go
│ ├── 07_panic_recover.go
│ ├── 08_method.go
│ ├── 09_interface.go
│ ├── 10_channel.go
│ ├── 10_goroutine.go
│ ├── 10_sync_mutex.go
│ ├── 10_sync_rwmutex.go
│ └── 11_defer.go
└── pic
│ ├── .DS_Store
│ ├── 00_GO安装与配置.png
│ ├── 00_go_dl.png
│ ├── 00_mac_install_go.png
│ ├── 00_vs_code_install_go.png
│ ├── 01_变量和常量.png
│ ├── 02_基础数据类型.png
│ ├── 03_slice_00.drawio
│ ├── 03_slice_00.png
│ ├── 03_slice_01.drawio
│ ├── 03_slice_01.png
│ ├── 03_数组和切片.png
│ ├── 04_字典和结构体.png
│ ├── 05_流程控制.png
│ ├── 06_函数.png
│ ├── 07_错误处理.png
│ ├── 08_方法.png
│ ├── 09_接口interface.png
│ └── 10_并发编程.png
├── weixinpay.jpg
├── whygo
├── 为什么 Go for-range 的 value 值地址每次都一样?.md
├── 为什么 Go 不支持 []T 转换为 []interface.md
├── 为什么 Go 语言 struct 要使用 tags.md
└── 为什么说 Go 语言字符串是不可变的?.md
└── xmind
├── Go Web 开发.xmind
├── Go 书籍推荐.xmind
├── Go 入门教程.xmind
├── Go 学习路线.xmind
├── Go 实用工具.xmind
├── Go 技术社区.xmind
├── Go 练手项目.xmind
└── Go 进阶教程.xmind
/.gitignore:
--------------------------------------------------------------------------------
1 | # Binaries for programs and plugins
2 | *.exe
3 | *.exe~
4 | *.dll
5 | *.so
6 | *.dylib
7 |
8 | # Test binary, built with `go test -c`
9 | *.test
10 |
11 | # Output of the go coverage tool, specifically when used with LiteIDE
12 | *.out
13 |
14 | # Dependency directories (remove the comment below to include it)
15 | # vendor/
16 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2021 yongxinz
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/advanced/000-开始读 Go 源码了.md:
--------------------------------------------------------------------------------
1 | **原文链接:** [开始读 Go 源码了](https://mp.weixin.qq.com/s/iPM-mPOepRuDqkBtcnG1ww)
2 |
3 | 学完 Go 的基础知识已经有一段时间了,那么接下来应该学什么呢?有几个方向可以考虑,比如说 Web 开发,网络编程等。
4 |
5 | 在下一阶段的学习之前,写了一个[开源项目|Go 开发的一款分布式唯一 ID 生成系统](https://mp.weixin.qq.com/s/tCGYTlB4nJH1ClViFQJ6Cw),如果你对这个项目感兴趣的话,可以在 [GitHub](https://github.com/yongxinz/id-maker) 上拿到源码。
6 |
7 | 在写项目的过程中,发现一个问题。实现功能是没问题的,但不知道自己写的代码是不是符合 Go 的风格,是不是够优雅。所以我觉得相比于继续学习应用开发,不如向底层前进,打好基础,打好写 Go 代码的基础。
8 |
9 | 所以,我决定开始读 Go 标准库源码,Go 一共有 150+ 标准库,想要全部读完的话不是不可能,但绝对是一项大工程,希望自己能坚持下去。
10 |
11 | 为什么从 Go 标准库的源码开始读呢?因为最近也看了一些 Go 底层原理的书,说实话,像 goroutine 调度,gc 垃圾回收这些内容,根本就看不懂。这要是一上来就读这部分代码,恐怕直接就放弃 Go 语言学习了。
12 |
13 | 而标准库就不一样了,有一部分代码根本不涉及底层原理,实现也相对简单,同时又能对 Go 的理念加深理解,作为入门再好不过了。然后再由简入深,循序渐进,就像打怪升级一样,一步一步征服 Go。
14 |
15 | 说了这么多,那到底应该怎么读呢?我想到了一些方法:
16 |
17 | - 看官方标准库文档。
18 | - 看网上其他人的技术文章。
19 | - 写一些例子来练习如何使用。
20 | - 如果可以的话,自己实现标准库的功能。
21 | - 将自己的阅读心得总结输出。
22 |
23 | 可以通过上面的一种或几种方法相结合,然后再不断阅读不断总结,最终找到一个完全适合自己的方法。
24 |
25 | 下面是我总结的一些标准库及功能介绍:
26 |
27 | - `archive/tar` 和 `/zip-compress`:压缩(解压缩)文件功能。
28 | - `fmt`-`io`-`bufio`-`path/filepath`-`flag`:
29 | - `fmt`:提供格式化输入输出功能。
30 | - `io`:提供基本输入输出功能,大多数是围绕系统功能的封装。
31 | - `bufio`:缓冲输入输出功能的封装。
32 | - `path/filepath`:用来操作在当前系统中的目标文件名路径。
33 | - `flag`:提供对命令行参数的操作。
34 | - `strings`-`strconv`-`unicode`-`regexp`-`bytes`:
35 | - `strings`:提供对字符串的操作。
36 | - `strconv`:提供将字符串转换为基础类型的功能。
37 | - `unicode`:为 unicode 型的字符串提供特殊的功能。
38 | - `regexp`:正则表达式功能。
39 | - `bytes`:提供对字符型分片的操作。
40 | - `index/suffixarray`:子字符串快速查询。
41 | - `math`-`math/cmath`-`math/big`-`math/rand-sort`:
42 | - `math`:基本的数学函数。
43 | - `math/cmath`:对复数的操作。
44 | - `math/rand`:伪随机数生成。
45 | - `sort`:为数组排序和自定义集合。
46 | - `math/big`:大数的实现和计算。
47 | - `container`-`/list`-`/ring`-`/heap`:
48 | - `list`:双链表。
49 | - `ring`:环形链表。
50 | - `heap`:堆。
51 | - `compress/bzip2`-`/flate`-`/gzip`-`/lzw`-`zlib`:
52 | - `compress/bzip2`:实现 bzip2 的解压。
53 | - `flate`:实现 deflate 的数据压缩格式,如 RFC 1951 所述。
54 | - `gzip`:实现 gzip 压缩文件的读写。
55 | - `lzw`:Lempel Ziv Welch 压缩数据格式实现。
56 | - `zlib`:实现 zlib 数据压缩格式的读写。
57 | - `context`:用来简化对于处理单个请求的多个 goroutine 之间与请求域的数据、取消信号、截止时间等相关操作。
58 | - `crypto`-`crypto/md5`-`crypto/sha1`:
59 | - `crypto`:常用密码常数的集合。
60 | - `crypto/md5`:MD5 加密。
61 | - `crypto/sha1`:SHA1 加密。
62 | - `errors`:实现操作出错的方法。
63 | - `expvar`:为公共变量提供标准化的接口。
64 | - `hash`:所有散列函数实现的通用接口。
65 | - `html`:HTML 文本转码转义功能。
66 | - `sort`:提供用于对切片和用户定义的集合进行排序的原始函数。
67 | - `unsafe`:包含了一些打破 Go 语言「类型安全」的命令,一般程序不会使用,可用在 C/C++ 程序的调用中。
68 | - `syscall`-`os`-`os/exec`:
69 | - `syscall`:提供了操作系统底层调用的基本接口。
70 | - `os`:提供给我们一个平台无关性的操作系统功能接口,采用类 Unix 设计,隐藏了不同操作系统间差异,让不同的文件系统和操作系统对象表现一致。
71 | - `os/exec`:提供了运行外部操作系统命令和程序的方式。
72 | - `time`-`log`:
73 | - `time`:日期和时间的基本操作。
74 | - `log`:记录程序运行时产生的日志。
75 | - `encoding/json`-`encoding/xml`-`text/template`:
76 | - `encoding/json`:读取并解码和写入并编码 JSON 数据。
77 | - `encoding/xml`:简单的 XML1.0 解析器。
78 | - `text/template`:生成像 HTML 一样的数据与文本混合的数据驱动模板。
79 | - `net`-`net/http`:
80 | - `net`:网络数据的基本操作。
81 | - `http`:提供了一个可扩展的 HTTP 服务器和基础客户端,解析 HTTP 请求和回复。
82 | - `runtime`:Go 程序运行时的交互操作,例如垃圾回收和协程创建。
83 | - `reflect`:实现通过程序运行时反射,让程序操作任意类型的变量。
84 |
85 | 这里仅仅列举了一部分标准库,更全面的标准库列表大家可以直接看官网。
86 |
87 | 那么问题来了,这么多库从何下手呢?
88 |
89 | 我这里做一个简单的分类,由于水平有限,只能做一些简单的梳理,然后大家可以结合自己的实际情况来做选择。
90 |
91 | 有些库涉及到非常专业的知识,投入产出比可能会比较低。比如 `archive`、`compress` 以及 `crypto`,涉及到压缩算法以及加密算法的知识。
92 |
93 | 有些库属于工具类,比如 `bufio`、`bytes`、`strings`、`path`、`strconv` 等,这些库不涉及领域知识,阅读起来比较容易。
94 |
95 | 有些库属于与操作系统打交道的,比如 `os`,`net`、`sync` 等,学习这些库需要对操作系统有明确的认识。
96 |
97 | `net` 下的很多子包与网络协议相关,比如 `net/http`,涉及 `http` 报文的解析,需要对网络协议比较了解。
98 |
99 | 如果想要深入了解语言的底层原理,则需要阅读 `runtime` 库。
100 |
101 | 要想快速入门,并且了解语言的设计理念,建议阅读 `io` 以及 `fmt` 库,阅读后会对接口的设计理解更深。
102 |
103 | 我已经看了一些源码,虽然过程痛苦,但确实非常有用。前期可能理解起来比较困难,用的时间长一些,但形成固定套路之后,会越来越熟悉,用的时间也会更少,理解也会更深刻。
104 |
105 | 后续我还会继续总结输出,请大家持续关注,让我们学起来。
106 |
107 | ---
108 |
109 | **开源项目:**
110 |
111 | - [https://github.com/yongxinz/id-maker](https://github.com/yongxinz/id-maker)
112 |
113 | **往期文章:**
114 |
115 | - [开源项目|Go 开发的一款分布式唯一 ID 生成系统](https://mp.weixin.qq.com/s/tCGYTlB4nJH1ClViFQJ6Cw)
116 | - [测试小姐姐问我 gRPC 怎么用,我直接把这篇文章甩给了她](https://mp.weixin.qq.com/s/qdI2JqpMq6t2KN1byHaNCQ)
117 | - [gRPC,爆赞](https://mp.weixin.qq.com/s/1Xbca4Dv0akonAZerrChgA)
--------------------------------------------------------------------------------
/advanced/001-如何在 Go 中将 []byte 转换为 io.Reader?.md:
--------------------------------------------------------------------------------
1 | **原文链接:** [如何在 Go 中将 []byte 转换为 io.Reader?](https://mp.weixin.qq.com/s/nFkob92GOs6Gp75pxA5wCQ)
2 |
3 | 
4 |
5 | 在 stackoverflow 上看到一个问题,题主进行了一个网络请求,接口返回的是 `[]byte`。如果想要将其转换成 `io.Reader`,需要怎么做呢?
6 |
7 | 这个问题解决起来并不复杂,简单几行代码就可以轻松将其转换成功。不仅如此,还可以再通过几行代码反向转换回来。
8 |
9 | 下面听我慢慢给你吹,首先直接看两段代码。
10 |
11 | ### []byte 转 io.Reader
12 |
13 | ```go
14 | package main
15 |
16 | import (
17 | "bytes"
18 | "fmt"
19 | "log"
20 | )
21 |
22 | func main() {
23 | data := []byte("Hello AlwaysBeta")
24 |
25 | // byte slice to bytes.Reader, which implements the io.Reader interface
26 | reader := bytes.NewReader(data)
27 |
28 | // read the data from reader
29 | buf := make([]byte, len(data))
30 | if _, err := reader.Read(buf); err != nil {
31 | log.Fatal(err)
32 | }
33 |
34 | fmt.Println(string(buf))
35 | }
36 | ```
37 |
38 | 输出:
39 |
40 | ```
41 | Hello AlwaysBeta
42 | ```
43 |
44 | 这段代码先将 `[]byte` 数据转换到 `reader` 中,然后再从 `reader` 中读取数据,并打印输出。
45 |
46 | ### io.Reader 转 []byte
47 |
48 | ```go
49 | package main
50 |
51 | import (
52 | "bytes"
53 | "fmt"
54 | "strings"
55 | )
56 |
57 | func main() {
58 | ioReaderData := strings.NewReader("Hello AlwaysBeta")
59 |
60 | // creates a bytes.Buffer and read from io.Reader
61 | buf := &bytes.Buffer{}
62 | buf.ReadFrom(ioReaderData)
63 |
64 | // retrieve a byte slice from bytes.Buffer
65 | data := buf.Bytes()
66 |
67 | // only read the left bytes from 6
68 | fmt.Println(string(data[6:]))
69 | }
70 | ```
71 |
72 | 输出:
73 |
74 | ```
75 | AlwaysBeta
76 | ```
77 |
78 | 这段代码先创建了一个 `reader`,然后读取数据到 `buf`,最后打印输出。
79 |
80 | 以上两段代码就是 `[]byte` 和 `io.Reader` 互相转换的过程。对比这两段代码不难发现,都有 `NewReader` 的身影。而且在转换过程中,都起到了关键作用。
81 |
82 | 那么问题来了,这个 `NewReader` 到底是什么呢?接下来我们通过源码来一探究竟。
83 |
84 | ### 源码解析
85 |
86 | Go 的 `io` 包提供了最基本的 IO 接口,其中 `io.Reader` 和 `io.Writer` 两个接口最为关键,很多原生结构都是围绕这两个接口展开的。
87 |
88 | 
89 |
90 | 下面就来分别说说这两个接口:
91 |
92 | #### Reader 接口
93 |
94 | `io.Reader` 表示一个读取器,它将数据从某个资源读取到传输缓冲区。在缓冲区中,数据可以被流式传输和使用。
95 |
96 | 
97 |
98 | 接口定义如下:
99 |
100 | ```go
101 | type Reader interface {
102 | Read(p []byte) (n int, err error)
103 | }
104 | ```
105 |
106 | `Read()` 方法将 `len(p)` 个字节读取到 `p` 中。它返回读取的字节数 `n`,以及发生错误时的错误信息。
107 |
108 | 举一个例子:
109 |
110 | ```go
111 | package main
112 |
113 | import (
114 | "fmt"
115 | "io"
116 | "os"
117 | "strings"
118 | )
119 |
120 | func main() {
121 | reader := strings.NewReader("Clear is better than clever")
122 | p := make([]byte, 4)
123 |
124 | for {
125 | n, err := reader.Read(p)
126 | if err != nil {
127 | if err == io.EOF {
128 | fmt.Println("EOF:", n)
129 | break
130 | }
131 | fmt.Println(err)
132 | os.Exit(1)
133 | }
134 | fmt.Println(n, string(p[:n]))
135 | }
136 | }
137 | ```
138 |
139 | 输出:
140 |
141 | ```
142 | 4 Clea
143 | 4 r is
144 | 4 bet
145 | 4 ter
146 | 4 than
147 | 4 cle
148 | 3 ver
149 | EOF: 0
150 | ```
151 |
152 | 这段代码从 `reader` 不断读取数据,每次读 4 个字节,然后打印输出,直到结尾。
153 |
154 | 最后一次返回的 n 值有可能小于缓冲区大小。
155 |
156 | #### Writer 接口
157 |
158 | `io.Writer` 表示一个编写器,它从缓冲区读取数据,并将数据写入目标资源。
159 |
160 | 
161 |
162 | ```go
163 | type Writer interface {
164 | Write(p []byte) (n int, err error)
165 | }
166 | ```
167 |
168 | `Write` 方法将 `len(p)` 个字节从 `p` 中写入到对象数据流中。它返回从 `p` 中被写入的字节数 `n`,以及发生错误时返回的错误信息。
169 |
170 | 举一个例子:
171 |
172 | ```go
173 | package main
174 |
175 | import (
176 | "bytes"
177 | "fmt"
178 | "os"
179 | )
180 |
181 | func main() {
182 | // 创建 Buffer 暂存空间,并将一个字符串写入 Buffer
183 | // 使用 io.Writer 的 Write 方法写入
184 | var buf bytes.Buffer
185 | buf.Write([]byte("hello world , "))
186 |
187 | // 用 Fprintf 将一个字符串拼接到 Buffer 里
188 | fmt.Fprintf(&buf, " welcome to golang !")
189 |
190 | // 将 Buffer 的内容输出到标准输出设备
191 | buf.WriteTo(os.Stdout)
192 | }
193 | ```
194 |
195 | 输出:
196 |
197 | ```
198 | hello world , welcome to golang !
199 | ```
200 |
201 | `bytes.Buffer` 是一个结构体类型,用来暂存写入的数据,其实现了 `io.Writer` 接口的 `Write` 方法。
202 |
203 | `WriteTo` 方法定义:
204 |
205 | ```go
206 | func (b *Buffer) WriteTo(w io.Writer) (n int64, err error)
207 | ```
208 |
209 | `WriteTo` 方法第一个参数是 `io.Writer` 接口类型。
210 |
211 | ### 转换原理
212 |
213 | 再说回文章开头的转换问题。
214 |
215 | 只要某个实例实现了接口 `io.Reader` 里的方法 `Read()` ,就满足了接口 `io.Reader`。
216 |
217 | 
218 |
219 | `bytes` 和 `strings` 包都实现了 `Read()` 方法。
220 |
221 | ```go
222 | // src/bytes/reader.go
223 |
224 | // NewReader returns a new Reader reading from b.
225 | func NewReader(b []byte) *Reader { return &Reader{b, 0, -1} }
226 | ```
227 |
228 | ```go
229 | // src/strings/reader.go
230 |
231 | // NewReader returns a new Reader reading from s.
232 | // It is similar to bytes.NewBufferString but more efficient and read-only.
233 | func NewReader(s string) *Reader { return &Reader{s, 0, -1} }
234 | ```
235 |
236 | 在调用 `NewReader` 的时候,会返回了对应的 `T.Reader` 类型,而它们都是通过 `io.Reader` 扩展而来的,所以也就实现了转换。
237 |
238 | ### 总结
239 |
240 | 在开发过程中,避免不了要进行一些 IO 操作,包括打印输出,文件读写,网络连接等。
241 |
242 | 在 Go 语言中,也提供了一系列标准库来应对这些操作,主要封装在以下几个包中:
243 |
244 | - `io`:基本的 IO 操作接口。
245 | - `io/ioutil`:封装了一些实用的 IO 函数。
246 | - `fmt`:实现了 IO 格式化操作。
247 | - `bufio`:实现了带缓冲的 IO。
248 | - `net.Conn`:网络读写。
249 | - `os.Stdin`,`os.Stdout`:系统标准输入输出。
250 | - `os.File`:系统文件操作。
251 | - `bytes`:字节相关 IO 操作。
252 |
253 | 除了 `io.Reader` 和 `io.Writer` 之外,`io` 包还封装了很多其他基本接口,比如 `ReaderAt`,`WriterAt`,`ReaderFrom` 和 `WriterTo` 等,这里就不一一介绍了。这部分代码并不复杂,读起来很轻松,而且还能加深对接口的理解,推荐大家看看。
254 |
255 | 好了,本文就到这里吧。关注我,带你通过问题读 Go 源码。
256 |
257 | ---
258 |
259 | **推荐阅读:**
260 |
261 | - [开始读 Go 源码了](https://mp.weixin.qq.com/s/iPM-mPOepRuDqkBtcnG1ww)
262 |
263 | **热情推荐:**
264 |
265 | - [计算机经典书籍(含下载方式)](https://mp.weixin.qq.com/s?__biz=MzI3MjY1ODI2Ng==&mid=2247484320&idx=1&sn=4f9ef828917db8b9c23688902ca46477&chksm=eb2e7995dc59f0834030ad6bad95190a9e1f5b9d44da9e53922ef8c81919b8bc68fa0b9841fd&token=1764237540&lang=zh_CN#rd)
266 | - **[技术博客](https://github.com/yongxinz/tech-blog):** 硬核后端技术干货,内容包括 Python、Django、Docker、Go、Redis、ElasticSearch、Kafka、Linux 等。
267 | - **[Go 程序员](https://github.com/yongxinz/gopher):** Go 学习路线图,包括基础专栏,进阶专栏,源码阅读,实战开发,面试刷题,必读书单等一系列资源。
268 | - **[面试题汇总](https://github.com/yongxinz/backend-interview):** 包括 Python、Go、Redis、MySQL、Kafka、数据结构、算法、编程、网络等各种常考题。
269 |
270 | **参考文章:**
271 |
272 | - https://books.studygolang.com/The-Golang-Standard-Library-by-Example/chapter01/01.1.html
273 | - https://www.cnblogs.com/jiujuan/p/14005731.html
274 | - https://segmentfault.com/a/1190000015591319
--------------------------------------------------------------------------------
/advanced/003-Go Error 嵌套到底是怎么实现的?.md:
--------------------------------------------------------------------------------
1 | **原文链接:** [Go Error 嵌套到底是怎么实现的?](https://mp.weixin.qq.com/s/nWb-0RTDG1Pg5ZmJZfbEPA)
2 |
3 | Go Error 的设计哲学是 「Errors Are Values」。
4 |
5 | 这句话应该怎么理解呢?翻译起来挺难的。不过从源码的角度来看,好像更容易理解其背后的含义。
6 |
7 | Go Error 源码很简单,寥寥几行:
8 |
9 | ```go
10 | // src/builtin/builtin.go
11 |
12 | type error interface {
13 | Error() string
14 | }
15 | ```
16 |
17 | `error` 是一个接口类型,只需要实现 `Error()` 方法即可。在 `Error()` 方法中,就可以返回自定义结构体的任意内容。
18 |
19 | 下面首先说说如何创建 `error`。
20 |
21 | ## 创建 Error
22 |
23 | 创建 `error` 有两种方式,分别是:
24 |
25 | 1. `errors.New()`;
26 | 2. `fmt.Errorf()`。
27 |
28 | ### errors.New()
29 |
30 | `errors.New()` 的使用延续了 Go 的一贯风格,`New` 一下就可以了。
31 |
32 | 举一个例子:
33 |
34 | ```go
35 | package main
36 |
37 | import (
38 | "errors"
39 | "fmt"
40 | )
41 |
42 | func main() {
43 | err := errors.New("这是 errors.New() 创建的错误")
44 | fmt.Printf("err 错误类型:%T,错误为:%v\n", err, err)
45 | }
46 |
47 | /* 输出
48 | err 错误类型:*errors.errorString,错误为:这是 errors.New() 创建的错误
49 | */
50 | ```
51 |
52 | 这段代码唯一让人困惑的地方可能就是错误类型了,但没关系。只要看一下源码,就瞬间迎刃而解。
53 |
54 | 源码如下:
55 |
56 | ```go
57 | // src/errors/errors.go
58 |
59 | // New returns an error that formats as the given text.
60 | // Each call to New returns a distinct error value even if the text is identical.
61 | func New(text string) error {
62 | return &errorString{text}
63 | }
64 |
65 | // errorString is a trivial implementation of error.
66 | type errorString struct {
67 | s string
68 | }
69 |
70 | func (e *errorString) Error() string {
71 | return e.s
72 | }
73 | ```
74 |
75 | 可以看到,`errorString` 是一个结构体,实现了 `Error()` 方法,`New` 函数直接返回 `errorString` 指针。
76 |
77 | 这种用法很简单,但不实用。假如我还想返回程序的上下文信息,它就没辙了。
78 |
79 | 下面看第二种方式。
80 |
81 | ### fmt.Errorf()
82 |
83 | 还是先看一个例子:
84 |
85 | ```go
86 | package main
87 |
88 | import (
89 | "database/sql"
90 | "fmt"
91 | )
92 |
93 | func foo() error {
94 | return sql.ErrNoRows
95 | }
96 |
97 | func bar() error {
98 | return foo()
99 | }
100 |
101 | func main() {
102 | err := bar()
103 | if err == sql.ErrNoRows {
104 | fmt.Printf("data not found, %+v\n", err)
105 | return
106 | }
107 | if err != nil {
108 | fmt.Println("Unknown error")
109 | }
110 | }
111 |
112 | /* 输出
113 | data not found, sql: no rows in result set
114 | */
115 | ```
116 |
117 | 这个例子输出了我们想要的结果,但是还不够。
118 |
119 | 一般情况下,我们会通过使用 `fmt.Errorf()` 函数,附加上我们想添加的文本信息,使返回内容更明确,处理起来更灵活。
120 |
121 | 所以,`foo()` 函数会改成下面这样:
122 |
123 | ```go
124 | func foo() error {
125 | return fmt.Errorf("foo err, %v", sql.ErrNoRows)
126 | }
127 | ```
128 |
129 | 这时问题就出现了,经过 `fmt.Errorf()` 的封装,原始 `error` 类型发生了改变,这就导致 `err == sql.ErrNoRows` 不再成立,返回信息变成了 `Unknown error`。
130 |
131 | 如果想根据返回的 `error` 类型做不同处理,就无法实现了。
132 |
133 | 因此,Go 1.13 为我们提供了 `wrapError` 来处理这个问题。
134 |
135 | ## Wrap Error
136 |
137 | 看一个例子:
138 |
139 | ```go
140 | package main
141 |
142 | import (
143 | "fmt"
144 | )
145 |
146 | type myError struct{}
147 |
148 | func (e myError) Error() string {
149 | return "Error happended"
150 | }
151 |
152 | func main() {
153 | e1 := myError{}
154 | e2 := fmt.Errorf("E2: %w", e1)
155 | e3 := fmt.Errorf("E3: %w", e2)
156 | fmt.Println(e2)
157 | fmt.Println(e3)
158 | }
159 |
160 | /* output
161 | E2: Error happended
162 | E3: E2: Error happended
163 | */
164 | ```
165 |
166 | 乍一看好像好没什么区别,但背后的实现原理却并不相同。
167 |
168 | Go 扩展了 `fmt.Errorf()` 函数,增加了一个 `%w` 标识符来创建 `wrapError`。
169 |
170 | ```go
171 | // src/fmt/errors.go
172 |
173 | func Errorf(format string, a ...interface{}) error {
174 | p := newPrinter()
175 | p.wrapErrs = true
176 | p.doPrintf(format, a)
177 | s := string(p.buf)
178 | var err error
179 | if p.wrappedErr == nil {
180 | err = errors.New(s)
181 | } else {
182 | err = &wrapError{s, p.wrappedErr}
183 | }
184 | p.free()
185 | return err
186 | }
187 | ```
188 |
189 | 当使用 `w%` 时,函数会返回 `&wrapError{s, p.wrappedErr}`,`wrapError` 结构体定义如下:
190 |
191 | ```go
192 | // src/fmt/errors.go
193 |
194 | type wrapError struct {
195 | msg string
196 | err error
197 | }
198 |
199 | func (e *wrapError) Error() string {
200 | return e.msg
201 | }
202 |
203 | func (e *wrapError) Unwrap() error {
204 | return e.err
205 | }
206 | ```
207 |
208 | 实现了 `Error()` 方法,说明它是一个 `error`,而 `Unwrap()` 方法是为了获取被封装的 `error`。
209 |
210 | ```go
211 | // src/errors/wrap.go
212 |
213 | func Unwrap(err error) error {
214 | u, ok := err.(interface {
215 | Unwrap() error
216 | })
217 | if !ok {
218 | return nil
219 | }
220 | return u.Unwrap()
221 | }
222 | ```
223 |
224 | 它们之间的关系是这样的:
225 |
226 | 
227 |
228 | 因此,我们可以使用 `w%` 将上文中的程序进行改造,使其内容输出更丰富。
229 |
230 | 如下:
231 |
232 | ```go
233 | package main
234 |
235 | import (
236 | "database/sql"
237 | "errors"
238 | "fmt"
239 | )
240 |
241 | func bar() error {
242 | if err := foo(); err != nil {
243 | return fmt.Errorf("bar failed: %w", foo())
244 | }
245 | return nil
246 | }
247 |
248 | func foo() error {
249 | return fmt.Errorf("foo failed: %w", sql.ErrNoRows)
250 | }
251 |
252 | func main() {
253 | err := bar()
254 | if errors.Is(err, sql.ErrNoRows) {
255 | fmt.Printf("data not found, %+v\n", err)
256 | return
257 | }
258 | if err != nil {
259 | fmt.Println("Unknown error")
260 | }
261 | }
262 |
263 | /* output
264 | data not found, bar failed: foo failed: sql: no rows in result set
265 | */
266 | ```
267 |
268 | 终于有了让人满意的输出结果,每个函数都增加了必要的上下文信息,而且也符合对错误类型的判断。
269 |
270 | `errors.Is()` 函数用来判断 `err` 以及其封装的 `error` 链中是否包含目标类型。这也就解决了上文提出的无法判断错误类型的问题。
271 |
272 | ## 后记
273 |
274 | 其实,Go 目前对 Error 的处理方式也是充满争议的。不过,官方团队正在积极和社区交流,提出改进方法。相信在不久的将来,一定会找到更好的解决方案。
275 |
276 | 现阶段来说,大部分团队可能会选择 `github.com/pkg/errors` 包来进行错误处理。如果感兴趣的话,可以学学看。
277 |
278 | 好了,本文就到这里吧。**关注我,带你通过问题读 Go 源码。**
279 |
280 | ---
281 |
282 | **源码地址:**
283 |
284 | - [https://github.com/yongxinz/gopher](https://github.com/yongxinz/gopher)
285 |
286 | **推荐阅读:**
287 |
288 | - [为什么要避免在 Go 中使用 ioutil.ReadAll?](https://mp.weixin.qq.com/s/e2A3ME4vhOK2S3hLEJtPsw)
289 | - [如何在 Go 中将 []byte 转换为 io.Reader?](https://mp.weixin.qq.com/s/nFkob92GOs6Gp75pxA5wCQ)
290 | - [开始读 Go 源码了](https://mp.weixin.qq.com/s/iPM-mPOepRuDqkBtcnG1ww)
291 |
292 | **参考文章:**
293 |
294 | - https://chasecs.github.io/posts/the-philosophy-of-go-error-handling/
295 | - https://medium.com/@dche423/golang-error-handling-best-practice-cn-42982bd72672
296 | - https://www.flysnow.org/2019/09/06/go1.13-error-wrapping.html
--------------------------------------------------------------------------------
/advanced/004-读 Go 源码,可以试试这个工具.md:
--------------------------------------------------------------------------------
1 | **原文链接:** [读 Go 源码,可以试试这个工具](https://mp.weixin.qq.com/s/E2TL_kcbVcRJ0CnxwbXWLw)
2 |
3 | 编程发展至今,从面向过程到面向对象,再到现在的面向框架。写代码变成了一件越来越容易的事情。
4 |
5 | 学习基础语法,看看框架文档,几天时间搞出一个小项目并不是一件很难的事情。
6 |
7 | 但时间长了就会发现,一直这样飘在表面是不行的,技术永远得不到提升。
8 |
9 | 想要技术水平有一个质的飞跃,有一个很好的方法,就是读源码。
10 |
11 | 但读源码真的是一件很有挑战的事情。
12 |
13 | 想想当年自己读 Django 源码,从启动流程开始看,没走几步就放弃了,而且还放弃了很多次。
14 |
15 | 这么说吧,我对 Django 启动部分的代码,就像对英文单词 abandon 那么熟悉。
16 |
17 | 后来总结经验,发现是方法不对。
18 |
19 | 主要原因是一上来就深入细节了,事无巨细,每个函数都不想错过。结果就导致对整体没有概念,抓不住重点,又深陷无关紧要的代码。最后就是看不进去,只能放弃。
20 |
21 | 最近看了一点 Go 源码,慢慢也摸索出了一些心得。有一个方法我觉得挺好,可以带着问题去读源码,比如:
22 |
23 | - [Go Error 嵌套到底是怎么实现的?](https://mp.weixin.qq.com/s/nWb-0RTDG1Pg5ZmJZfbEPA)
24 | - [为什么要避免在 Go 中使用 ioutil.ReadAll?](https://mp.weixin.qq.com/s/e2A3ME4vhOK2S3hLEJtPsw)
25 | - [如何在 Go 中将 []byte 转换为 io.Reader?](https://mp.weixin.qq.com/s/nFkob92GOs6Gp75pxA5wCQ)
26 |
27 | 在解决问题的过程中也就对源码更熟悉了。
28 |
29 | 还有一点要注意的就是,先看整体,再看细节。
30 |
31 | 在这里推荐给大家一个工具,这个工具可以帮我们梳理出代码的整体结构,我觉得还是挺有用的。是一个开源项目:
32 |
33 | **项目地址:** https://github.com/jfeliu007/goplantuml
34 |
35 | 这个项目可以分析一个 Go 项目,然后生成接口和结构体的 UML 图。有了这个图之后,基本上也就对项目整体关系有了一个基本概念,再读代码的话,相对来说会容易一些。
36 |
37 | 项目具体怎么用我倒是没仔细研究,因为老哥非常贴心的写了一个 WEB 页面:
38 |
39 | **网站链接:** https://www.dumels.com/
40 |
41 | 
42 |
43 | 使用起来很方便,首先在页面最上方输入框输入项目地址,然后在左侧输入要分析的代码目录就可以了。默认生成的图中会包括 Fields 和 Methods。
44 |
45 | 填写好信息之后就可以生成 UML 图了。比如我输入的 `src/sync`,就得到了下面这张图,有了这张图,对代码结构之间的关系就更清晰了。
46 |
47 | 
48 |
49 | 还可以一次分析多个目录,多个目录用英文逗号分割。
50 |
51 | 如果不填写要分析的目录,则会分析整个项目,也可以选择是否要忽略某个目录。
52 |
53 | 友情提示一点,不要试图分析整个 Go 项目,可能是项目太大了,页面是不会给你返回的。
54 |
55 | 好了,本文就到这里了。你有什么好用的工具吗?欢迎给我留言交流。
56 |
57 | ---
58 |
59 | **往期推荐:**
60 |
61 | - [开始读 Go 源码了](https://mp.weixin.qq.com/s/iPM-mPOepRuDqkBtcnG1ww)
62 | - [推荐三个实用的 Go 开发工具](https://mp.weixin.qq.com/s/3GLMLhegB3wF5_62mpmePA)
63 |
--------------------------------------------------------------------------------
/advanced/example/001_byteslice_to_ioreader.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "bytes"
5 | "fmt"
6 | "log"
7 | )
8 |
9 | func main() {
10 | data := []byte("Hello AlwaysBeta")
11 |
12 | // byte slice to bytes.Reader, which implements the io.Reader interface
13 | reader := bytes.NewReader(data)
14 |
15 | // read the data from reader
16 | buf := make([]byte, len(data))
17 | if _, err := reader.Read(buf); err != nil {
18 | log.Fatal(err)
19 | }
20 |
21 | fmt.Println(string(buf))
22 | }
23 |
--------------------------------------------------------------------------------
/advanced/example/001_io_reader.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "fmt"
5 | "io"
6 | "os"
7 | "strings"
8 | )
9 |
10 | func main() {
11 | reader := strings.NewReader("Clear is better than clever")
12 | p := make([]byte, 4)
13 |
14 | for {
15 | n, err := reader.Read(p)
16 | if err != nil {
17 | if err == io.EOF {
18 | fmt.Println("EOF:", n)
19 | break
20 | }
21 | fmt.Println(err)
22 | os.Exit(1)
23 | }
24 | fmt.Println(n, string(p[:n]))
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/advanced/example/001_io_writer.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "bytes"
5 | "fmt"
6 | "os"
7 | )
8 |
9 | func main() {
10 | // 创建 Buffer 暂存空间,并将一个字符串写入 Buffer
11 | // 使用 io.Writer 的 Write 方法写入
12 | var buf bytes.Buffer
13 | buf.Write([]byte("hello world , "))
14 |
15 | // 用 Fprintf 将一个字符串拼接到 Buffer 里
16 | fmt.Fprintf(&buf, " welcome to golang !")
17 |
18 | // 将 Buffer 的内容输出到标准输出设备
19 | buf.WriteTo(os.Stdout)
20 | }
21 |
--------------------------------------------------------------------------------
/advanced/example/001_ioreader_to_byteslice.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "bytes"
5 | "fmt"
6 | "strings"
7 | )
8 |
9 | func main() {
10 | ioReaderData := strings.NewReader("Hello AlwaysBeta")
11 | fmt.Printf("%T", ioReaderData)
12 |
13 | // creates a bytes.Buffer and read from io.Reader
14 | buf := &bytes.Buffer{}
15 | buf.ReadFrom(ioReaderData)
16 |
17 | // retrieve a byte slice from bytes.Buffer
18 | data := buf.Bytes()
19 |
20 | // only read the left bytes from 6
21 | fmt.Println(string(data[6:]))
22 | }
23 |
--------------------------------------------------------------------------------
/advanced/example/002_nopcloser.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "fmt"
5 | "io/ioutil"
6 | "reflect"
7 | "strings"
8 | )
9 |
10 | func main() {
11 | //返回*strings.Reader
12 | reader := strings.NewReader("Hello AlwaysBeta")
13 | r := ioutil.NopCloser(reader)
14 | defer r.Close()
15 |
16 | fmt.Println(reflect.TypeOf(reader))
17 | data, _ := ioutil.ReadAll(reader)
18 | fmt.Println(string(data))
19 | }
20 |
--------------------------------------------------------------------------------
/advanced/example/002_readdir.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "fmt"
5 | "io/ioutil"
6 | )
7 |
8 | func main() {
9 | dirName := "../"
10 | fileInfos, _ := ioutil.ReadDir(dirName)
11 | fmt.Println(len(fileInfos))
12 | for i := 0; i < len(fileInfos); i++ {
13 | fmt.Printf("%T\n", fileInfos[i])
14 | fmt.Println(i, fileInfos[i].Name(), fileInfos[i].IsDir())
15 |
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/advanced/example/002_readfile.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "fmt"
5 | "io/ioutil"
6 | "os"
7 | )
8 |
9 | func main() {
10 | data, err := ioutil.ReadFile("./text.txt")
11 | if err != nil {
12 | fmt.Println("read error")
13 | os.Exit(1)
14 | }
15 | fmt.Println(string(data))
16 | }
17 |
--------------------------------------------------------------------------------
/advanced/example/002_tempdir.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "fmt"
5 | "io/ioutil"
6 | "os"
7 | )
8 |
9 | func main() {
10 | dir, err := ioutil.TempDir("./", "Test")
11 | if err != nil {
12 | fmt.Println(err)
13 | }
14 | defer os.Remove(dir) // 用完删除
15 | fmt.Printf("%s\n", dir)
16 | }
17 |
--------------------------------------------------------------------------------
/advanced/example/002_tempfile.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "fmt"
5 | "io/ioutil"
6 | "os"
7 | )
8 |
9 | func main() {
10 | f, err := ioutil.TempFile("./", "Test")
11 | if err != nil {
12 | fmt.Println(err)
13 | }
14 | defer os.Remove(f.Name()) // 用完删除
15 | fmt.Printf("%s\n", f.Name())
16 | }
17 |
--------------------------------------------------------------------------------
/advanced/example/002_writefile.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "fmt"
5 | "io/ioutil"
6 | )
7 |
8 | func main() {
9 | fileName := "./text.txt"
10 | s := "Hello AlwaysBeta"
11 | err := ioutil.WriteFile(fileName, []byte(s), 0777)
12 | fmt.Println(err)
13 | }
14 |
--------------------------------------------------------------------------------
/advanced/example/003_errors.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "database/sql"
5 | "fmt"
6 | )
7 |
8 | func foo() error {
9 | return fmt.Errorf("foo err, %v", sql.ErrNoRows)
10 | }
11 |
12 | func bar() error {
13 | return foo()
14 | }
15 |
16 | func main() {
17 | // err := errors.New("这是 errors.New() 创建的错误")
18 | // fmt.Printf("err 错误类型:%T,错误为:%v\n", err, err)
19 |
20 | err := bar()
21 | if err == sql.ErrNoRows {
22 | fmt.Printf("data not found, %+v\n", err)
23 | return
24 | }
25 | if err != nil {
26 | fmt.Println("Unknown error")
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/advanced/example/003_fmt_error.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "fmt"
5 | )
6 |
7 | func main() {
8 | err := fmt.Errorf("这个是 fmt.Errorf() 创建的错误,错误编码为:%d", 500)
9 | fmt.Printf("err2 错误类型:%T,错误为:%v\n", err, err)
10 | }
11 |
--------------------------------------------------------------------------------
/advanced/example/003_wrap_error.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "database/sql"
5 | "errors"
6 | "fmt"
7 | )
8 |
9 | func bar() error {
10 | if err := foo(); err != nil {
11 | return fmt.Errorf("bar failed: %w", foo())
12 | }
13 | return nil
14 | }
15 |
16 | func foo() error {
17 | return fmt.Errorf("foo failed: %w", sql.ErrNoRows)
18 | }
19 |
20 | func main() {
21 | err := bar()
22 | if errors.Is(err, sql.ErrNoRows) {
23 | fmt.Printf("data not found, %+v\n", err)
24 | return
25 | }
26 | if err != nil {
27 | fmt.Println("Unknown error")
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/advanced/example/003_wrap_error_1.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "fmt"
5 | )
6 |
7 | type myError struct{}
8 |
9 | func (e myError) Error() string {
10 | return "Error happended"
11 | }
12 |
13 | func main() {
14 | e1 := myError{}
15 | e2 := fmt.Errorf("E2: %w", e1)
16 | e3 := fmt.Errorf("E3: %w", e2)
17 | fmt.Println(e2)
18 | fmt.Println(e3)
19 | }
20 |
--------------------------------------------------------------------------------
/advanced/example/go_ping.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "bytes"
5 | "encoding/binary"
6 | "fmt"
7 | "net"
8 | "os"
9 | "time"
10 | )
11 |
12 | type ICMP struct {
13 | Type uint8
14 | Code uint8
15 | CheckSum uint16
16 | Identifier uint16
17 | SequenceNum uint16
18 | }
19 |
20 | func usage() {
21 | msg := `
22 | Need to run as root!
23 |
24 | Usage:
25 | goping host
26 |
27 | Example: ./goping www.baidu.com`
28 |
29 | fmt.Println(msg)
30 | os.Exit(0)
31 | }
32 |
33 | func getICMP(seq uint16) ICMP {
34 | icmp := ICMP{
35 | Type: 8,
36 | Code: 0,
37 | CheckSum: 0,
38 | Identifier: 0,
39 | SequenceNum: seq,
40 | }
41 |
42 | var buffer bytes.Buffer
43 | binary.Write(&buffer, binary.BigEndian, icmp)
44 | icmp.CheckSum = CheckSum(buffer.Bytes())
45 | buffer.Reset()
46 |
47 | return icmp
48 | }
49 |
50 | func sendICMPRequest(icmp ICMP, destAddr *net.IPAddr) error {
51 | conn, err := net.DialIP("ip4:icmp", nil, destAddr)
52 | if err != nil {
53 | fmt.Printf("Fail to connect to remote host: %s\n", err)
54 | return err
55 | }
56 | defer conn.Close()
57 |
58 | var buffer bytes.Buffer
59 | binary.Write(&buffer, binary.BigEndian, icmp)
60 |
61 | if _, err := conn.Write(buffer.Bytes()); err != nil {
62 | return err
63 | }
64 |
65 | tStart := time.Now()
66 |
67 | conn.SetReadDeadline((time.Now().Add(time.Second * 2)))
68 |
69 | recv := make([]byte, 1024)
70 | receiveCnt, err := conn.Read(recv)
71 |
72 | if err != nil {
73 | return err
74 | }
75 |
76 | tEnd := time.Now()
77 | duration := tEnd.Sub(tStart).Nanoseconds() / 1e6
78 |
79 | fmt.Printf("%d bytes from %s: seq=%d time=%dms\n", receiveCnt, destAddr.String(), icmp.SequenceNum, duration)
80 |
81 | return err
82 | }
83 |
84 | func CheckSum(data []byte) uint16 {
85 | var (
86 | sum uint32
87 | length int = len(data)
88 | index int
89 | )
90 | for length > 1 {
91 | sum += uint32(data[index])<<8 + uint32(data[index+1])
92 | index += 2
93 | length -= 2
94 | }
95 | if length > 0 {
96 | sum += uint32(data[index])
97 | }
98 | sum += (sum >> 16)
99 |
100 | return uint16(^sum)
101 | }
102 |
103 | func main() {
104 | if len(os.Args) < 2 {
105 | usage()
106 | }
107 |
108 | host := os.Args[1]
109 | raddr, err := net.ResolveIPAddr("ip", host)
110 | if err != nil {
111 | fmt.Printf("Fail to resolve %s, %s\n", host, err)
112 | return
113 | }
114 |
115 | fmt.Printf("Ping %s (%s):\n\n", raddr.String(), host)
116 |
117 | for i := 1; i < 6; i++ {
118 | if err = sendICMPRequest(getICMP(uint16(i)), raddr); err != nil {
119 | fmt.Printf("Error: %s\n", err)
120 | }
121 | time.Sleep(2 * time.Second)
122 | }
123 | }
124 |
--------------------------------------------------------------------------------
/advanced/example/icmp_ping.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "fmt"
5 | "net"
6 | "os"
7 |
8 | "golang.org/x/net/icmp"
9 | "golang.org/x/net/ipv4"
10 | )
11 |
12 | var Data = []byte("I'm alwaysbeta!")
13 |
14 | type ping struct {
15 | Addr string
16 | Conn net.Conn
17 | Data []byte
18 | }
19 |
20 | func main() {
21 | ping, err := Run("10.249.42.227", Data)
22 |
23 | if err != nil {
24 | fmt.Println(err)
25 | }
26 |
27 | ping.Ping()
28 | }
29 |
30 | func MarshalMsg(req int, data []byte) ([]byte, error) {
31 | xid, xseq := os.Getpid()&0xffff, req
32 | wm := icmp.Message{
33 | Type: ipv4.ICMPTypeEcho, Code: 0,
34 | Body: &icmp.Echo{
35 | ID: xid, Seq: xseq,
36 | Data: data,
37 | },
38 | }
39 | return wm.Marshal(nil)
40 | }
41 |
42 | func Run(addr string, data []byte) (*ping, error) {
43 | wb, err := MarshalMsg(1, data)
44 | if err != nil {
45 | return nil, err
46 | }
47 |
48 | if err != nil {
49 | return nil, err
50 | }
51 | return &ping{Data: wb, Addr: addr}, nil
52 | }
53 |
54 | func (self *ping) Dail() (err error) {
55 | self.Conn, err = net.Dial("ip4:icmp", self.Addr)
56 | if err != nil {
57 | return err
58 | }
59 | return nil
60 | }
61 |
62 | func (self *ping) Ping() {
63 | if err := self.Dail(); err != nil {
64 | fmt.Println("ICMP error:", err)
65 | return
66 | }
67 | fmt.Println("Start ping from ", self.Conn.LocalAddr())
68 | sendPingMsg(self.Conn, self.Data)
69 | }
70 |
71 | func sendPingMsg(c net.Conn, wb []byte) {
72 | if _, err := c.Write(wb); err != nil {
73 | print(err)
74 | }
75 | }
76 |
--------------------------------------------------------------------------------
/advanced/example/text.txt:
--------------------------------------------------------------------------------
1 | Hello AlwaysBeta
--------------------------------------------------------------------------------
/advanced/pic/001-byte-slice-to-io-reader.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/yongxinz/gopher/030046cf44fb5b2a2c90112b3d179ae9ba9df000/advanced/pic/001-byte-slice-to-io-reader.png
--------------------------------------------------------------------------------
/advanced/pic/001-io-bytes-strings.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/yongxinz/gopher/030046cf44fb5b2a2c90112b3d179ae9ba9df000/advanced/pic/001-io-bytes-strings.png
--------------------------------------------------------------------------------
/advanced/pic/001-io-reader-writer.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/yongxinz/gopher/030046cf44fb5b2a2c90112b3d179ae9ba9df000/advanced/pic/001-io-reader-writer.png
--------------------------------------------------------------------------------
/advanced/pic/001-io-reader.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/yongxinz/gopher/030046cf44fb5b2a2c90112b3d179ae9ba9df000/advanced/pic/001-io-reader.png
--------------------------------------------------------------------------------
/advanced/pic/001-io-writer.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/yongxinz/gopher/030046cf44fb5b2a2c90112b3d179ae9ba9df000/advanced/pic/001-io-writer.png
--------------------------------------------------------------------------------
/advanced/pic/002-ioutil-readall-github.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/yongxinz/gopher/030046cf44fb5b2a2c90112b3d179ae9ba9df000/advanced/pic/002-ioutil-readall-github.png
--------------------------------------------------------------------------------
/advanced/pic/003-errors.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/yongxinz/gopher/030046cf44fb5b2a2c90112b3d179ae9ba9df000/advanced/pic/003-errors.png
--------------------------------------------------------------------------------
/advanced/pic/004-dumels-1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/yongxinz/gopher/030046cf44fb5b2a2c90112b3d179ae9ba9df000/advanced/pic/004-dumels-1.png
--------------------------------------------------------------------------------
/advanced/pic/004-dumels-2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/yongxinz/gopher/030046cf44fb5b2a2c90112b3d179ae9ba9df000/advanced/pic/004-dumels-2.png
--------------------------------------------------------------------------------
/advanced/pic/004-dumels.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/yongxinz/gopher/030046cf44fb5b2a2c90112b3d179ae9ba9df000/advanced/pic/004-dumels.png
--------------------------------------------------------------------------------
/advanced/uml/errors.drawio:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
--------------------------------------------------------------------------------
/advanced/uml/io-reader-writer.drawio:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
--------------------------------------------------------------------------------
/advanced/uml/io-reader.drawio:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 |
63 |
--------------------------------------------------------------------------------
/advanced/uml/io-writer.drawio:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 |
63 |
--------------------------------------------------------------------------------
/alipay.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/yongxinz/gopher/030046cf44fb5b2a2c90112b3d179ae9ba9df000/alipay.jpg
--------------------------------------------------------------------------------
/alwaysbeta.JPG:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/yongxinz/gopher/030046cf44fb5b2a2c90112b3d179ae9ba9df000/alwaysbeta.JPG
--------------------------------------------------------------------------------
/blog/.DS_Store:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/yongxinz/gopher/030046cf44fb5b2a2c90112b3d179ae9ba9df000/blog/.DS_Store
--------------------------------------------------------------------------------
/blog/02-使用 grpcurl 通过命令行访问 gRPC 服务.md:
--------------------------------------------------------------------------------
1 | **原文链接:** [使用 grpcurl 通过命令行访问 gRPC 服务](https://mp.weixin.qq.com/s/GShwcGCopXVmxCKnYf5FhA)
2 |
3 | 一般情况下测试 gRPC 服务,都是通过客户端来直接请求服务端。如果客户端还没准备好的话,也可以使用 [BloomRPC](https://appimage.github.io/BloomRPC/) 这样的 GUI 客户端。
4 |
5 | 如果环境不支持安装这种 GUI 客户端的话,那么有没有一种工具,类似于 `curl` 这样的,直接通过终端,在命令行发起请求呢?
6 |
7 | 答案肯定是有的,就是本文要介绍的 `grpcurl`。
8 |
9 | ### gRPC Server
10 |
11 | 首先来写一个简单的 gRPC Server:
12 |
13 | helloworld.proto:
14 |
15 | ```
16 | syntax = "proto3";
17 |
18 | package proto;
19 |
20 | // The greeting service definition.
21 | service Greeter {
22 | // Sends a greeting
23 | rpc SayHello (HelloRequest) returns (HelloReply) {}
24 | }
25 |
26 | // The request message containing the user's name.
27 | message HelloRequest {
28 | string name = 1;
29 | }
30 |
31 | // The response message containing the greetings
32 | message HelloReply {
33 | string message = 1;
34 | }
35 | ```
36 |
37 | main.go
38 |
39 | ```go
40 | package main
41 |
42 | import (
43 | "context"
44 | "fmt"
45 | "grpc-hello/proto"
46 | "log"
47 | "net"
48 |
49 | "google.golang.org/grpc"
50 | "google.golang.org/grpc/reflection"
51 | )
52 |
53 | func main() {
54 | lis, err := net.Listen("tcp", ":50051")
55 | if err != nil {
56 | log.Fatalf("failed to listen: %v", err)
57 | }
58 |
59 | server := grpc.NewServer()
60 | // 注册 grpcurl 所需的 reflection 服务
61 | reflection.Register(server)
62 | // 注册业务服务
63 | proto.RegisterGreeterServer(server, &greeter{})
64 |
65 | fmt.Println("grpc server start ...")
66 | if err := server.Serve(lis); err != nil {
67 | log.Fatalf("failed to serve: %v", err)
68 | }
69 | }
70 |
71 | type greeter struct {
72 | }
73 |
74 | func (*greeter) SayHello(ctx context.Context, req *proto.HelloRequest) (*proto.HelloReply, error) {
75 | fmt.Println(req)
76 | reply := &proto.HelloReply{Message: "hello"}
77 | return reply, nil
78 | }
79 |
80 | ```
81 |
82 | 运行服务:
83 |
84 | ```
85 | go run main.go
86 |
87 | server start ...
88 | ```
89 |
90 | ### grpcurl 安装
91 |
92 | 这里我介绍三种方式:
93 |
94 | #### Mac
95 |
96 | ```
97 | brew install grpcurl
98 | ```
99 |
100 | #### Docker
101 |
102 | ```
103 | # Download image
104 | docker pull fullstorydev/grpcurl:latest
105 | # Run the tool
106 | docker run fullstorydev/grpcurl api.grpc.me:443 list
107 | ```
108 |
109 | #### go tool
110 |
111 | 如果有 Go 环境的话,可以通过 go tool 来安装:
112 |
113 | ```
114 | go install github.com/fullstorydev/grpcurl/cmd/grpcurl@latest
115 | ```
116 |
117 | ### grpcurl 使用
118 |
119 | 在使用 grpcurl 时,需要通过 `-cert` 和 `-key` 参数设置公钥和私钥文件,表示链接启用了 TLS 协议的服务。
120 |
121 | 对于没有启用 TLS 协议的 gRPC 服务,通过 `-plaintext` 参数忽略 TLS 证书的验证过程。
122 |
123 | 如果是 Unix Socket 协议,则需要指定 `-unix` 参数。
124 |
125 | **查看服务列表:**
126 |
127 | ```
128 | grpcurl -plaintext 127.0.0.1:50051 list
129 | ```
130 |
131 | 输出:
132 |
133 | ```
134 | grpc.reflection.v1alpha.ServerReflection
135 | proto.Greeter
136 | ```
137 |
138 | **查看某个服务的方法列表:**
139 |
140 | ```
141 | grpcurl -plaintext 127.0.0.1:50051 list proto.Greeter
142 | ```
143 |
144 | 输出:
145 |
146 | ```
147 | proto.Greeter.SayHello
148 | ```
149 |
150 | **查看方法定义:**
151 |
152 | ```
153 | grpcurl -plaintext 127.0.0.1:50051 describe proto.Greeter.SayHello
154 | ```
155 |
156 | 输出:
157 |
158 | ```
159 | proto.Greeter.SayHello is a method:
160 | rpc SayHello ( .proto.HelloRequest ) returns ( .proto.HelloReply );
161 | ```
162 |
163 | **查看请求参数:**
164 |
165 | ```
166 | grpcurl -plaintext 127.0.0.1:50051 describe proto.HelloRequest
167 | ```
168 |
169 | 输出:
170 |
171 | ```
172 | proto.HelloRequest is a message:
173 | message HelloRequest {
174 | string name = 1;
175 | }
176 | ```
177 |
178 | **请求服务:**
179 |
180 | ```
181 | grpcurl -d '{"name": "zhangsan"}' -plaintext 127.0.0.1:50051 proto.Greeter.SayHello
182 | ```
183 |
184 | 输出:
185 |
186 | ```
187 | {
188 | "message": "hello"
189 | }
190 | ```
191 |
192 | `-d` 参数后面也可以跟 `@`,表示从标准输入读取 json 参数,一般用于输入比较复杂的 json 数据,也可以用于测试流方法。
193 |
194 | ```
195 | grpcurl -d @ -plaintext 127.0.0.1:50051 proto.Greeter.SayHello
196 | ```
197 |
198 | ### 可能遇到的错误
199 |
200 | 可能会遇到三个报错:
201 |
202 | **1、gRPC Server 未启用 TLS:**
203 |
204 | 报错信息:
205 |
206 | ```
207 | Failed to dial target host "127.0.0.1:50051": tls: first record does not look like a TLS handshake
208 | ```
209 |
210 | **解决:**
211 |
212 | 请求时增加参数:`-plaintext`,参考上面的命令。
213 |
214 | **2、服务没有启动 reflection 反射服务**
215 |
216 | 报错信息:
217 |
218 | ```
219 | Failed to list services: server does not support the reflection API
220 | ```
221 |
222 | **解决:**
223 |
224 | 这行代码是关键,一定要包含:
225 |
226 | ```go
227 | // 注册 grpcurl 所需的 reflection 服务
228 | reflection.Register(server)
229 | ```
230 |
231 | **3、参数格式错误:**
232 |
233 | 报错信息:
234 |
235 | ```
236 | Error invoking method "greet.Greeter/SayHello": error getting request data: invalid character 'n' looking for beginning of object key string
237 | ```
238 |
239 | **解决:**
240 |
241 | `-d` 后面参数为 json 格式,并且需要使用 `''` 包裹起来。
242 |
243 | ### 总结
244 |
245 | 用这个工具做一些简单的测试还是相当方便的,上手也简单。只要掌握文中提到的几条命令,基本可以涵盖大部分的测试需求了。
246 |
247 | ---
248 |
249 | **扩展阅读:**
250 |
251 | 1. https://appimage.github.io/BloomRPC/
252 | 2. https://github.com/fullstorydev/grpcurl
253 |
254 | 文章中的脑图和源码都上传到了 GitHub,有需要的同学可自行下载。
255 |
256 | **地址:** https://github.com/yongxinz/gopher/tree/main/blog
257 |
258 | 关注公众号 **AlwaysBeta**,回复「**goebook**」领取 Go 编程经典书籍。
259 |
260 |
261 |
262 |
263 |
264 | **往期文章列表:**
265 |
266 | 1. [被 Docker 日志坑惨了](https://mp.weixin.qq.com/s/3Tkc15dTCEDUAZaZ88pcSQ)
267 | 2. [推荐三个实用的 Go 开发工具](https://mp.weixin.qq.com/s/3GLMLhegB3wF5_62mpmePA)
268 | 3. [这个 TCP 问题你得懂:Cannot assign requested address](https://mp.weixin.qq.com/s/-cThzr5N2w3IEYYf-duCDA)
269 |
270 |
271 | **Go 专栏文章列表:**
272 |
273 | 1. [Go 专栏|开发环境搭建以及开发工具 VS Code 配置](https://mp.weixin.qq.com/s/x1OW--3mwSTjgB2HaKGVVA)
274 | 2. [Go 专栏|变量和常量的声明与赋值](https://mp.weixin.qq.com/s/cIceTj02bGa0BYqu-JN1Bg)
275 | 3. [Go 专栏|基础数据类型:整数、浮点数、复数、布尔值和字符串](https://mp.weixin.qq.com/s/aotpxglSGRFfl6A1xPN-dw)
276 | 4. [Go 专栏|复合数据类型:数组和切片 slice](https://mp.weixin.qq.com/s/MnjIeJPUAA6n48o4yns3hg)
277 | 5. [Go 专栏|复合数据类型:字典 map 和 结构体 struct](https://mp.weixin.qq.com/s/1unl6K9xHxy4V3KukORC3A)
278 | 6. [Go 专栏|流程控制,一网打尽](https://mp.weixin.qq.com/s/TbjT1dmTvwiKCzzbWc23kA)
279 | 7. [Go 专栏|函数那些事](https://mp.weixin.qq.com/s/RKpyVrhtSk9pXMWNVpWYjQ)
280 | 8. [Go 专栏|错误处理:defer,panic 和 recover](https://mp.weixin.qq.com/s/qYZXfAifBxwl1cDDaP0FNA)
281 | 9. [Go 专栏|说说方法](https://mp.weixin.qq.com/s/qvFipY0pnmqxok6CVKquvg)
282 | 10. [Go 专栏|接口 interface](https://mp.weixin.qq.com/s/g7ngRIxxbd-M8K_sL_M4KQ)
283 | 11. [Go 专栏|并发编程:goroutine,channel 和 sync](https://mp.weixin.qq.com/s/VG4CSfT2OfxA6nfygWLSyw)
--------------------------------------------------------------------------------
/blog/05-开源项目|Go 开发的一款分布式唯一 ID 生成系统.md:
--------------------------------------------------------------------------------
1 | **原文连接:** [开源项目|Go 开发的一款分布式唯一 ID 生成系统](https://mp.weixin.qq.com/s/tCGYTlB4nJH1ClViFQJ6Cw)
2 |
3 | 今天跟大家介绍一个开源项目:[**id-maker**](https://github.com/yongxinz/id-maker),主要功能是用来在分布式环境下生成唯一 ID。上周停更了一周,也是用来开发和测试这个项目的相关代码。
4 |
5 | 美团有一个开源项目叫 [**Leaf**](https://github.com/Meituan-Dianping/Leaf),使用 Java 开发。本项目就是在此思路的基础上,使用 Go 开发实现的。
6 |
7 | 项目整体代码量并不多,不管是想要在实际生产环境中使用,还是想找个项目练手,我觉得都是一个不错的选择。
8 |
9 | ### 项目背景
10 |
11 | 在大部分系统中,全局唯一 ID 都是一个强需求。比如快递,外卖,电影等,都需要生成唯一 ID 来保证单号唯一。
12 |
13 | 那业务系统对 ID 号的要求有哪些呢?
14 |
15 | 1. **全局唯一性**:不能出现重复的 ID 号,既然是唯一标识,这是最基本的要求。
16 | 2. **趋势递增**:在 MySQL InnoDB 引擎中使用的是聚集索引,由于多数 RDBMS 使用 B-tree 的数据结构来存储索引数据,在主键的选择上面我们应该尽量使用有序的主键保证写入性能。
17 | 3. **单调递增**:保证下一个 ID 一定大于上一个 ID,例如事务版本号、IM 增量消息、排序等特殊需求。
18 | 4. **信息安全**:如果 ID 是连续的,恶意用户的扒取工作就非常容易做了,直接按照顺序下载指定 URL 即可;如果是订单号就更危险了,竞对可以直接知道我们一天的单量。所以在一些应用场景下,会需要 ID 无规则、不规则。
19 |
20 | 在此背景下,有一个高可用的唯一 ID 生成系统就很重要了。
21 |
22 | ### 项目使用
23 |
24 | 生成 ID 分两种方式:
25 |
26 | 1. 根据数据库生成 ID。
27 | 2. 根据雪花算法生成 ID。
28 |
29 | 使用上提供两种方式来调用接口:
30 |
31 | 1. HTTP 方式
32 | 2. gRPC 方式
33 |
34 | #### HTTP 方式
35 |
36 | 1、健康检查:
37 |
38 | ```
39 | curl http://127.0.0.1:8080/ping
40 | ```
41 |
42 | 2、获取 ID:
43 |
44 | 获取 tag 是 test 的 ID:
45 |
46 | ```
47 | curl http://127.0.0.1:8080/v1/id/test
48 | ```
49 |
50 | 3、获取雪花 ID:
51 |
52 | ```
53 | curl http://127.0.0.1:8080/v1/snowid
54 | ```
55 |
56 | #### gRPC 方式
57 |
58 | 1、获取 ID:
59 |
60 | ```
61 | grpcurl -plaintext -d '{"tag":"test"}' -import-path $HOME/src/id-maker/internal/controller/rpc/proto -proto segment.proto localhost:50051 proto.Gid/GetId
62 | ```
63 |
64 | 2、获取雪花 ID:
65 |
66 | ```
67 | grpcurl -plaintext -import-path $HOME/src/id-maker/internal/controller/rpc/proto -proto segment.proto localhost:50051 proto.Gid/GetSnowId
68 | ```
69 |
70 | #### 本地开发
71 |
72 | ```
73 | # Run MySQL
74 | $ make compose-up
75 |
76 | # Run app with migrations
77 | $ make run
78 | ```
79 |
80 | ### 项目架构
81 |
82 | 项目使用 [**go-clean-template**](https://github.com/evrone/go-clean-template) 架构模板开发,目录结构如下:
83 |
84 | 
85 |
86 | 下面对各目录做一个简要说明:
87 |
88 | - **cmd**:程序入口
89 | - **config**:配置文件
90 | - **docs**:生成的项目文档
91 | - **integration-test**:整合测试
92 | - **internal**:业务代码
93 | - **pkg**:一些调用的包
94 |
95 | 借用官方的两张图:
96 |
97 | 
98 |
99 | 整体的层次关系是这样的,最里面是 models,定义我们的表结构,然后中间是业务逻辑层,业务逻辑层会提供接口,给最外层的 API 来调用,最外层就是一些工具和调用入口。
100 |
101 | 这样做的最大好处就是解耦,不管最外层如何变化,只要在业务逻辑层实现对应接口即可,核心代码可能根本不需要改变。
102 |
103 | 所以,它们之间的调用关系看起来是这样的:
104 |
105 | 
106 |
107 | ```
108 | HTTP > usecase
109 | usecase > repository (Postgres)
110 | usecase < repository (Postgres)
111 | HTTP < usecase
112 | ```
113 |
114 | 以上就是本项目的全部内容,如果大家感兴趣的话,欢迎给我留言交流,要是能给个 **star** 那就太好了。
115 |
116 | ---
117 |
118 | **项目地址:** :[**id-maker**](https://github.com/yongxinz/id-maker)
119 |
120 | 关注公众号 **AlwaysBeta**,回复「**goebook**」领取 Go 编程经典书籍。
121 |
122 |
123 |
124 |
125 |
126 | **往期文章:**
127 |
128 | - [听说,99% 的 Go 程序员都被 defer 坑过](https://mp.weixin.qq.com/s/1T6Z74Wri27Ap8skeJiyWQ)
129 | - [测试小姐姐问我 gRPC 怎么用,我直接把这篇文章甩给了她](https://mp.weixin.qq.com/s/qdI2JqpMq6t2KN1byHaNCQ)
130 | - [gRPC,爆赞](https://mp.weixin.qq.com/s/1Xbca4Dv0akonAZerrChgA)
131 | - [使用 grpcurl 通过命令行访问 gRPC 服务](https://mp.weixin.qq.com/s/GShwcGCopXVmxCKnYf5FhA)
132 | - [推荐三个实用的 Go 开发工具](https://mp.weixin.qq.com/s/3GLMLhegB3wF5_62mpmePA)
133 |
134 | **推荐阅读:**
135 |
136 | - [go-clean-template](https://github.com/evrone/go-clean-template)
137 | - [hwholiday/gid](https://github.com/hwholiday/gid)
138 | - [Leaf——美团点评分布式ID生成系统](https://tech.meituan.com/2017/04/21/mt-leaf.html)
--------------------------------------------------------------------------------
/blog/example/grpc-hello/go.mod:
--------------------------------------------------------------------------------
1 | module grpc-hello
2 |
3 | go 1.16
4 |
5 | require (
6 | github.com/golang/protobuf v1.5.2
7 | google.golang.org/grpc v1.41.0
8 | )
9 |
--------------------------------------------------------------------------------
/blog/example/grpc-hello/main.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "context"
5 | "fmt"
6 | "grpc-hello/proto"
7 | "log"
8 | "net"
9 |
10 | "google.golang.org/grpc"
11 | "google.golang.org/grpc/reflection"
12 | )
13 |
14 | func main() {
15 | lis, err := net.Listen("tcp", ":50051")
16 | if err != nil {
17 | log.Fatalf("failed to listen: %v", err)
18 | }
19 |
20 | server := grpc.NewServer()
21 | // 注册 grpcurl 所需的 reflection 服务
22 | reflection.Register(server)
23 | // 注册业务服务
24 | proto.RegisterGreeterServer(server, &greeter{})
25 |
26 | fmt.Println("grpc server start ...")
27 | if err := server.Serve(lis); err != nil {
28 | log.Fatalf("failed to serve: %v", err)
29 | }
30 | }
31 |
32 | type greeter struct {
33 | }
34 |
35 | func (*greeter) SayHello(ctx context.Context, req *proto.HelloRequest) (*proto.HelloReply, error) {
36 | fmt.Println(req)
37 | reply := &proto.HelloReply{Message: "hello"}
38 | return reply, nil
39 | }
40 |
--------------------------------------------------------------------------------
/blog/example/grpc-hello/proto/helloworld.proto:
--------------------------------------------------------------------------------
1 | syntax = "proto3";
2 |
3 | package proto;
4 |
5 | // The greeting service definition.
6 | service Greeter {
7 | // Sends a greeting
8 | rpc SayHello (HelloRequest) returns (HelloReply) {}
9 | }
10 |
11 | // The request message containing the user's name.
12 | message HelloRequest {
13 | string name = 1;
14 | }
15 |
16 | // The response message containing the greetings
17 | message HelloReply {
18 | string message = 1;
19 | }
--------------------------------------------------------------------------------
/blog/example/grpc-hello/proto/make.sh:
--------------------------------------------------------------------------------
1 |
2 | protoc --go_out=plugins=grpc:. *.proto
--------------------------------------------------------------------------------
/blog/pic/01-json-to-struct.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/yongxinz/gopher/030046cf44fb5b2a2c90112b3d179ae9ba9df000/blog/pic/01-json-to-struct.png
--------------------------------------------------------------------------------
/blog/pic/01-yaml-to-struct.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/yongxinz/gopher/030046cf44fb5b2a2c90112b3d179ae9ba9df000/blog/pic/01-yaml-to-struct.png
--------------------------------------------------------------------------------
/blog/pic/01-实用工具.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/yongxinz/gopher/030046cf44fb5b2a2c90112b3d179ae9ba9df000/blog/pic/01-实用工具.png
--------------------------------------------------------------------------------
/blog/pic/02-grpc-gui-client.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/yongxinz/gopher/030046cf44fb5b2a2c90112b3d179ae9ba9df000/blog/pic/02-grpc-gui-client.png
--------------------------------------------------------------------------------
/blog/pic/03-grpc.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/yongxinz/gopher/030046cf44fb5b2a2c90112b3d179ae9ba9df000/blog/pic/03-grpc.png
--------------------------------------------------------------------------------
/blog/pic/05-go-clean-template-1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/yongxinz/gopher/030046cf44fb5b2a2c90112b3d179ae9ba9df000/blog/pic/05-go-clean-template-1.png
--------------------------------------------------------------------------------
/blog/pic/05-go-clean-template-2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/yongxinz/gopher/030046cf44fb5b2a2c90112b3d179ae9ba9df000/blog/pic/05-go-clean-template-2.png
--------------------------------------------------------------------------------
/blog/pic/05-id-maker.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/yongxinz/gopher/030046cf44fb5b2a2c90112b3d179ae9ba9df000/blog/pic/05-id-maker.png
--------------------------------------------------------------------------------
/example/go_set_test.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "testing"
5 | )
6 |
7 | const num = int(1 << 24)
8 |
9 | // 测试 bool 类型
10 | func Benchmark_SetWithBoolValueWrite(b *testing.B) {
11 | set := make(map[int]bool)
12 | for i := 0; i < num; i++ {
13 | set[i] = true
14 | }
15 | }
16 |
17 | // 测试 interface{} 类型
18 | func Benchmark_SetWithInterfaceValueWrite(b *testing.B) {
19 | set := make(map[int]interface{})
20 | for i := 0; i < num; i++ {
21 | set[i] = struct{}{}
22 | }
23 | }
24 |
25 | // 测试 int 类型
26 | func Benchmark_SetWithIntValueWrite(b *testing.B) {
27 | set := make(map[int]int)
28 | for i := 0; i < num; i++ {
29 | set[i] = 0
30 | }
31 | }
32 |
33 | // 测试 struct{} 类型
34 | func Benchmark_SetWithStructValueWrite(b *testing.B) {
35 | set := make(map[int]struct{})
36 | for i := 0; i < num; i++ {
37 | set[i] = struct{}{}
38 | }
39 | }
40 |
--------------------------------------------------------------------------------
/example/result.txt:
--------------------------------------------------------------------------------
1 | goos: darwin
2 | goarch: amd64
3 | pkg: gopher/example
4 | cpu: Intel(R) Core(TM) i5-7360U CPU @ 2.30GHz
5 | Benchmark_SetWithBoolValueWrite
6 | Benchmark_SetWithBoolValueWrite-4 1 3978074842 ns/op 883632216 B/op 614563 allocs/op
7 | Benchmark_SetWithBoolValueWrite-4 1 3495463383 ns/op 883497584 B/op 613347 allocs/op
8 | Benchmark_SetWithBoolValueWrite-4 1 3414244101 ns/op 883626856 B/op 614504 allocs/op
9 | Benchmark_SetWithInterfaceValueWrite
10 | Benchmark_SetWithInterfaceValueWrite-4 1 5441186730 ns/op 1981569296 B/op 613814 allocs/op
11 | Benchmark_SetWithInterfaceValueWrite-4 1 4550611792 ns/op 1981700648 B/op 614450 allocs/op
12 | Benchmark_SetWithInterfaceValueWrite-4 1 4573016917 ns/op 1981301312 B/op 612533 allocs/op
13 | Benchmark_SetWithIntValueWrite
14 | Benchmark_SetWithIntValueWrite-4 1 4266794219 ns/op 1412198232 B/op 614367 allocs/op
15 | Benchmark_SetWithIntValueWrite-4 1 4742827612 ns/op 1412255680 B/op 614639 allocs/op
16 | Benchmark_SetWithIntValueWrite-4 1 4203061495 ns/op 1412305928 B/op 614987 allocs/op
17 | Benchmark_SetWithStructValueWrite
18 | Benchmark_SetWithStructValueWrite-4 1 3491457170 ns/op 802402808 B/op 613278 allocs/op
19 | Benchmark_SetWithStructValueWrite-4 1 3359559632 ns/op 802401680 B/op 613492 allocs/op
20 | Benchmark_SetWithStructValueWrite-4 1 3549251996 ns/op 802452016 B/op 614126 allocs/op
21 | PASS
22 | ok gopher/example 49.952s
23 |
--------------------------------------------------------------------------------
/go-zero/go-zero 是如何实现计数器限流的?.md:
--------------------------------------------------------------------------------
1 | **原文链接:** [如何实现计数器限流?](https://mp.weixin.qq.com/s/CTemkZ2aKPCPTuQiDJri0Q)
2 |
3 | 上一篇文章 [go-zero 是如何做路由管理的?](https://mp.weixin.qq.com/s/uTJ1En-BXiLvH45xx0eFsA) 介绍了路由管理,这篇文章来说说限流,主要介绍计数器限流算法,具体的代码实现,我们还是来分析微服务框架 go-zero 的源码。
4 |
5 | 在微服务架构中,一个服务可能需要频繁地与其他服务交互,而过多的请求可能导致性能下降或系统崩溃。为了确保系统的稳定性和高可用性,限流算法应运而生。
6 |
7 | 限流算法允许在给定时间段内,对服务的请求流量进行控制和调整,以防止资源耗尽和服务过载。
8 |
9 | 计数器限流算法主要有两种实现方式,分别是:
10 |
11 | 1. 固定窗口计数器
12 | 2. 滑动窗口计数器
13 |
14 | 下面分别来介绍。
15 |
16 | ## 固定窗口计数器
17 |
18 | 算法概念如下:
19 |
20 | - 将时间划分为多个窗口;
21 | - 在每个窗口内每有一次请求就将计数器加一;
22 | - 如果计数器超过了限制数量,则本窗口内所有的请求都被丢弃当时间到达下一个窗口时,计数器重置。
23 |
24 | 
25 |
26 | 固定窗口计数器是最为简单的算法,但这个算法有时会让通过请求量允许为限制的两倍。
27 |
28 | 
29 |
30 | 考虑如下情况:限制 1 秒内最多通过 5 个请求,在第一个窗口的最后半秒内通过了 5 个请求,第二个窗口的前半秒内又通过了 5 个请求。这样看来就是在 1 秒内通过了 10 个请求。
31 |
32 | ## 滑动窗口计数器
33 |
34 | 算法概念如下:
35 |
36 | - 将时间划分为多个区间;
37 | - 在每个区间内每有一次请求就将计数器加一维持一个时间窗口,占据多个区间;
38 | - 每经过一个区间的时间,则抛弃最老的一个区间,并纳入最新的一个区间;
39 | - 如果当前窗口内区间的请求计数总和超过了限制数量,则本窗口内所有的请求都被丢弃。
40 |
41 | 
42 |
43 | 滑动窗口计数器是通过将窗口再细分,并且按照时间滑动,这种算法避免了固定窗口计数器带来的双倍突发请求,但时间区间的精度越高,算法所需的空间容量就越大。
44 |
45 | ## go-zero 实现
46 |
47 | go-zero 实现的是**固定窗口**的方式,计算一段时间内对同一个资源的访问次数,如果超过指定的 `limit`,则拒绝访问。当然如果在一段时间内访问不同的资源,每一个资源访问量都不超过 `limit`,此种情况是不会拒绝的。
48 |
49 | 而在一个分布式系统中,存在多个微服务提供服务。所以当瞬间的流量同时访问同一个资源,如何让计数器在分布式系统中正常计数?
50 |
51 | 这里要解决的一个主要问题就是计算的原子性,保证多个计算都能得到正确结果。
52 |
53 | 通过以下两个方面来解决:
54 |
55 | - 使用 redis 的 `incrby` 做资源访问计数
56 | - 采用 lua script 做整个窗口计算,保证计算的原子性
57 |
58 | 接下来先看一下 lua script 的源码:
59 |
60 | ```go
61 | // core/limit/periodlimit.go
62 |
63 | const periodScript = `local limit = tonumber(ARGV[1])
64 | local window = tonumber(ARGV[2])
65 | local current = redis.call("INCRBY", KEYS[1], 1)
66 | if current == 1 then
67 | redis.call("expire", KEYS[1], window)
68 | end
69 | if current < limit then
70 | return 1
71 | elseif current == limit then
72 | return 2
73 | else
74 | return 0
75 | end`
76 | ```
77 |
78 | 主要就是使用 `INCRBY` 命令来实现,第一次请求需要给 key 加上一个过期时间,到达过期时间之后,key 过期被清楚,重新计数。
79 |
80 | 限流器初始化:
81 |
82 | ```go
83 | type (
84 | // PeriodOption defines the method to customize a PeriodLimit.
85 | PeriodOption func(l *PeriodLimit)
86 |
87 | // A PeriodLimit is used to limit requests during a period of time.
88 | PeriodLimit struct {
89 | period int // 窗口大小,单位 s
90 | quota int // 请求上限
91 | limitStore *redis.Redis
92 | keyPrefix string // key 前缀
93 | align bool
94 | }
95 | )
96 |
97 | // NewPeriodLimit returns a PeriodLimit with given parameters.
98 | func NewPeriodLimit(period, quota int, limitStore *redis.Redis, keyPrefix string,
99 | opts ...PeriodOption) *PeriodLimit {
100 | limiter := &PeriodLimit{
101 | period: period,
102 | quota: quota,
103 | limitStore: limitStore,
104 | keyPrefix: keyPrefix,
105 | }
106 |
107 | for _, opt := range opts {
108 | opt(limiter)
109 | }
110 |
111 | return limiter
112 | }
113 | ```
114 |
115 | 调用限流:
116 |
117 | ```go
118 | // key 就是需要被限制的资源标识
119 | func (h *PeriodLimit) Take(key string) (int, error) {
120 | return h.TakeCtx(context.Background(), key)
121 | }
122 |
123 | // TakeCtx requests a permit with context, it returns the permit state.
124 | func (h *PeriodLimit) TakeCtx(ctx context.Context, key string) (int, error) {
125 | resp, err := h.limitStore.EvalCtx(ctx, periodScript, []string{h.keyPrefix + key}, []string{
126 | strconv.Itoa(h.quota),
127 | strconv.Itoa(h.calcExpireSeconds()),
128 | })
129 | if err != nil {
130 | return Unknown, err
131 | }
132 |
133 | code, ok := resp.(int64)
134 | if !ok {
135 | return Unknown, ErrUnknownCode
136 | }
137 |
138 | switch code {
139 | case internalOverQuota: // 超过上限
140 | return OverQuota, nil
141 | case internalAllowed: // 未超过,允许访问
142 | return Allowed, nil
143 | case internalHitQuota: // 正好达到限流上限
144 | return HitQuota, nil
145 | default:
146 | return Unknown, ErrUnknownCode
147 | }
148 | }
149 | ```
150 |
151 | 上文已经介绍了,固定时间窗口会有临界突发问题,并不是那么严谨,下篇文章我们来介绍令牌桶限流。
152 |
153 | 以上就是本文的全部内容,如果觉得还不错的话欢迎**点赞**,**转发**和**关注**,感谢支持。
154 |
155 | ***
156 |
157 | **参考文章:**
158 |
159 | - https://juejin.cn/post/6895928148521648141
160 | - https://juejin.cn/post/7051406419823689765
161 | - https://www.infoq.cn/article/Qg2tX8fyw5Vt-f3HH673
162 |
163 | **推荐阅读:**
164 |
165 | - [go-zero 是如何做路由管理的?](https://mp.weixin.qq.com/s/uTJ1En-BXiLvH45xx0eFsA)
166 |
--------------------------------------------------------------------------------
/go.mod:
--------------------------------------------------------------------------------
1 | module gopher
2 |
3 | go 1.17
4 |
5 | require golang.org/x/net v0.0.0-20211216030914-fe4d6282115f
6 |
7 | require (
8 | golang.org/x/perf v0.0.0-20211012211434-03971e389cd3 // indirect
9 | golang.org/x/sys v0.0.0-20210423082822-04245dca01da // indirect
10 | )
11 |
--------------------------------------------------------------------------------
/go.sum:
--------------------------------------------------------------------------------
1 | cloud.google.com/go v0.0.0-20170206221025-ce650573d812/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
2 | github.com/GoogleCloudPlatform/cloudsql-proxy v0.0.0-20190129172621-c8b1d7a94ddf/go.mod h1:aJ4qN3TfrelA6NZ6AXsXRfmEVaYin3EDbSPJrKS8OXo=
3 | github.com/aclements/go-gg v0.0.0-20170118225347-6dbb4e4fefb0/go.mod h1:55qNq4vcpkIuHowELi5C8e+1yUHtoLoOUR9QU5j7Tes=
4 | github.com/aclements/go-moremath v0.0.0-20161014184102-0ff62e0875ff/go.mod h1:idZL3yvz4kzx1dsBOAC+oYv6L92P1oFEhUXUB1A/lwQ=
5 | github.com/go-sql-driver/mysql v1.4.1/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w=
6 | github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
7 | github.com/gonum/blas v0.0.0-20181208220705-f22b278b28ac/go.mod h1:P32wAyui1PQ58Oce/KYkOqQv8cVw1zAapXOl+dRFGbc=
8 | github.com/gonum/floats v0.0.0-20181209220543-c233463c7e82/go.mod h1:PxC8OnwL11+aosOB5+iEPoV3picfs8tUpkVd0pDo+Kg=
9 | github.com/gonum/internal v0.0.0-20181124074243-f884aa714029/go.mod h1:Pu4dmpkhSyOzRwuXkOgAvijx4o+4YMUJJo9OvPYMkks=
10 | github.com/gonum/lapack v0.0.0-20181123203213-e4cdc5a0bff9/go.mod h1:XA3DeT6rxh2EAE789SSiSJNqxPaC0aE9J8NTOI0Jo/A=
11 | github.com/gonum/matrix v0.0.0-20181209220409-c518dec07be9/go.mod h1:0EXg4mc1CNP0HCqCz+K4ts155PXIlUywf0wqN+GfPZw=
12 | github.com/googleapis/gax-go v0.0.0-20161107002406-da06d194a00e/go.mod h1:SFVmujtThgffbyetf+mdk2eWhX2bMyUtNHzFKcPA9HY=
13 | github.com/mattn/go-sqlite3 v1.14.5/go.mod h1:WVKg1VTActs4Qso6iwGbiFih2UIHo0ENGwNd0Lj+XmI=
14 | golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
15 | golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
16 | golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
17 | golang.org/x/net v0.0.0-20211216030914-fe4d6282115f h1:hEYJvxw1lSnWIl8X9ofsYMklzaDs90JI2az5YMd4fPM=
18 | golang.org/x/net v0.0.0-20211216030914-fe4d6282115f/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
19 | golang.org/x/oauth2 v0.0.0-20170207211851-4464e7848382/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
20 | golang.org/x/perf v0.0.0-20211012211434-03971e389cd3 h1:TxpziJvKtFH7T75kH/tX3QELShnXGyWX1iVgw8hU9EY=
21 | golang.org/x/perf v0.0.0-20211012211434-03971e389cd3/go.mod h1:KRSrLY7jerMEa0Ih7gBheQ3FYDiSx6liMnniX1o3j2g=
22 | golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
23 | golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
24 | golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
25 | golang.org/x/sys v0.0.0-20210423082822-04245dca01da h1:b3NXsE2LusjYGGjL5bxEVZZORm/YEFFrWFjR8eFrw/c=
26 | golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
27 | golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
28 | golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
29 | golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
30 | golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
31 | google.golang.org/api v0.0.0-20170206182103-3d017632ea10/go.mod h1:4mhQ8q/RsB7i+udVvVy5NUi08OU8ZlA0gRVgrF7VFY0=
32 | google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
33 | google.golang.org/grpc v0.0.0-20170208002647-2a6bf6142e96/go.mod h1:yo6s7OP7yaDglbqo1J04qKzAhqBH6lvTonzMVmEdcZw=
34 |
--------------------------------------------------------------------------------
/interview/Go 语言 map 如何顺序读取?.md:
--------------------------------------------------------------------------------
1 | **原文链接:** [Go 语言 map 如何顺序读取?](https://mp.weixin.qq.com/s/iScSgfpSE2y14GH7JNRJSA)
2 |
3 | Go 语言中的 map 是一种非常强大的数据结构,它允许我们快速地存储和检索键值对。
4 |
5 | 然而,当我们遍历 map 时,会有一个有趣的现象,那就是输出的键值对顺序是不确定的。
6 |
7 | ## 现象
8 |
9 | 先看一段代码示例:
10 |
11 | ```go
12 | package main
13 |
14 | import "fmt"
15 |
16 | func main() {
17 | m := map[string]int{
18 | "apple": 1,
19 | "banana": 2,
20 | "orange": 3,
21 | }
22 |
23 | for k, v := range m {
24 | fmt.Printf("key=%s, value=%d\n", k, v)
25 | }
26 | }
27 | ```
28 |
29 | 当我们多执行几次这段代码时,就会发现,输出的顺序是不同的。
30 |
31 | ## 原因
32 |
33 | 首先,Go 语言 map 的底层实现是哈希表,在进行插入时,会对 key 进行 hash 运算。这也就导致了数据不是按顺序存储的,和遍历的顺序也就会不一致。
34 |
35 | 第二,map 在扩容后,会发生 key 的搬迁,原来落在同一个 bucket 中的 key,搬迁后,有些 key 可能就到其他 bucket 了。
36 |
37 | 而遍历的过程,就是按顺序遍历 bucket,同时按顺序遍历 bucket 中的 key。
38 |
39 | 搬迁后,key 的位置发生了重大的变化,有些 key 被搬走了,有些 key 则原地不动。这样,遍历 map 的结果就不可能按原来的顺序了。
40 |
41 | 最后,也是最有意思的一点。
42 |
43 | 那如果说我已经初始化好了一个 map,并且不对这个 map 做任何操作,也就是不会发生扩容,那遍历顺序是固定的吗?
44 |
45 | 答:也不是。
46 |
47 | Go 杜绝了这种做法,主要是担心程序员会在开发过程中依赖稳定的遍历顺序,因为这是不对的。
48 |
49 | 所以在遍历 map 时,并不是固定地从 0 号 bucket 开始遍历,每次都是从一个随机值序号的 bucket 开始遍历,并且是从这个 bucket 的一个随机序号的 cell 开始遍历。
50 |
51 | ## 如何顺序读取
52 |
53 | 如果希望按照特定顺序遍历 map,可以先将键或值存储到切片中,然后对切片进行排序,最后再遍历切片。
54 |
55 | 改造一下上面的代码,让它按顺序输出:
56 |
57 | ```go
58 | package main
59 |
60 | import (
61 | "fmt"
62 | "sort"
63 | )
64 |
65 | func main() {
66 | m := map[string]int{
67 | "apple": 1,
68 | "banana": 2,
69 | "orange": 3,
70 | }
71 |
72 | // 将 map 中的键存储到切片中
73 | keys := make([]string, 0, len(m))
74 | for k := range m {
75 | keys = append(keys, k)
76 | }
77 |
78 | // 对切片进行排序
79 | sort.Strings(keys)
80 |
81 | // 按照排序后的顺序遍历 map
82 | for _, k := range keys {
83 | fmt.Printf("key=%s, value=%d\n", k, m[k])
84 | }
85 | }
86 | ```
87 |
88 | 在上面的代码中,首先将 map 中的键存储到一个切片中,然后对切片进行排序。
89 |
90 | 最后,按照排序后的顺序遍历 map。这样就可以按照特定顺序输出键值对了。
91 |
92 | 以上就是本文的全部内容,如果觉得还不错的话欢迎**点赞**,**转发**和**关注**,感谢支持。
93 |
94 | ***
95 |
96 | **参考文章:**
97 |
98 | - https://go.dev/blog/maps
99 | - https://golang.design/go-questions/map/unordered/
100 |
101 | **推荐阅读:**
102 |
103 | * [Go 语言 map 是并发安全的吗?](https://mp.weixin.qq.com/s/4mDzMdMbunR_p94Du65QOA)
104 | * [Go 语言切片是如何扩容的?](https://mp.weixin.qq.com/s/VVM8nqs4mMGdFyCNJx16_g)
105 | * [Go 语言数组和切片的区别](https://mp.weixin.qq.com/s/esaAmAdmV4w3_qjtAzTr4A)
106 | * [Go 语言 new 和 make 关键字的区别](https://mp.weixin.qq.com/s/NBDkI3roHgNgW1iW4e_6cA)
107 | * [为什么 Go 不支持 \[\]T 转换为 \[\]interface](https://mp.weixin.qq.com/s/cwDEgnicK4jkuNpzulU2bw)
108 | * [为什么 Go 语言 struct 要使用 tags](https://mp.weixin.qq.com/s/L7-TJ-CzYfuVrIBWP7Ebaw)
109 |
--------------------------------------------------------------------------------
/interview/Go 语言 new 和 make 关键字的区别.md:
--------------------------------------------------------------------------------
1 | **原文链接:** [Go 语言 new 和 make 关键字的区别](https://mp.weixin.qq.com/s/NBDkI3roHgNgW1iW4e_6cA)
2 |
3 | 本篇文章来介绍一道非常常见的面试题,到底有多常见呢?可能很多面试的开场白就是由此开始的。那就是 new 和 make 这两个内置函数的区别。
4 |
5 | 其实这个问题本身并不复杂,简单来说就是,new 只分配内存,而 make 只能用于 slice、map 和 chan 的初始化,下面我们就来详细介绍一下。
6 |
7 | ## new
8 |
9 | new 是一个内置函数,它会分配一段内存,并返回指向该内存的指针。
10 |
11 | 其函数签名如下:
12 |
13 | ### 源码
14 |
15 | ```go
16 | // The new built-in function allocates memory. The first argument is a type,
17 | // not a value, and the value returned is a pointer to a newly
18 | // allocated zero value of that type.
19 | func new(Type) *Type
20 | ```
21 |
22 | 从上面的代码可以看出,new 函数只接受一个参数,这个参数是一个类型,并且返回一个指向该类型内存地址的指针。
23 |
24 | 同时 new 函数会把分配的内存置为零,也就是类型的零值。
25 |
26 | ### 使用
27 |
28 | 使用 new 函数为变量分配内存空间:
29 |
30 | ```go
31 | p1 := new(int)
32 | fmt.Printf("p1 --> %#v \n ", p1) //(*int)(0xc42000e250)
33 | fmt.Printf("p1 point to --> %#v \n ", *p1) //0
34 |
35 | var p2 *int
36 | i := 0
37 | p2 = &i
38 | fmt.Printf("p2 --> %#v \n ", p2) //(*int)(0xc42000e278)
39 | fmt.Printf("p2 point to --> %#v \n ", *p2) //0
40 | ```
41 |
42 | 上面的代码是等价的,`new(int)` 将分配的空间初始化为 int 的零值,也就是 0,并返回 int 的指针,这和直接声明指针并初始化的效果是相同的。
43 |
44 | 当然,new 函数不仅能够为系统默认的数据类型分配空间,自定义类型也可以使用 new 函数来分配空间,如下所示:
45 |
46 | ```go
47 | type Student struct {
48 | name string
49 | age int
50 | }
51 | var s *Student
52 | s = new(Student) //分配空间
53 | s.name = "zhangsan"
54 | fmt.Println(s)
55 | ```
56 |
57 | 这就是 new 函数,它返回的永远是类型的指针,指针指向分配类型的内存地址。需要注意的是,new 函数只会分配内存空间,但并不会初始化该内存空间。
58 |
59 | ## make
60 |
61 | make 也是用于内存分配的,但是和 new 不同,它只用于 slice、map 和 chan 的内存创建,而且它返回的类型就是这三个类型本身,而不是他们的指针类型。因为这三种类型本身就是引用类型,所以就没有必要返回他们的指针了。
62 |
63 | 其函数签名如下:
64 |
65 | ### 源码
66 |
67 | ```go
68 | // The make built-in function allocates and initializes an object of type
69 | // slice, map, or chan (only). Like new, the first argument is a type, not a
70 | // value. Unlike new, make's return type is the same as the type of its
71 | // argument, not a pointer to it. The specification of the result depends on
72 | // the type:
73 | // Slice: The size specifies the length. The capacity of the slice is
74 | // equal to its length. A second integer argument may be provided to
75 | // specify a different capacity; it must be no smaller than the
76 | // length, so make([]int, 0, 10) allocates a slice of length 0 and
77 | // capacity 10.
78 | // Map: An empty map is allocated with enough space to hold the
79 | // specified number of elements. The size may be omitted, in which case
80 | // a small starting size is allocated.
81 | // Channel: The channel's buffer is initialized with the specified
82 | // buffer capacity. If zero, or the size is omitted, the channel is
83 | // unbuffered.
84 | func make(t Type, size ...IntegerType) Type
85 | ```
86 |
87 | 通过上面的代码可以看出 make 函数的 `t` 参数必须是 slice、map 和 chan 中的一个,并且返回值也是类型本身。
88 |
89 | ### 使用
90 |
91 | 下面用 slice 来举一个例子:
92 |
93 | ```go
94 | var s1 []int
95 | if s1 == nil {
96 | fmt.Printf("s1 is nil --> %#v \n ", s1) // []int(nil)
97 | }
98 |
99 | s2 := make([]int, 3)
100 | if s2 == nil {
101 | fmt.Printf("s2 is nil --> %#v \n ", s2)
102 | } else {
103 | fmt.Printf("s2 is not nill --> %#v \n ", s2)// []int{0, 0, 0}
104 | }
105 | ```
106 |
107 | slice 的零值是 `nil`,但使用 make 初始化之后,slice 内容被类型 int 的零值填充,如:`[]int{0, 0, 0}`。
108 |
109 | map 和 chan 也是类似的,就不多说了。
110 |
111 | ## 总结
112 |
113 | 通过以上分析,总结一下 new 和 make 主要区别如下:
114 |
115 | 1. make 只能用来分配及初始化类型为 slice、map 和 chan 的数据。new 可以分配任意类型的数据;
116 | 2. new 分配返回的是指针,即类型 `*Type`。make 返回类型本身,即 `Type`;
117 | 3. new 分配的空间被清零。make 分配空间后,会进行初始化;
118 |
119 |
120 | 以上就是本文的全部内容,如果觉得还不错的话欢迎**点赞**,**转发**和**关注**,感谢支持。
121 |
122 | ***
123 |
124 | **参考文章:**
125 |
126 | - https://go.dev/doc/effective_go#allocation_new
127 | - http://c.biancheng.net/view/5722.html
128 | - https://sanyuesha.com/2017/07/26/go-make-and-new/
129 |
130 | **推荐阅读:**
131 |
132 | - [为什么 Go 不支持 []T 转换为 []interface](https://mp.weixin.qq.com/s/cwDEgnicK4jkuNpzulU2bw)
133 | - [为什么 Go 语言 struct 要使用 tags](https://mp.weixin.qq.com/s/L7-TJ-CzYfuVrIBWP7Ebaw)
--------------------------------------------------------------------------------
/interview/Go 语言 select 都能做什么?.md:
--------------------------------------------------------------------------------
1 | **原文链接:** [Go 语言 select 都能做什么?](https://mp.weixin.qq.com/s/YyyMzYxMi8I4HEaxzy4c7g)
2 |
3 | 在 Go 语言中,`select` 是一个关键字,用于监听和 `channel` 有关的 IO 操作。
4 |
5 | 通过 `select` 语句,我们可以同时监听多个 `channel`,并在其中任意一个 `channel` 就绪时进行相应的处理。
6 |
7 | 本文将总结一下 `select` 语句的常见用法,以及在使用过程中的注意事项。
8 |
9 | ## 基本语法
10 |
11 | `select` 语句的基本语法如下:
12 |
13 | ```go
14 | select {
15 | case <-channel1:
16 | // 通道 channel1 就绪时的处理逻辑
17 | case data := <-channel2:
18 | // 通道 channel2 就绪时的处理逻辑
19 | default:
20 | // 当没有任何通道就绪时的默认处理逻辑
21 | }
22 | ```
23 |
24 | 看到这个语法,很容易想到 `switch` 语句。
25 |
26 | 虽然 `select` 语句和 `switch` 语句在表面上有些相似,但它们的用途和功能是不同的。
27 |
28 | `switch` 用于条件判断,而 `select` 用于通道操作。不能在 `select` 语句中使用任意类型的条件表达式,只能对通道进行操作。
29 |
30 | ## 使用规则
31 |
32 | 虽然语法简单,但是在使用过程中,还是有一些地方需要注意,我总结了如下四点:
33 |
34 | 1. `select` 语句只能用于通道操作,用于在多个通道之间进行选择,以监听通道的就绪状态,而不是用于其他类型的条件判断。
35 | 2. `select` 语句可以包含多个 `case` 子句,每个 `case` 子句对应一个通道操作。当其中任意一个通道就绪时,相应的 `case` 子句会被执行。
36 | 3. 如果多个通道都已经就绪,`select` 语句会随机选择一个通道来执行。这样确保了多个通道之间的公平竞争。
37 | 4. `select` 语句的执行可能是阻塞的,也可能是非阻塞的。如果没有任何一个通道就绪且没有默认的 `default` 子句,`select` 语句会阻塞,直到有一个通道就绪。如果有 `default` 子句,且没有任何通道就绪,那么 `select` 语句会执行 `default` 子句,从而避免阻塞。
38 |
39 | ## 多路复用
40 |
41 | `select` 最常见的用途之一,同时监听多个通道,并根据它们的就绪状态执行不同的操作。
42 |
43 | ```go
44 | package main
45 |
46 | import (
47 | "fmt"
48 | "time"
49 | )
50 |
51 | func main() {
52 | c1 := make(chan string)
53 | c2 := make(chan string)
54 |
55 | go func() {
56 | time.Sleep(3 * time.Second)
57 | c1 <- "one"
58 | }()
59 |
60 | go func() {
61 | time.Sleep(3 * time.Second)
62 | c2 <- "two"
63 | }()
64 |
65 | select {
66 | case msg := <-c1:
67 | fmt.Println(msg)
68 | case msg := <-c2:
69 | fmt.Println(msg)
70 | }
71 | }
72 | ```
73 |
74 | 执行上面的代码,程序会随机打印 `one` 或者 `two`,如果通道为空的话,程序就会一直阻塞在那里。
75 |
76 | ## 非阻塞通信
77 |
78 | 当通道中没有数据可读或者没有缓冲空间可写时,普通的读写操作将会阻塞。
79 |
80 | 但通过 `select` 语句,我们可以在没有数据就绪时执行默认的逻辑,避免程序陷入无限等待状态。
81 |
82 | ```go
83 | package main
84 |
85 | import (
86 | "fmt"
87 | )
88 |
89 | func main() {
90 | channel := make(chan int)
91 |
92 | select {
93 | case data := <-channel:
94 | fmt.Println("Received:", data)
95 | default:
96 | fmt.Println("No data available.")
97 | }
98 | }
99 | ```
100 |
101 | 执行上面代码,程序会执行 `default` 分支。
102 |
103 | 输出:
104 |
105 | ```go
106 | No data available.
107 | ```
108 |
109 | ## 超时处理
110 |
111 | 通过结合 `select` 和 `time.After` 函数,我们可以在指定时间内等待通道就绪,超过时间后执行相应的逻辑。
112 |
113 | ```go
114 | package main
115 |
116 | import (
117 | "fmt"
118 | "time"
119 | )
120 |
121 | func main() {
122 | channel := make(chan int)
123 |
124 | select {
125 | case data := <-channel:
126 | fmt.Println("Received:", data)
127 | case <-time.After(3 * time.Second):
128 | fmt.Println("Timeout occurred.")
129 | }
130 | }
131 | ```
132 |
133 | 执行上面代码,如果 `channel` 在 `3` 秒内没有数据可读,`select` 会执行 `time.After` 分支。
134 |
135 | 输出:
136 |
137 | ```go
138 | Timeout occurred.
139 | ```
140 |
141 | 以上就是本文的全部内容,如果觉得还不错的话欢迎**点赞**,**转发**和**关注**,感谢支持。
142 |
143 | ***
144 |
145 | **推荐阅读:**
146 |
147 | * [Go 语言 context 都能做什么?](https://mp.weixin.qq.com/s/7IliODEUt3JpEuzL8K_sOg)
148 |
149 |
--------------------------------------------------------------------------------
/interview/Go 语言数组和切片的区别.md:
--------------------------------------------------------------------------------
1 | **原文链接:** [Go 语言数组和切片的区别](https://mp.weixin.qq.com/s/esaAmAdmV4w3_qjtAzTr4A)
2 |
3 | 在 Go 语言中,数组和切片看起来很像,但其实它们又有很多的不同之处,这篇文章就来说说它们到底有哪些不同。
4 |
5 | 另外,这个问题在面试中也经常会被问到,属于入门级题目,看过文章之后,相信你会有一个很好的答案。
6 |
7 | ## 数组
8 |
9 | 数组是同一种数据类型元素的集合,数组在定义时需要指定长度和元素类型。
10 |
11 | 
12 |
13 | 例如:`[4]int` 表示一个包含四个整数的数组,数组的大小是固定的。并且长度是其类型的一部分(`[4]int` 和 `[5]int` 是不同的、不兼容的类型)。
14 |
15 | 数组元素可以通过索引来访问,比如表达式 `s[n]` 表示访问第 `n` 个元素,索引从零开始。
16 |
17 | ### 声明以及初始化
18 |
19 | ```go
20 | func main() {
21 | var nums [3]int // 声明并初始化为默认零值
22 | var nums1 = [4]int{1, 2, 3, 4} // 声明同时初始化
23 | var nums2 = [...]int{1, 2, 3, 4, 5} // ...可以表示后面初始化值的长度
24 | fmt.Println(nums) // [0 0 0]
25 | fmt.Println(nums1) // [1 2 3 4]
26 | fmt.Println(nums2) // [1 2 3 4 5]
27 | }
28 | ```
29 |
30 | ### 函数参数
31 |
32 | 如果数组作为函数的参数,那么实际传递的是一份数组的拷贝,而不是数组的指针。这也就意味着,在函数中修改数组的元素是不会影响到原始数组的。
33 |
34 | 
35 |
36 | ```go
37 | package main
38 |
39 | import (
40 | "fmt"
41 | )
42 |
43 | func Add(numbers [5]int) {
44 | for i := 0; i < len(numbers); i++ {
45 | numbers[i] = numbers[i] + 1
46 | }
47 | fmt.Println("numbers in Add:", numbers) // [2 3 4 5 6]
48 | }
49 |
50 | func main() {
51 | // declare and initialize the array
52 | var numbers [5]int
53 | for i := 0; i < len(numbers); i++ {
54 | numbers[i] = i + 1
55 | }
56 |
57 | Add(numbers)
58 | fmt.Println("numbers in main:", numbers) // [1 2 3 4 5]
59 | }
60 | ```
61 |
62 | ## 切片
63 |
64 | 数组的使用场景相对有限,切片才更加常用。
65 |
66 | 切片(Slice)是一个拥有相同类型元素的可变长度的序列。它是基于数组类型做的一层封装。它非常灵活,支持自动扩容。
67 |
68 | 
69 |
70 | 切片是一种引用类型,它有三个属性:**指针**,**长度**和**容量**。
71 |
72 | 1. 指针:指向 slice 可以访问到的第一个元素。
73 | 2. 长度:slice 中元素个数。
74 | 3. 容量:slice 起始元素到底层数组最后一个元素间的元素个数。
75 |
76 | 底层源码定义如下:
77 |
78 | ```go
79 | type slice struct {
80 | array unsafe.Pointer
81 | len int
82 | cap int
83 | }
84 | ```
85 |
86 | ### 声明以及初始化
87 |
88 | ```go
89 | func main() {
90 | var nums []int // 声明切片
91 | fmt.Println(len(nums), cap(nums)) // 0 0
92 | nums = append(nums, 1) // 初始化
93 | fmt.Println(len(nums), cap(nums)) // 1 1
94 |
95 | nums1 := []int{1,2,3,4} // 声明并初始化
96 | fmt.Println(len(nums1), cap(nums1)) // 4 4
97 |
98 | nums2 := make([]int,3,5) // 使用make()函数构造切片
99 | fmt.Println(len(nums2), cap(nums2)) // 3 5
100 | }
101 | ```
102 |
103 | ### 函数参数
104 |
105 | 当切片作为函数参数时,和数组是不同的,如果一个函数接受一个切片参数,它对切片元素所做的更改将对调用者可见,类似于将指针传递给了底层数组。
106 |
107 | ```go
108 | package main
109 |
110 | import (
111 | "fmt"
112 | )
113 |
114 | func Add(numbers []int) {
115 | for i := 0; i < len(numbers); i++ {
116 | numbers[i] = numbers[i] + 1
117 | }
118 | fmt.Println("numbers in Add:", numbers) // [2 3 4 5 6]
119 | }
120 |
121 | func main() {
122 | var numbers []int
123 | for i := 0; i < 5; i++ {
124 | numbers = append(numbers, i+1)
125 | }
126 |
127 | Add(numbers)
128 |
129 | fmt.Println("numbers in main:", numbers) // [2 3 4 5 6]
130 | }
131 | ```
132 |
133 | 再看一下上面的例子,把参数由数组变成切片,`Add` 函数中的修改会影响到 `main` 函数。
134 |
135 | ## 总结
136 |
137 | 最后来总结一下,面试时也可以这么来回答:
138 |
139 | 1. 数组是一个长度固定的数据类型,其长度在定义时就已经确定,不能动态改变;切片是一个长度可变的数据类型,其长度在定义时可以为空,也可以指定一个初始长度。
140 | 2. 数组的内存空间是在定义时分配的,其大小是固定的;切片的内存空间是在运行时动态分配的,其大小是可变的。
141 | 3. 当数组作为函数参数时,函数操作的是数组的一个副本,不会影响原始数组;当切片作为函数参数时,函数操作的是切片的引用,会影响原始切片。
142 | 4. 切片还有容量的概念,它指的是分配的内存空间。
143 |
144 | 以上就是本文的全部内容,如果觉得还不错的话欢迎**点赞**,**转发**和**关注**,感谢支持。
145 |
146 | ***
147 |
148 | **参考文章:**
149 |
150 | - https://go.dev/doc/effective_go#arrays
151 | - https://go.dev/blog/slices-intro
152 | - https://levelup.gitconnected.com/go-programming-array-vs-slice-5902b7fdd436
153 |
154 | **推荐阅读:**
155 |
156 | - [Go 语言 new 和 make 关键字的区别](https://mp.weixin.qq.com/s/NBDkI3roHgNgW1iW4e_6cA)
157 | - [为什么 Go 不支持 []T 转换为 []interface](https://mp.weixin.qq.com/s/cwDEgnicK4jkuNpzulU2bw)
158 | - [为什么 Go 语言 struct 要使用 tags](https://mp.weixin.qq.com/s/L7-TJ-CzYfuVrIBWP7Ebaw)
--------------------------------------------------------------------------------
/interview/go-gpm.md:
--------------------------------------------------------------------------------
1 | # Go语言的GPM调度器是什么?
2 |
3 | 相信很多人都听说过Go语言天然支持高并发,原因是内部有协程(goroutine)加持,可以在一个进程中启动成千上万个协程。那么,它凭什么做到如此高的并发呢?那就需要先了解什么是并发模型。
4 |
5 |
6 | ## 并发模型
7 |
8 | 著名的C++专家Herb Sutter曾经说过“免费的午餐已经终结”。为了让代码运行的更快,单纯依靠更快的硬件已经无法得到满足,我们需要利用多核来挖掘并行的价值,而并发模型的目的就是来告诉你不同执行实体之间是如何协作的。
9 |
10 | 当然,不同的并发模型的协作方式也不尽相同,常见的并发模型有七种:
11 |
12 | - 线程与锁
13 | - 函数式编程
14 | - Clojure之道
15 | - actor
16 | - 通讯顺序进程(CSP)
17 | - 数据级并行
18 | - Lambda架构
19 |
20 | 而今天,我们只讲与Go语言相关的并发模型CSP,感兴趣的同学可以自行查阅书籍《七周七并发模型》。
21 |
22 |
23 | ### CSP篇
24 |
25 | CSP,全称Communicating Sequential Processes,意为通讯顺序进程,它是七大并发模型中的一种,它的核心观念是将两个并发执行的实体通过通道channel连接起来,所有的消息都通过channel传输。其实CSP概念早在1978年就被东尼·霍尔提出,由于近来Go语言的兴起,CSP又火了起来。
26 | 那么CSP与Go语言有什么关系呢?接下来我们来看Go语言对CSP并发模型的实现——GPM调度模型。
27 |
28 |
29 | ### GPM调度模型
30 |
31 | GPM代表了三个角色,分别是Goroutine、Processor、Machine。
32 |
33 | 
34 |
35 | - Goroutine:就是咱们常用的用go关键字创建的执行体,它对应一个结构体g,结构体里保存了goroutine的堆栈信息
36 | - Machine:表示操作系统的线程
37 | - Processor:表示处理器,有了它才能建立G、M的联系
38 |
39 | ### Goroutine
40 |
41 | Goroutine就是代码中使用go关键词创建的执行单元,也是大家熟知的有“轻量级线程”之称的协程,协程是不为操作系统所知的,它由编程语言层面实现,上下文切换不需要经过内核态,再加上协程占用的内存空间极小,所以有着非常大的发展潜力。
42 |
43 | ```go
44 | go func() {}()
45 | ```
46 |
47 | 复制代码在Go语言中,Goroutine由一个名为runtime.go的结构体表示,该结构体非常复杂,有40多个成员变量,主要存储执行栈、状态、当前占用的线程、调度相关的数据。还有玩大家很想获取的goroutine标识,但是很抱歉,官方考虑到Go语言的发展,设置成私有了,不给你调用😏。
48 |
49 | ```go
50 | type g struct {
51 | stack struct {
52 | lo uintptr
53 | hi uintptr
54 | } // 栈内存:[stack.lo, stack.hi)
55 | stackguard0 uintptr
56 | stackguard1 uintptr
57 |
58 | _panic *_panic
59 | _defer *_defer
60 | m *m // 当前的 m
61 | sched gobuf
62 | stktopsp uintptr // 期望 sp 位于栈顶,用于回溯检查
63 | param unsafe.Pointer // wakeup 唤醒时候传递的参数
64 | atomicstatus uint32
65 | goid int64
66 | preempt bool // 抢占信号,stackguard0 = stackpreempt 的副本
67 | timer *timer // 为 time.Sleep 缓存的计时器
68 |
69 | ...
70 | }
71 | ```
72 | Goroutine调度相关的数据存储在sched,在协程切换、恢复上下文的时候用到。
73 |
74 | ```go
75 | type gobuf struct {
76 | sp uintptr
77 | pc uintptr
78 | g guintptr
79 | ret sys.Uintreg
80 | ...
81 | }
82 | ```
83 |
84 | M就是对应操作系统的线程,最多会有GOMAXPROCS个活跃线程能够正常运行,默认情况下GOMAXPROCS被设置为内核数,假如有四个内核,那么默认就创建四个线程,每一个线程对应一个runtime.m结构体。线程数等于CPU个数的原因是,每个线程分配到一个CPU上就不至于出现线程的上下文切换,可以保证系统开销降到最低。
85 |
86 | ```go
87 | type m struct {
88 | g0 *g
89 | curg *g
90 | ...
91 | }
92 | ```
93 |
94 | M里面存了两个比较重要的东西,一个是g0,一个是curg。
95 |
96 | - g0:会深度参与运行时的调度过程,比如goroutine的创建、内存分配等
97 | - curg:代表当前正在线程上执行的goroutine。
98 |
99 | 刚才说P是负责M与G的关联,所以M里面还要存储与P相关的数据。
100 |
101 | ```go
102 | type m struct {
103 | ...
104 | p puintptr
105 | nextp puintptr
106 | oldp puintptr
107 | }
108 | ```
109 |
110 | - p:正在运行代码的处理器
111 | - nextp:暂存的处理器
112 | - oldp:系统调用之前的线程的处理器
113 |
114 | ### Processor
115 |
116 | Proccessor负责Machine与Goroutine的连接,它能提供线程需要的上下文环境,也能分配G到它应该去的线程上执行,有了它,每个G都能得到合理的调用,每个线程都不再浑水摸鱼,真是居家必备之良品。
117 |
118 | 同样的,处理器的数量也是默认按照GOMAXPROCS来设置的,与线程的数量一一对应。
119 |
120 | ```go
121 | type p struct {
122 | m muintptr
123 |
124 | runqhead uint32
125 | runqtail uint32
126 | runq [256]guintptr
127 | runnext guintptr
128 | ...
129 | }
130 | ```
131 |
132 | 结构体P中存储了性能追踪、垃圾回收、计时器等相关的字段外,还存储了处理器的待运行队列,队列中存储的是待执行的Goroutine列表。
133 |
134 | ### 三者的关系
135 |
136 | 首先,默认启动四个线程四个处理器,然后互相绑定。
137 |
138 | 
139 |
140 | 这个时候,一个Goroutine结构体被创建,在进行函数体地址、参数起始地址、参数长度等信息以及调度相关属性更新之后,它就要进到一个处理器的队列等待发车。
141 |
142 | 
143 |
144 | 啥,又创建了一个G?那就轮流往其他P里面放呗,相信你排队取号的时候看到其他窗口没人排队也会过去的。
145 |
146 | 
147 |
148 | 假如有很多G,都塞满了怎么办呢?那就不把G塞到处理器的私有队列里了,而是把它塞到全局队列里(候车大厅)。
149 |
150 | 
151 |
152 | 除了往里塞之外,M这边还要疯狂往外取,首先去处理器的私有队列里取G执行,如果取完的话就去全局队列取,如果全局队列里也没有的话,就去其他处理器队列里偷,哇,这么饥渴,简直是恶魔啊!
153 |
154 | 
155 |
156 | 如果哪里都没找到要执行的G呢?那M就会因为太失望和P断开关系,然后去睡觉(idle)了。
157 |
158 | 
159 |
160 | 那如果两个Goroutine正在通过channel做一些恩恩爱爱的事阻塞住了怎么办,难道M要等他们完事了再继续执行?显然不会,M并不稀罕这对Go男女,而会转身去找别的G执行。
161 |
162 | 
163 |
164 |
165 | ## 系统调用
166 |
167 | 如果G进行了系统调用syscall,M也会跟着进入系统调用状态,那么这个P留在这里就浪费了,怎么办呢?这点精妙之处在于,P不会傻傻的等待G和M系统调用完成,而会去找其他比较闲的M执行其他的G。
168 |
169 | 
170 |
171 | 当G完成了系统调用,因为要继续往下执行,所以必须要再找一个空闲的处理器发车。
172 |
173 | 
174 |
175 | 如果没有空闲的处理器了,那就只能把G放回全局队列当中等待分配。
176 |
177 | 
178 |
179 |
180 | ## sysmon
181 |
182 | sysmon是我们的保洁阿姨,它是一个M,又叫监控线程,不需要P就可以独立运行,每20us~10ms会被唤醒一次出来打扫卫生,主要工作就是回收垃圾、回收长时间系统调度阻塞的P、向长时间运行的G发出抢占调度等等。
183 |
184 | > 作者:平也
185 | > 链接:https://juejin.im/post/5e999ead518825739b2d44d7
186 | > 来源:掘金
187 | > 著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
188 |
189 |
190 |
191 |
192 |
193 |
194 |
195 |
196 |
197 |
198 |
199 |
200 |
201 |
202 |
203 |
204 |
205 |
206 |
207 |
208 |
209 |
--------------------------------------------------------------------------------
/interview/q001.md:
--------------------------------------------------------------------------------
1 | ## 交替打印数字和字母
2 |
3 | **问题描述**
4 |
5 | 使用两个 `goroutine` 交替打印序列,一个 `goroutine` 打印数字, 另外一个 `goroutine` 打印字母, 最终效果如下:
6 |
7 | ```bash
8 | 12AB34CD56EF78GH910IJ1112KL1314MN1516OP1718QR1920ST2122UV2324WX2526YZ2728
9 | ```
10 |
11 | **解题思路**
12 |
13 | 问题很简单,使用 channel 来控制打印的进度。使用两个 channel ,来分别控制数字和字母的打印序列, 数字打印完成后通过 channel 通知字母打印, 字母打印完成后通知数字打印,然后周而复始的工作。
14 |
15 | **源码参考**
16 |
17 | ```
18 | letter,number := make(chan bool),make(chan bool)
19 | wait := sync.WaitGroup{}
20 |
21 | go func() {
22 | i := 1
23 | for {
24 | select {
25 | case <-number:
26 | fmt.Print(i)
27 | i++
28 | fmt.Print(i)
29 | i++
30 | letter <- true
31 | }
32 | }
33 | }()
34 | wait.Add(1)
35 | go func(wait *sync.WaitGroup) {
36 | i := 'A'
37 | for{
38 | select {
39 | case <-letter:
40 | if i >= 'Z' {
41 | wait.Done()
42 | return
43 | }
44 |
45 | fmt.Print(string(i))
46 | i++
47 | fmt.Print(string(i))
48 | i++
49 | number <- true
50 | }
51 |
52 | }
53 | }(&wait)
54 | number<-true
55 | wait.Wait()
56 | ```
57 |
58 | **源码解析**
59 |
60 | 这里用到了两个`channel`负责通知,letter负责通知打印字母的goroutine来打印字母,number用来通知打印数字的goroutine打印数字。
61 |
62 | wait用来等待字母打印完成后退出循环。
63 |
--------------------------------------------------------------------------------
/interview/q002.md:
--------------------------------------------------------------------------------
1 | ## 判断字符串中字符是否全都不同
2 |
3 | **问题描述**
4 |
5 | 请实现一个算法,确定一个字符串的所有字符【是否全都不同】。这里我们要求【不允许使用额外的存储结构】。
6 | 给定一个string,请返回一个bool值,true代表所有字符全都不同,false代表存在相同的字符。
7 | 保证字符串中的字符为【ASCII字符】。字符串的长度小于等于【3000】。
8 |
9 |
10 | **解题思路**
11 |
12 | 这里有几个重点,第一个是`ASCII字符`,`ASCII字符`字符一共有256个,其中128个是常用字符,可以在键盘上输入。128之后的是键盘上无法找到的。
13 |
14 | 然后是全部不同,也就是字符串中的字符没有重复的,再次,不准使用额外的储存结构,且字符串小于等于3000。
15 |
16 | 如果允许其他额外储存结构,这个题目很好做。如果不允许的话,可以使用golang内置的方式实现。
17 |
18 | **源码参考**
19 |
20 | 通过`strings.Count` 函数判断:
21 |
22 | ```
23 | func isUniqueString(s string) bool {
24 | if strings.Count(s,"") > 3000{
25 | return false
26 | }
27 | for _,v := range s {
28 | if v > 127 {
29 | return false
30 | }
31 | if strings.Count(s,string(v)) > 1 {
32 | return false
33 | }
34 | }
35 | return true
36 | }
37 | ```
38 |
39 | 通过`strings.Index`和`strings.LastIndex`函数判断:
40 |
41 | ```
42 | func isUniqueString2(s string) bool {
43 | if strings.Count(s,"") > 3000{
44 | return false
45 | }
46 | for k,v := range s {
47 | if v > 127 {
48 | return false
49 | }
50 | if strings.Index(s,string(v)) != k {
51 | return false
52 | }
53 | }
54 | return true
55 | }
56 | ```
57 |
58 | **源码解析**
59 |
60 | 以上两种方法都可以实现这个算法。
61 |
62 | 第一个方法使用的是golang内置方法`strings.Count`,可以用来判断在一个字符串中包含的另外一个字符串的数量。
63 |
64 | 第二个方法使用的是golang内置方法`strings.Index`和`strings.LastIndex`,用来判断指定字符串在另外一个字符串的索引未知,分别是第一次发现位置和最后发现位置。
65 |
--------------------------------------------------------------------------------
/interview/q003.md:
--------------------------------------------------------------------------------
1 | ## 翻转字符串
2 |
3 | **问题描述**
4 |
5 | 请实现一个算法,在不使用【额外数据结构和储存空间】的情况下,翻转一个给定的字符串(可以使用单个过程变量)。
6 |
7 | 给定一个string,请返回一个string,为翻转后的字符串。保证字符串的长度小于等于5000。
8 |
9 | **解题思路**
10 |
11 | 翻转字符串其实是将一个字符串以中间字符为轴,前后翻转,即将str[len]赋值给str[0],将str[0] 赋值 str[len]。
12 |
13 | **源码参考**
14 |
15 | ```
16 | func reverString(s string) (string, bool) {
17 | str := []rune(s)
18 | l := len(str)
19 | if l > 5000 {
20 | return s, false
21 | }
22 | for i := 0; i < l/2; i++ {
23 | str[i], str[l-1-i] = str[l-1-i], str[i]
24 | }
25 | return string(str), true
26 | }
27 | ```
28 |
29 | **源码解析**
30 |
31 | 以字符串长度的1/2为轴,前后赋值
32 |
--------------------------------------------------------------------------------
/interview/q004.md:
--------------------------------------------------------------------------------
1 | ## 判断两个给定的字符串排序后是否一致
2 |
3 | **问题描述**
4 |
5 | 给定两个字符串,请编写程序,确定其中一个字符串的字符重新排列后,能否变成另一个字符串。
6 | 这里规定【大小写为不同字符】,且考虑字符串重点空格。给定一个string s1和一个string s2,请返回一个bool,代表两串是否重新排列后可相同。
7 | 保证两串的长度都小于等于5000。
8 |
9 | **解题思路**
10 |
11 | 首先要保证字符串长度小于5000。之后只需要一次循环遍历s1中的字符在s2是否都存在即可。
12 |
13 | **源码参考**
14 |
15 | ```
16 | func isRegroup(s1,s2 string) bool {
17 | sl1 := len([]rune(s1))
18 | sl2 := len([]rune(s2))
19 |
20 | if sl1 > 5000 || sl2 > 5000 || sl1 != sl2{
21 | return false
22 | }
23 |
24 | for _,v := range s1 {
25 | if strings.Count(s1,string(v)) != strings.Count(s2,string(v)) {
26 | return false
27 | }
28 | }
29 | return true
30 | }
31 | ```
32 |
33 | **源码解析**
34 |
35 | 这里还是使用golang内置方法 `strings.Count` 来判断字符是否一致。
36 |
--------------------------------------------------------------------------------
/interview/q005.md:
--------------------------------------------------------------------------------
1 | ## 字符串替换问题
2 |
3 | **问题描述**
4 |
5 | 请编写一个方法,将字符串中的空格全部替换为“%20”。
6 | 假定该字符串有足够的空间存放新增的字符,并且知道字符串的真实长度(小于等于1000),同时保证字符串由【大小写的英文字母组成】。
7 | 给定一个string为原始的串,返回替换后的string。
8 |
9 | **解题思路**
10 |
11 | 两个问题,第一个是只能是英文字母,第二个是替换空格。
12 |
13 | **源码参考**
14 |
15 | ```
16 | func replaceBlank(s string) (string, bool) {
17 | if len([]rune(s)) > 1000 {
18 | return s, false
19 | }
20 | for _, v := range s {
21 | if string(v) != " " && unicode.IsLetter(v) == false {
22 | return s, false
23 | }
24 | }
25 | return strings.Replace(s, " ", "%20", -1), true
26 | }
27 | ```
28 |
29 | **源码解析**
30 |
31 | 这里使用了golang内置方法`unicode.IsLetter`判断字符是否是字母,之后使用`strings.Replace`来替换空格。
--------------------------------------------------------------------------------
/interview/q006.md:
--------------------------------------------------------------------------------
1 | ## 机器人坐标问题
2 |
3 | **问题描述**
4 |
5 | 有一个机器人,给一串指令,L左转 R右转,F前进一步,B后退一步,问最后机器人的坐标,最开始,机器人位于 0 0,方向为正Y。
6 | 可以输入重复指令n : 比如 R2(LF) 这个等于指令 RLFLF。
7 | 问最后机器人的坐标是多少?
8 |
9 | **解题思路**
10 |
11 | 这里的一个难点是解析重复指令。主要指令解析成功,计算坐标就简单了。
12 |
13 | **源码参考**
14 |
15 | ```
16 | package main
17 |
18 | import (
19 | "unicode"
20 | )
21 |
22 | const (
23 | Left = iota
24 | Top
25 | Right
26 | Bottom
27 | )
28 |
29 | func main() {
30 | println(move("R2(LF)", 0, 0, Top))
31 | }
32 |
33 | func move(cmd string, x0 int, y0 int, z0 int) (x, y, z int) {
34 | x, y, z = x0, y0, z0
35 | repeat := 0
36 | repeatCmd := ""
37 | for _, s := range cmd {
38 | switch {
39 | case unicode.IsNumber(s):
40 | repeat = repeat*10 + (int(s) - '0')
41 | case s == ')':
42 | for i := 0; i < repeat; i++ {
43 | x, y, z = move(repeatCmd, x, y, z)
44 | }
45 | repeat = 0
46 | repeatCmd = ""
47 | case repeat > 0 && s != '(' && s != ')':
48 | repeatCmd = repeatCmd + string(s)
49 | case s == 'L':
50 | z = (z + 1) % 4
51 | case s == 'R':
52 | z = (z - 1 + 4) % 4
53 | case s == 'F':
54 | switch {
55 | case z == Left || z == Right:
56 | x = x - z + 1
57 | case z == Top || z == Bottom:
58 | y = y - z + 2
59 | }
60 | case s == 'B':
61 | switch {
62 | case z == Left || z == Right:
63 | x = x + z - 1
64 | case z == Top || z == Bottom:
65 | y = y + z - 2
66 | }
67 | }
68 | }
69 | return
70 | }
71 |
72 | ```
73 |
74 | **源码解析**
75 |
76 | 这里使用三个值表示机器人当前的状况,分别是:x表示x坐标,y表示y坐标,z表示当前方向。
77 | L、R 命令会改变值z,F、B命令会改变值x、y。
78 | 值x、y的改变还受当前的z值影响。
79 |
80 | 如果是重复指令,那么将重复次数和重复的指令存起来递归调用即可。
81 |
--------------------------------------------------------------------------------
/interview/q007.md:
--------------------------------------------------------------------------------
1 | ## 常见语法题目 一
2 |
3 | ### 1、下面代码能运行吗?为什么。
4 |
5 | ```go
6 | type Param map[string]interface{}
7 |
8 | type Show struct {
9 | Param
10 | }
11 |
12 | func main1() {
13 | s := new(Show)
14 | s.Param["RMB"] = 10000
15 | }
16 | ```
17 |
18 | **解析**
19 |
20 | 共发现两个问题:
21 |
22 | 1. `main` 函数不能加数字。
23 | 2. `new` 关键字无法初始化 `Show` 结构体中的 `Param` 属性,所以直接对 `s.Param` 操作会出错。
24 |
25 | ### 2、请说出下面代码存在什么问题。
26 |
27 | ```go
28 | type student struct {
29 | Name string
30 | }
31 |
32 | func zhoujielun(v interface{}) {
33 | switch msg := v.(type) {
34 | case *student, student:
35 | msg.Name
36 | }
37 | }
38 | ```
39 |
40 | **解析:**
41 |
42 | golang中有规定,`switch type`的`case T1`,类型列表只有一个,那么`v := m.(type)`中的`v`的类型就是T1类型。
43 |
44 | 如果是`case T1, T2`,类型列表中有多个,那`v`的类型还是多对应接口的类型,也就是`m`的类型。
45 |
46 | 所以这里`msg`的类型还是`interface{}`,所以他没有`Name`这个字段,编译阶段就会报错。具体解释见:
47 |
48 | ### 3、写出打印的结果。
49 |
50 | ```go
51 | type People struct {
52 | name string `json:"name"`
53 | }
54 |
55 | func main() {
56 | js := `{
57 | "name":"11"
58 | }`
59 | var p People
60 | err := json.Unmarshal([]byte(js), &p)
61 | if err != nil {
62 | fmt.Println("err: ", err)
63 | return
64 | }
65 | fmt.Println("people: ", p)
66 | }
67 | ```
68 |
69 | **解析:**
70 |
71 | 按照 golang 的语法,小写开头的方法、属性或 `struct` 是私有的,同样,在`json` 解码或转码的时候也无法上线私有属性的转换。
72 |
73 | 题目中是无法正常得到`People`的`name`值的。而且,私有属性`name`也不应该加`json`的标签。
74 |
75 |
76 | ### 4、下面的代码是有问题的,请说明原因。
77 |
78 | ```go
79 | type People struct {
80 | Name string
81 | }
82 |
83 | func (p *People) String() string {
84 | return fmt.Sprintf("print: %v", p)
85 | }
86 |
87 | func main() {
88 | p := &People{}
89 | p.String()
90 | }
91 | ```
92 |
93 | **解析:**
94 |
95 | 在golang中`String() string` 方法实际上是实现了`String`的接口的,该接口定义在`fmt/print.go` 中:
96 |
97 | ```go
98 | type Stringer interface {
99 | String() string
100 | }
101 | ```
102 |
103 | 在使用 `fmt` 包中的打印方法时,如果类型实现了这个接口,会直接调用。而题目中打印 `p` 的时候会直接调用 `p` 实现的 `String()` 方法,然后就产生了循环调用。
104 |
105 |
106 | ### 5、请找出下面代码的问题所在。
107 |
108 | ```go
109 | func main() {
110 | ch := make(chan int, 1000)
111 | go func() {
112 | for i := 0; i < 10; i++ {
113 | ch <- i
114 | }
115 | }()
116 | go func() {
117 | for {
118 | a, ok := <-ch
119 | if !ok {
120 | fmt.Println("close")
121 | return
122 | }
123 | fmt.Println("a: ", a)
124 | }
125 | }()
126 | close(ch)
127 | fmt.Println("ok")
128 | time.Sleep(time.Second * 100)
129 | }
130 | ```
131 |
132 |
133 | **解析:**
134 |
135 | 在 golang 中 `goroutine` 的调度时间是不确定的,在题目中,第一个写 `channel` 的 `goroutine` 可能还未调用,或已调用但没有写完时直接 `close` 管道,可能导致写失败,既然出现 `panic` 错误。
136 |
137 |
138 |
139 | ### 6、请说明下面代码书写是否正确。
140 |
141 | ```go
142 | var value int32
143 |
144 | func SetValue(delta int32) {
145 | for {
146 | v := value
147 | if atomic.CompareAndSwapInt32(&value, v, (v+delta)) {
148 | break
149 | }
150 | }
151 | }
152 | ```
153 |
154 |
155 |
156 | **解析:**
157 |
158 | `atomic.CompareAndSwapInt32` 函数不需要循环调用。
159 |
160 |
161 | ### 7、下面的程序运行后为什么会爆异常。
162 |
163 | ```go
164 | type Project struct{}
165 |
166 | func (p *Project) deferError() {
167 | if err := recover(); err != nil {
168 | fmt.Println("recover: ", err)
169 | }
170 | }
171 |
172 | func (p *Project) exec(msgchan chan interface{}) {
173 | for msg := range msgchan {
174 | m := msg.(int)
175 | fmt.Println("msg: ", m)
176 | }
177 | }
178 |
179 | func (p *Project) run(msgchan chan interface{}) {
180 | for {
181 | defer p.deferError()
182 | go p.exec(msgchan)
183 | time.Sleep(time.Second * 2)
184 | }
185 | }
186 |
187 | func (p *Project) Main() {
188 | a := make(chan interface{}, 100)
189 | go p.run(a)
190 | go func() {
191 | for {
192 | a <- "1"
193 | time.Sleep(time.Second)
194 | }
195 | }()
196 | time.Sleep(time.Second * 100000000000000)
197 | }
198 |
199 | func main() {
200 | p := new(Project)
201 | p.Main()
202 | }
203 | ```
204 |
205 | **解析:**
206 |
207 | 有一下几个问题:
208 |
209 | 1. `time.Sleep` 的参数数值太大,超过了 `1<<63 - 1` 的限制。
210 | 2. `defer p.deferError()` 需要在协程开始出调用,否则无法捕获 `panic`。
211 |
212 |
213 | ### 8、请说出下面代码哪里写错了
214 |
215 | ```go
216 | func main() {
217 | abc := make(chan int, 1000)
218 | for i := 0; i < 10; i++ {
219 | abc <- i
220 | }
221 | go func() {
222 | for a := range abc {
223 | fmt.Println("a: ", a)
224 | }
225 | }()
226 | close(abc)
227 | fmt.Println("close")
228 | time.Sleep(time.Second * 100)
229 | }
230 | ```
231 |
232 | **解析:**
233 |
234 | 协程可能还未启动,管道就关闭了。
235 |
236 |
237 | ### 9、请说出下面代码,执行时为什么会报错
238 |
239 | ```go
240 | type Student struct {
241 | name string
242 | }
243 |
244 | func main() {
245 | m := map[string]Student{"people": {"zhoujielun"}}
246 | m["people"].name = "wuyanzu"
247 | }
248 |
249 | ```
250 |
251 | **解析:**
252 |
253 | map的value本身是不可寻址的,因为map中的值会在内存中移动,并且旧的指针地址在map改变时会变得无效。故如果需要修改map值,可以将`map`中的非指针类型`value`,修改为指针类型,比如使用`map[string]*Student`.
254 |
255 |
256 | ### 10、请说出下面的代码存在什么问题?
257 |
258 | ```go
259 | type query func(string) string
260 |
261 | func exec(name string, vs ...query) string {
262 | ch := make(chan string)
263 | fn := func(i int) {
264 | ch <- vs[i](name)
265 | }
266 | for i, _ := range vs {
267 | go fn(i)
268 | }
269 | return <-ch
270 | }
271 |
272 | func main() {
273 | ret := exec("111", func(n string) string {
274 | return n + "func1"
275 | }, func(n string) string {
276 | return n + "func2"
277 | }, func(n string) string {
278 | return n + "func3"
279 | }, func(n string) string {
280 | return n + "func4"
281 | })
282 | fmt.Println(ret)
283 | }
284 | ```
285 |
286 | **解析:**
287 |
288 | 依据4个goroutine的启动后执行效率,很可能打印111func4,但其他的111func*也可能先执行,exec只会返回一条信息。
289 |
290 |
291 | ### 11、下面这段代码为什么会卡死?
292 |
293 | ```go
294 | package main
295 |
296 | import (
297 | "fmt"
298 | "runtime"
299 | )
300 |
301 | func main() {
302 | var i byte
303 | go func() {
304 | for i = 0; i <= 255; i++ {
305 | }
306 | }()
307 | fmt.Println("Dropping mic")
308 | // Yield execution to force executing other goroutines
309 | runtime.Gosched()
310 | runtime.GC()
311 | fmt.Println("Done")
312 | }
313 | ```
314 |
315 | **解析:**
316 |
317 | Golang 中,byte 其实被 alias 到 uint8 上了。所以上面的 for 循环会始终成立,因为 i++ 到 i=255 的时候会溢出,i <= 255 一定成立。
318 |
319 | 也即是, for 循环永远无法退出,所以上面的代码其实可以等价于这样:
320 | ```go
321 | go func() {
322 | for {}
323 | }
324 | ```
325 |
326 | 正在被执行的 goroutine 发生以下情况时让出当前 goroutine 的执行权,并调度后面的 goroutine 执行:
327 |
328 | - IO 操作
329 | - Channel 阻塞
330 | - system call
331 | - 运行较长时间
332 |
333 | 如果一个 goroutine 执行时间太长,scheduler 会在其 G 对象上打上一个标志( preempt),当这个 goroutine 内部发生函数调用的时候,会先主动检查这个标志,如果为 true 则会让出执行权。
334 |
335 | main 函数里启动的 goroutine 其实是一个没有 IO 阻塞、没有 Channel 阻塞、没有 system call、没有函数调用的死循环。
336 |
337 | 也就是,它无法主动让出自己的执行权,即使已经执行很长时间,scheduler 已经标志了 preempt。
338 |
339 | 而 golang 的 GC 动作是需要所有正在运行 `goroutine` 都停止后进行的。因此,程序会卡在 `runtime.GC()` 等待所有协程退出。
340 |
--------------------------------------------------------------------------------
/interview/q009.md:
--------------------------------------------------------------------------------
1 | # 在 golang 协程和channel配合使用
2 |
3 | > 写代码实现两个 goroutine,其中一个产生随机数并写入到 go channel 中,另外一个从 channel 中读取数字并打印到标准输出。最终输出五个随机数。
4 |
5 | **解析**
6 |
7 | 这是一道很简单的golang基础题目,实现方法也有很多种,一般想答让面试官满意的答案还是有几点注意的地方。
8 |
9 | 1. `goroutine` 在golang中式非阻塞的
10 | 2. `channel` 无缓冲情况下,读写都是阻塞的,且可以用`for`循环来读取数据,当管道关闭后,`for` 退出。
11 | 3. golang 中有专用的`select case` 语法从管道读取数据。
12 |
13 | 示例代码如下:
14 |
15 | ```go
16 | func main() {
17 | out := make(chan int)
18 | wg := sync.WaitGroup{}
19 | wg.Add(2)
20 | go func() {
21 | defer wg.Done()
22 | for i := 0; i < 5; i++ {
23 | out <- rand.Intn(5)
24 | }
25 | close(out)
26 | }()
27 | go func() {
28 | defer wg.Done()
29 | for i := range out {
30 | fmt.Println(i)
31 | }
32 | }()
33 | wg.Wait()
34 | }
35 | ```
--------------------------------------------------------------------------------
/interview/q010.md:
--------------------------------------------------------------------------------
1 | # 实现阻塞读且并发安全的map
2 |
3 | GO里面MAP如何实现key不存在 get操作等待 直到key存在或者超时,保证并发安全,且需要实现以下接口:
4 |
5 | ```go
6 | type sp interface {
7 | Out(key string, val interface{}) //存入key /val,如果该key读取的goroutine挂起,则唤醒。此方法不会阻塞,时刻都可以立即执行并返回
8 | Rd(key string, timeout time.Duration) interface{} //读取一个key,如果key不存在阻塞,等待key存在或者超时
9 | }
10 | ```
11 |
12 | **解析:**
13 |
14 | 看到阻塞协程第一个想到的就是`channel`,题目中要求并发安全,那么必须用锁,还要实现多个`goroutine`读的时候如果值不存在则阻塞,直到写入值,那么每个键值需要有一个阻塞`goroutine` 的 `channel`。
15 |
16 | [实现如下:](../src/q010.go)
17 |
18 | ```go
19 | type Map struct {
20 | c map[string]*entry
21 | rmx *sync.RWMutex
22 | }
23 | type entry struct {
24 | ch chan struct{}
25 | value interface{}
26 | isExist bool
27 | }
28 |
29 | func (m *Map) Out(key string, val interface{}) {
30 | m.rmx.Lock()
31 | defer m.rmx.Unlock()
32 | item, ok := m.c[key]
33 | if !ok {
34 | m.c[key] = &entry{
35 | value: val,
36 | isExist: true,
37 | }
38 | return
39 | }
40 | item.value = val
41 | if !item.isExist {
42 | if item.ch != nil {
43 | close(item.ch)
44 | item.ch = nil
45 | }
46 | }
47 | return
48 | }
49 | ```
50 |
--------------------------------------------------------------------------------
/interview/q011.md:
--------------------------------------------------------------------------------
1 | # 高并发下的锁与map的读写
2 |
3 | 场景:在一个高并发的web服务器中,要限制IP的频繁访问。现模拟100个IP同时并发访问服务器,每个IP要重复访问1000次。
4 |
5 | 每个IP三分钟之内只能访问一次。修改以下代码完成该过程,要求能成功输出 success:100
6 |
7 | ```go
8 | package main
9 |
10 | import (
11 | "fmt"
12 | "time"
13 | )
14 |
15 | type Ban struct {
16 | visitIPs map[string]time.Time
17 | }
18 |
19 | func NewBan() *Ban {
20 | return &Ban{visitIPs: make(map[string]time.Time)}
21 | }
22 | func (o *Ban) visit(ip string) bool {
23 | if _, ok := o.visitIPs[ip]; ok {
24 | return true
25 | }
26 | o.visitIPs[ip] = time.Now()
27 | return false
28 | }
29 | func main() {
30 | success := 0
31 | ban := NewBan()
32 | for i := 0; i < 1000; i++ {
33 | for j := 0; j < 100; j++ {
34 | go func() {
35 | ip := fmt.Sprintf("192.168.1.%d", j)
36 | if !ban.visit(ip) {
37 | success++
38 | }
39 | }()
40 | }
41 |
42 | }
43 | fmt.Println("success:", success)
44 | }
45 | ```
46 |
47 | **解析**
48 |
49 | 该问题主要考察了并发情况下map的读写问题,而给出的初始代码,又存在`for`循环中启动`goroutine`时变量使用问题以及`goroutine`执行滞后问题。
50 |
51 | 因此,首先要保证启动的`goroutine`得到的参数是正确的,然后保证`map`的并发读写,最后保证三分钟只能访问一次。
52 |
53 | 多CPU核心下修改`int`的值极端情况下会存在不同步情况,因此需要原子性的修改int值。
54 |
55 | 下面给出的实例代码,是启动了一个协程每分钟检查一下`map`中的过期`ip`,`for`启动协程时传参。
56 |
57 | ```go
58 | package main
59 |
60 | import (
61 | "context"
62 | "fmt"
63 | "sync"
64 | "sync/atomic"
65 | "time"
66 | )
67 |
68 | type Ban struct {
69 | visitIPs map[string]time.Time
70 | lock sync.Mutex
71 | }
72 |
73 | func NewBan(ctx context.Context) *Ban {
74 | o := &Ban{visitIPs: make(map[string]time.Time)}
75 | go func() {
76 | timer := time.NewTimer(time.Minute * 1)
77 | for {
78 | select {
79 | case <-timer.C:
80 | o.lock.Lock()
81 | for k, v := range o.visitIPs {
82 | if time.Now().Sub(v) >= time.Minute*1 {
83 | delete(o.visitIPs, k)
84 | }
85 | }
86 | o.lock.Unlock()
87 | timer.Reset(time.Minute * 1)
88 | case <-ctx.Done():
89 | return
90 | }
91 | }
92 | }()
93 | return o
94 | }
95 | func (o *Ban) visit(ip string) bool {
96 | o.lock.Lock()
97 | defer o.lock.Unlock()
98 | if _, ok := o.visitIPs[ip]; ok {
99 | return true
100 | }
101 | o.visitIPs[ip] = time.Now()
102 | return false
103 | }
104 | func main() {
105 | success := int64(0)
106 | ctx, cancel := context.WithCancel(context.Background())
107 | defer cancel()
108 |
109 | ban := NewBan(ctx)
110 |
111 | wait := &sync.WaitGroup{}
112 |
113 | wait.Add(1000 * 100)
114 | for i := 0; i < 1000; i++ {
115 | for j := 0; j < 100; j++ {
116 | go func(j int) {
117 | defer wait.Done()
118 | ip := fmt.Sprintf("192.168.1.%d", j)
119 | if !ban.visit(ip) {
120 | atomic.AddInt64(&success, 1)
121 | }
122 | }(j)
123 | }
124 |
125 | }
126 | wait.Wait()
127 |
128 | fmt.Println("success:", success)
129 | }
130 | ```
--------------------------------------------------------------------------------
/interview/q012.md:
--------------------------------------------------------------------------------
1 | ## 1. 写出以下逻辑,要求每秒钟调用一次proc并保证程序不退出?
2 |
3 | ```go
4 | package main
5 |
6 | func main() {
7 | go func() {
8 | // 1 在这里需要你写算法
9 | // 2 要求每秒钟调用一次proc函数
10 | // 3 要求程序不能退出
11 | }()
12 |
13 | select {}
14 | }
15 |
16 | func proc() {
17 | panic("ok")
18 | }
19 | ```
20 |
21 | **解析**
22 |
23 | 题目主要考察了两个知识点:
24 |
25 | 1. 定时执行执行任务
26 | 2. 捕获 panic 错误
27 |
28 | 题目中要求每秒钟执行一次,首先想到的就是 `time.Ticker`对象,该函数可每秒钟往`chan`中放一个`Time`,正好符合我们的要求。
29 |
30 | 在 `golang` 中捕获 `panic` 一般会用到 `recover()` 函数。
31 |
32 | ```go
33 | package main
34 |
35 | import (
36 | "fmt"
37 | "time"
38 | )
39 |
40 | func main() {
41 | go func() {
42 | // 1 在这里需要你写算法
43 | // 2 要求每秒钟调用一次proc函数
44 | // 3 要求程序不能退出
45 |
46 | t := time.NewTicker(time.Second * 1)
47 | for {
48 | select {
49 | case <-t.C:
50 | go func() {
51 | defer func() {
52 | if err := recover(); err != nil {
53 | fmt.Println(err)
54 | }
55 | }()
56 | proc()
57 | }()
58 | }
59 | }
60 | }()
61 |
62 | select {}
63 | }
64 |
65 | func proc() {
66 | panic("ok")
67 | }
68 |
69 | ```
--------------------------------------------------------------------------------
/interview/q013.md:
--------------------------------------------------------------------------------
1 | ## 为 sync.WaitGroup 中Wait函数支持 WaitTimeout 功能.
2 |
3 | ```go
4 | package main
5 |
6 | import (
7 | "fmt"
8 | "sync"
9 | "time"
10 | )
11 |
12 | func main() {
13 | wg := sync.WaitGroup{}
14 | c := make(chan struct{})
15 | for i := 0; i < 10; i++ {
16 | wg.Add(1)
17 | go func(num int, close <-chan struct{}) {
18 | defer wg.Done()
19 | <-close
20 | fmt.Println(num)
21 | }(i, c)
22 | }
23 |
24 | if WaitTimeout(&wg, time.Second*5) {
25 | close(c)
26 | fmt.Println("timeout exit")
27 | }
28 | time.Sleep(time.Second * 10)
29 | }
30 |
31 | func WaitTimeout(wg *sync.WaitGroup, timeout time.Duration) bool {
32 | // 要求手写代码
33 | // 要求sync.WaitGroup支持timeout功能
34 | // 如果timeout到了超时时间返回true
35 | // 如果WaitGroup自然结束返回false
36 | }
37 | ```
38 |
39 |
40 | **解析**
41 |
42 | 首先 `sync.WaitGroup` 对象的 `Wait` 函数本身是阻塞的,同时,超时用到的`time.Timer` 对象也需要阻塞的读。
43 |
44 | 同时阻塞的两个对象肯定要每个启动一个协程,每个协程去处理一个阻塞,难点在于怎么知道哪个阻塞先完成。
45 |
46 | 目前我用的方式是声明一个没有缓冲的`chan`,谁先完成谁优先向管道中写入数据。
47 |
48 | ```go
49 | package main
50 |
51 | import (
52 | "fmt"
53 | "sync"
54 | "time"
55 | )
56 |
57 | func main() {
58 | wg := sync.WaitGroup{}
59 | c := make(chan struct{})
60 | for i := 0; i < 10; i++ {
61 | wg.Add(1)
62 | go func(num int, close <-chan struct{}) {
63 | defer wg.Done()
64 | <-close
65 | fmt.Println(num)
66 | }(i, c)
67 | }
68 |
69 | if WaitTimeout(&wg, time.Second*5) {
70 | close(c)
71 | fmt.Println("timeout exit")
72 | }
73 | time.Sleep(time.Second * 10)
74 | }
75 |
76 | func WaitTimeout(wg *sync.WaitGroup, timeout time.Duration) bool {
77 | // 要求手写代码
78 | // 要求sync.WaitGroup支持timeout功能
79 | // 如果timeout到了超时时间返回true
80 | // 如果WaitGroup自然结束返回false
81 | ch := make(chan bool, 1)
82 |
83 | go time.AfterFunc(timeout, func() {
84 | ch <- true
85 | })
86 |
87 | go func() {
88 | wg.Wait()
89 | ch <- false
90 | }()
91 |
92 | return <- ch
93 | }
94 | ```
95 |
--------------------------------------------------------------------------------
/interview/q014.md:
--------------------------------------------------------------------------------
1 | # 语法找错题
2 |
3 | ## 写出以下代码出现的问题
4 |
5 | ```go
6 | package main
7 | import (
8 | "fmt"
9 | )
10 | func main() {
11 | var x string = nil
12 | if x == nil {
13 | x = "default"
14 | }
15 | fmt.Println(x)
16 | }
17 | ```
18 |
19 | golang 中字符串是不能赋值 `nil` 的,也不能跟 `nil` 比较。
20 |
21 | ## 写出以下打印内容
22 |
23 | ```go
24 | package main
25 | import "fmt"
26 | const (
27 | a = iota
28 | b = iota
29 | )
30 | const (
31 | name = "menglu"
32 | c = iota
33 | d = iota
34 | )
35 | func main() {
36 | fmt.Println(a)
37 | fmt.Println(b)
38 | fmt.Println(c)
39 | fmt.Println(d)
40 | }
41 | ```
42 |
43 | ## 找出下面代码的问题
44 |
45 | ```go
46 | package main
47 | import "fmt"
48 | type query func(string) string
49 |
50 | func exec(name string, vs ...query) string {
51 | ch := make(chan string)
52 | fn := func(i int) {
53 | ch <- vs[i](name)
54 | }
55 | for i, _ := range vs {
56 | go fn(i)
57 | }
58 | return <-ch
59 | }
60 |
61 | func main() {
62 | ret := exec("111", func(n string) string {
63 | return n + "func1"
64 | }, func(n string) string {
65 | return n + "func2"
66 | }, func(n string) string {
67 | return n + "func3"
68 | }, func(n string) string {
69 | return n + "func4"
70 | })
71 | fmt.Println(ret)
72 | }
73 | ```
74 |
75 | 上面的代码有严重的内存泄漏问题,出错的位置是 `go fn(i)`,实际上代码执行后会启动 4 个协程,但是因为 `ch` 是非缓冲的,只可能有一个协程写入成功。而其他三个协程会一直在后台等待写入。
76 |
77 | ## 写出以下打印结果,并解释下为什么这么打印的。
78 |
79 | ```go
80 | package main
81 | import (
82 | "fmt"
83 | )
84 | func main() {
85 | str1 := []string{"a", "b", "c"}
86 | str2 := str1[1:]
87 | str2[1] = "new"
88 | fmt.Println(str1)
89 | str2 = append(str2, "z", "x", "y")
90 | fmt.Println(str1)
91 | }
92 | ```
93 |
94 | golang 中的切片底层其实使用的是数组。当使用`str1[1:]` 使,`str2` 和 `str1` 底层共享一个数组,这回导致 `str2[1] = "new"` 语句影响 `str1`。
95 |
96 | 而 `append` 会导致底层数组扩容,生成新的数组,因此追加数据后的 `str2` 不会影响 `str1`。
97 |
98 | 但是为什么对 `str2` 复制后影响的确实 `str1` 的第三个元素呢?这是因为切片 `str2` 是从数组的第二个元素开始,`str2` 索引为 1 的元素对应的是 `str1` 索引为 2 的元素。
99 |
100 | ## 写出以下打印结果
101 |
102 | ```go
103 | package main
104 |
105 | import (
106 | "fmt"
107 | )
108 |
109 | type Student struct {
110 | Name string
111 | }
112 |
113 | func main() {
114 | fmt.Println(&Student{Name: "menglu"} == &Student{Name: "menglu"})
115 | fmt.Println(Student{Name: "menglu"} == Student{Name: "menglu"})
116 | }
117 | ```
118 |
119 | 个人理解:指针类型比较的是指针地址,非指针类型比较的是每个属性的值。
120 |
121 | ## 写出以下代码的问题
122 |
123 | ```go
124 | package main
125 |
126 | import (
127 | "fmt"
128 | )
129 |
130 | func main() {
131 | fmt.Println([...]string{"1"} == [...]string{"1"})
132 | fmt.Println([]string{"1"} == []string{"1"})
133 | }
134 | ```
135 |
136 | 数组只能与相同纬度长度以及类型的其他数组比较,切片之间不能直接比较。。
137 |
138 | ## 下面代码写法有什么问题?
139 |
140 | ```go
141 | package main
142 | import (
143 | "fmt"
144 | )
145 | type Student struct {
146 | Age int
147 | }
148 | func main() {
149 | kv := map[string]Student{"menglu": {Age: 21}}
150 | kv["menglu"].Age = 22
151 | s := []Student{{Age: 21}}
152 | s[0].Age = 22
153 | fmt.Println(kv, s)
154 | }
155 | ```
156 |
157 | golang中的`map` 通过`key`获取到的实际上是两个值,第一个是获取到的值,第二个是是否存在该`key`。因此不能直接通过`key`来赋值对象。
158 |
--------------------------------------------------------------------------------
/interview/q015.md:
--------------------------------------------------------------------------------
1 | ## golang 并发题目测试
2 |
3 | 题目来源: [Go并发编程小测验: 你能答对几道题?](https://colobu.com/2019/04/28/go-concurrency-quizzes/)
4 |
5 | ### 1 Mutex
6 |
7 | ```go
8 | package main
9 | import (
10 | "fmt"
11 | "sync"
12 | )
13 | var mu sync.Mutex
14 | var chain string
15 | func main() {
16 | chain = "main"
17 | A()
18 | fmt.Println(chain)
19 | }
20 | func A() {
21 | mu.Lock()
22 | defer mu.Unlock()
23 | chain = chain + " --> A"
24 | B()
25 | }
26 | func B() {
27 | chain = chain + " --> B"
28 | C()
29 | }
30 | func C() {
31 | mu.Lock()
32 | defer mu.Unlock()
33 | chain = chain + " --> C"
34 | }
35 | ```
36 |
37 | - A: 不能编译
38 | - B: 输出 main --> A --> B --> C
39 | - C: 输出 main
40 | - D: panic
41 |
42 | ### 2 RWMutex
43 |
44 | ```go
45 | package main
46 | import (
47 | "fmt"
48 | "sync"
49 | "time"
50 | )
51 | var mu sync.RWMutex
52 | var count int
53 | func main() {
54 | go A()
55 | time.Sleep(2 * time.Second)
56 | mu.Lock()
57 | defer mu.Unlock()
58 | count++
59 | fmt.Println(count)
60 | }
61 | func A() {
62 | mu.RLock()
63 | defer mu.RUnlock()
64 | B()
65 | }
66 | func B() {
67 | time.Sleep(5 * time.Second)
68 | C()
69 | }
70 | func C() {
71 | mu.RLock()
72 | defer mu.RUnlock()
73 | }
74 | ```
75 |
76 | - A: 不能编译
77 | - B: 输出 1
78 | - C: 程序hang住
79 | - D: panic
80 |
81 | ### 3 Waitgroup
82 |
83 | ```go
84 | package main
85 | import (
86 | "sync"
87 | "time"
88 | )
89 | func main() {
90 | var wg sync.WaitGroup
91 | wg.Add(1)
92 | go func() {
93 | time.Sleep(time.Millisecond)
94 | wg.Done()
95 | wg.Add(1)
96 | }()
97 | wg.Wait()
98 | }
99 | ```
100 |
101 | - A: 不能编译
102 | - B: 无输出,正常退出
103 | - C: 程序hang住
104 | - D: panic
105 |
106 | ### 4 双检查实现单例
107 |
108 | ```go
109 | package doublecheck
110 | import (
111 | "sync"
112 | )
113 | type Once struct {
114 | m sync.Mutex
115 | done uint32
116 | }
117 | func (o *Once) Do(f func()) {
118 | if o.done == 1 {
119 | return
120 | }
121 | o.m.Lock()
122 | defer o.m.Unlock()
123 | if o.done == 0 {
124 | o.done = 1
125 | f()
126 | }
127 | }
128 | ```
129 |
130 | - A: 不能编译
131 | - B: 可以编译,正确实现了单例
132 | - C: 可以编译,有并发问题,f函数可能会被执行多次
133 | - D: 可以编译,但是程序运行会panic
134 |
135 | ### 5 Mutex
136 |
137 | ```go
138 | package main
139 | import (
140 | "fmt"
141 | "sync"
142 | )
143 | type MyMutex struct {
144 | count int
145 | sync.Mutex
146 | }
147 | func main() {
148 | var mu MyMutex
149 | mu.Lock()
150 | var mu2 = mu
151 | mu.count++
152 | mu.Unlock()
153 | mu2.Lock()
154 | mu2.count++
155 | mu2.Unlock()
156 | fmt.Println(mu.count, mu2.count)
157 | }
158 | ```
159 |
160 | - A: 不能编译
161 | - B: 输出 1, 1
162 | - C: 输出 1, 2
163 | - D: panic
164 |
165 | ### 6 Pool
166 |
167 | ```go
168 | package main
169 | import (
170 | "bytes"
171 | "fmt"
172 | "runtime"
173 | "sync"
174 | "time"
175 | )
176 | var pool = sync.Pool{New: func() interface{} { return new(bytes.Buffer) }}
177 | func main() {
178 | go func() {
179 | for {
180 | processRequest(1 << 28) // 256MiB
181 | }
182 | }()
183 | for i := 0; i < 1000; i++ {
184 | go func() {
185 | for {
186 | processRequest(1 << 10) // 1KiB
187 | }
188 | }()
189 | }
190 | var stats runtime.MemStats
191 | for i := 0; ; i++ {
192 | runtime.ReadMemStats(&stats)
193 | fmt.Printf("Cycle %d: %dB\n", i, stats.Alloc)
194 | time.Sleep(time.Second)
195 | runtime.GC()
196 | }
197 | }
198 | func processRequest(size int) {
199 | b := pool.Get().(*bytes.Buffer)
200 | time.Sleep(500 * time.Millisecond)
201 | b.Grow(size)
202 | pool.Put(b)
203 | time.Sleep(1 * time.Millisecond)
204 | }
205 | ```
206 |
207 | - A: 不能编译
208 | - B: 可以编译,运行时正常,内存稳定
209 | - C: 可以编译,运行时内存可能暴涨
210 | - D: 可以编译,运行时内存先暴涨,但是过一会会回收掉
211 |
212 | ### 7 channel
213 |
214 | ```go
215 | package main
216 | import (
217 | "fmt"
218 | "runtime"
219 | "time"
220 | )
221 | func main() {
222 | var ch chan int
223 | go func() {
224 | ch = make(chan int, 1)
225 | ch <- 1
226 | }()
227 | go func(ch chan int) {
228 | time.Sleep(time.Second)
229 | <-ch
230 | }(ch)
231 | c := time.Tick(1 * time.Second)
232 | for range c {
233 | fmt.Printf("#goroutines: %d\n", runtime.NumGoroutine())
234 | }
235 | }
236 | ```
237 |
238 | - A: 不能编译
239 | - B: 一段时间后总是输出 `#goroutines: 1`
240 | - C: 一段时间后总是输出 `#goroutines: 2`
241 | - D: panic
242 |
243 | ### 8 channel
244 |
245 | ```go
246 | package main
247 | import "fmt"
248 | func main() {
249 | var ch chan int
250 | var count int
251 | go func() {
252 | ch <- 1
253 | }()
254 | go func() {
255 | count++
256 | close(ch)
257 | }()
258 | <-ch
259 | fmt.Println(count)
260 | }
261 | ```
262 |
263 | - A: 不能编译
264 | - B: 输出 1
265 | - C: 输出 0
266 | - D: panic
267 |
268 | ### 9 Map
269 |
270 | ```go
271 | package main
272 | import (
273 | "fmt"
274 | "sync"
275 | )
276 | func main() {
277 | var m sync.Map
278 | m.LoadOrStore("a", 1)
279 | m.Delete("a")
280 | fmt.Println(m.Len())
281 | }
282 | ```
283 |
284 | - A: 不能编译
285 | - B: 输出 1
286 | - C: 输出 0
287 | - D: panic
288 |
289 | ### 10 happens before
290 |
291 | ```go
292 | package main
293 | var c = make(chan int)
294 | var a int
295 | func f() {
296 | a = 1
297 | <-c
298 | }
299 | func main() {
300 | go f()
301 | c <- 0
302 | print(a)
303 | }
304 | ```
305 |
306 | - A: 不能编译
307 | - B: 输出 1
308 | - C: 输出 0
309 | - D: panic
310 |
311 |
312 |
313 |
314 |
315 | ## 答案
316 |
317 | ### 1. D
318 |
319 | 会产生死锁`panic`,因为`Mutex` 是互斥锁。
320 |
321 | ### 2. D
322 |
323 | 会产生死锁`panic`,根据`sync/rwmutex.go` 中注释可以知道,读写锁当有一个协程在等待写锁时,其他协程是不能获得读锁的,而在`A`和`C`中同一个调用链中间需要让出读锁,让写锁优先获取,而`A`的读锁又要求`C`调用完成,因此死锁。
324 |
325 |
326 | ### 3. D
327 |
328 | `WaitGroup` 在调用 `Wait` 之后是不能再调用 `Add` 方法的。
329 |
330 | ### 4. C
331 |
332 | 在多核CPU中,因为CPU缓存会导致多个核心中变量值不同步。
333 |
334 | ### 5. D
335 |
336 | 加锁后复制变量,会将锁的状态也复制,所以`mu1` 其实是已经加锁状态,再加锁会死锁。
337 |
338 | ### 6. C
339 |
340 | 个人理解,在单核CPU中,内存可能会稳定在`256MB`,如果是多核可能会暴涨。
341 |
342 | ### 7. C
343 |
344 | 因为 `ch` 未初始化,写和读都会阻塞,之后被第一个协程重新赋值,导致写的`ch` 都阻塞。
345 |
346 | ### 8. D
347 |
348 | `ch` 未有被初始化,关闭时会报错。
349 |
350 | ### 9. A
351 |
352 | `sync.Map` 没有 `Len` 方法。
353 |
354 | ### 10. B
355 |
356 | `c <- 0` 会阻塞依赖于 `f()` 的执行。
357 |
358 |
359 |
360 |
361 |
362 |
363 |
364 |
--------------------------------------------------------------------------------
/interview/q016.md:
--------------------------------------------------------------------------------
1 | # 记一道字节跳动的算法面试题
2 |
3 | ## 题目
4 |
5 | 这其实是一道变形的链表反转题,大致描述如下
6 | 给定一个单链表的头节点 head,实现一个调整单链表的函数,使得每K个节点之间为一组进行逆序,并且从链表的尾部开始组起,头部剩余节点数量不够一组的不需要逆序。(不能使用队列或者栈作为辅助)
7 |
8 | **例如:**
9 |
10 | 链表:`1->2->3->4->5->6->7->8->null, K = 3`。那么 `6->7->8`,`3->4->5`,`1->2`各位一组。调整后:`1->2->5->4->3->8->7->6->null`。其中 1,2不调整,因为不够一组。
11 |
12 | **解析**
13 |
14 | 原文:
--------------------------------------------------------------------------------
/interview/q017.md:
--------------------------------------------------------------------------------
1 | # 多协程查询切片问题
2 |
3 | ## 题目
4 |
5 | 假设有一个超长的切片,切片的元素类型为int,切片中的元素为乱序排序。限时5秒,使用多个goroutine查找切片中是否存在给定的值,在查找到目标值或者超时后立刻结束所有goroutine的执行。
6 |
7 | 比如,切片 `[23,32,78,43,76,65,345,762,......915,86]`,查找目标值为 345 ,如果切片中存在,则目标值输出`"Found it!"`并立即取消仍在执行查询任务的`goroutine`。
8 |
9 | 如果在超时时间未查到目标值程序,则输出`"Timeout!Not Found"`,同时立即取消仍在执行的查找任务的`goroutine`。
10 |
11 | > 答案:
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
--------------------------------------------------------------------------------
/interview/q018.md:
--------------------------------------------------------------------------------
1 | # 对已经关闭的的chan进行读写,会怎么样?为什么?
2 |
3 | ## 题目
4 |
5 | 对已经关闭的的 chan 进行读写,会怎么样?为什么?
6 |
7 | ## 回答
8 |
9 | - 读已经关闭的 chan 能一直读到东西,但是读到的内容根据通道内关闭前是否有元素而不同。
10 | - 如果 chan 关闭前,buffer 内有元素还未读 , 会正确读到 chan 内的值,且返回的第二个 bool 值(是否读成功)为 true。
11 | - 如果 chan 关闭前,buffer 内有元素已经被读完,chan 内无值,接下来所有接收的值都会非阻塞直接成功,返回 channel 元素的零值,但是第二个 bool 值一直为 false。
12 | - 写已经关闭的 chan 会 panic
13 |
14 |
15 | ## 示例
16 |
17 | ### 1. 写已经关闭的 chan
18 |
19 | ```go
20 | func main(){
21 | c := make(chan int,3)
22 | close(c)
23 | c <- 1
24 | }
25 | //输出结果
26 | panic: send on closed channel
27 |
28 | goroutine 1 [running]
29 | main.main()
30 | ...
31 | ```
32 |
33 | - 注意这个 send on closed channel,待会会提到。
34 |
35 | ### 2. 读已经关闭的 chan
36 |
37 | ```go
38 | package main
39 | import "fmt"
40 |
41 | func main() {
42 | fmt.Println("以下是数值的chan")
43 | ci:=make(chan int,3)
44 | ci<-1
45 | close(ci)
46 | num,ok := <- ci
47 | fmt.Printf("读chan的协程结束,num=%v, ok=%v\n",num,ok)
48 | num1,ok1 := <-ci
49 | fmt.Printf("再读chan的协程结束,num=%v, ok=%v\n",num1,ok1)
50 | num2,ok2 := <-ci
51 | fmt.Printf("再再读chan的协程结束,num=%v, ok=%v\n",num2,ok2)
52 |
53 | fmt.Println("以下是字符串chan")
54 | cs := make(chan string,3)
55 | cs <- "aaa"
56 | close(cs)
57 | str,ok := <- cs
58 | fmt.Printf("读chan的协程结束,str=%v, ok=%v\n",str,ok)
59 | str1,ok1 := <-cs
60 | fmt.Printf("再读chan的协程结束,str=%v, ok=%v\n",str1,ok1)
61 | str2,ok2 := <-cs
62 | fmt.Printf("再再读chan的协程结束,str=%v, ok=%v\n",str2,ok2)
63 |
64 | fmt.Println("以下是结构体chan")
65 | type MyStruct struct{
66 | Name string
67 | }
68 | cstruct := make(chan MyStruct,3)
69 | cstruct <- MyStruct{Name: "haha"}
70 | close(cstruct)
71 | stru,ok := <- cstruct
72 | fmt.Printf("读chan的协程结束,stru=%v, ok=%v\n",stru,ok)
73 | stru1,ok1 := <-cs
74 | fmt.Printf("再读chan的协程结束,stru=%v, ok=%v\n",stru1,ok1)
75 | stru2,ok2 := <-cs
76 | fmt.Printf("再再读chan的协程结束,stru=%v, ok=%v\n",stru2,ok2)
77 | }
78 |
79 | ```
80 |
81 | 输出结果
82 |
83 | ```bash
84 | 以下是数值的chan
85 | 读chan的协程结束,num=1, ok=true
86 | 再读chan的协程结束,num=0, ok=false
87 | 再再读chan的协程结束,num=0, ok=false
88 | 以下是字符串chan
89 | 读chan的协程结束,str=aaa, ok=true
90 | 再读chan的协程结束,str=, ok=false
91 | 再再读chan的协程结束,str=, ok=false
92 | 以下是结构体chan
93 | 读chan的协程结束,stru={haha}, ok=true
94 | 再读chan的协程结束,stru=, ok=false
95 | 再再读chan的协程结束,stru=, ok=false
96 | ```
97 |
98 |
99 | ## 多问一句
100 |
101 | ### 1. 为什么写已经关闭的 `chan` 就会 `panic` 呢?
102 |
103 | ```go
104 | //在 src/runtime/chan.go
105 | func chansend(c *hchan,ep unsafe.Pointer,block bool,callerpc uintptr) bool {
106 | //省略其他
107 | if c.closed != 0 {
108 | unlock(&c.lock)
109 | panic(plainError("send on closed channel"))
110 | }
111 | //省略其他
112 | }
113 | ```
114 |
115 | - 当 `c.closed != 0` 则为通道关闭,此时执行写,源码提示直接 `panic`,输出的内容就是上面提到的 `"send on closed channel"`。
116 |
117 | ### 2. 为什么读已关闭的 chan 会一直能读到值?
118 |
119 | ```go
120 | func chanrecv(c *hchan,ep unsafe.Pointer,block bool) (selected,received bool) {
121 | //省略部分逻辑
122 | lock(&c.lock)
123 | //当chan被关闭了,而且缓存为空时
124 | //ep 是指 val,ok := <-c 里的val地址
125 | if c.closed != 0 && c.qcount == 0 {
126 | if receenabled {
127 | raceacquire(c.raceaddr())
128 | }
129 | unlock(&c.lock)
130 | //如果接受之的地址不空,那接收值将获得一个该值类型的零值
131 | //typedmemclr 会根据类型清理响应的内存
132 | //这就解释了上面代码为什么关闭的chan 会返回对应类型的零值
133 | if ep != null {
134 | typedmemclr(c.elemtype,ep)
135 | }
136 | //返回两个参数 selected,received
137 | // 第二个采纳数就是 val,ok := <- c 里的 ok
138 | //也就解释了为什么读关闭的chan会一直返回false
139 | return true,false
140 | }
141 | }
142 | ```
143 | - `c.closed != 0 && c.qcount == 0` 指通道已经关闭,且缓存为空的情况下(已经读完了之前写到通道里的值)
144 | - 如果接收值的地址 `ep` 不为空
145 | - 那接收值将获得是一个该类型的零值
146 | - `typedmemclr` 会根据类型清理相应地址的内存
147 | - 这就解释了上面代码为什么关闭的 chan 会返回对应类型的零值
148 |
149 |
150 |
151 |
152 |
153 |
--------------------------------------------------------------------------------
/interview/q019.md:
--------------------------------------------------------------------------------
1 | # 简单聊聊内存逃逸?
2 |
3 | ## 问题
4 |
5 | 知道golang的内存逃逸吗?什么情况下会发生内存逃逸?
6 |
7 | ## 回答
8 |
9 | golang程序变量会携带有一组校验数据,用来证明它的整个生命周期是否在运行时完全可知。如果变量通过了这些校验,它就可以在栈上分配。否则就说它 逃逸 了,必须在堆上分配。
10 |
11 | 能引起变量逃逸到堆上的典型情况:
12 |
13 | - **在方法内把局部变量指针返回** 局部变量原本应该在栈中分配,在栈中回收。但是由于返回时被外部引用,因此其生命周期大于栈,则溢出。
14 | - **发送指针或带有指针的值到 channel 中。** 在编译时,是没有办法知道哪个 `goroutine` 会在 `channel` 上接收数据。所以编译器没法知道变量什么时候才会被释放。
15 | - **在一个切片上存储指针或带指针的值。** 一个典型的例子就是 `[]*string` 。这会导致切片的内容逃逸。尽管其后面的数组可能是在栈上分配的,但其引用的值一定是在堆上。
16 | - **slice 的背后数组被重新分配了,因为 append 时可能会超出其容量( cap )。** slice 初始化的地方在编译时是可以知道的,它最开始会在栈上分配。如果切片背后的存储要基于运行时的数据进行扩充,就会在堆上分配。
17 | - **在 interface 类型上调用方法。** 在 interface 类型上调用方法都是动态调度的 —— 方法的真正实现只能在运行时知道。想像一个 io.Reader 类型的变量 r , 调用 r.Read(b) 会使得 r 的值和切片b 的背后存储都逃逸掉,所以会在堆上分配。
18 |
19 | ## 举例
20 |
21 | **通过一个例子加深理解,接下来尝试下怎么通过 `go build -gcflags=-m` 查看逃逸的情况。**
22 |
23 | ```go
24 | package main
25 | import "fmt"
26 | type A struct {
27 | s string
28 | }
29 | // 这是上面提到的 "在方法内把局部变量指针返回" 的情况
30 | func foo(s string) *A {
31 | a := new(A)
32 | a.s = s
33 | return a //返回局部变量a,在C语言中妥妥野指针,但在go则ok,但a会逃逸到堆
34 | }
35 | func main() {
36 | a := foo("hello")
37 | b := a.s + " world"
38 | c := b + "!"
39 | fmt.Println(c)
40 | }
41 | ```
42 |
43 | 执行go build -gcflags=-m main.go
44 |
45 | ```bash
46 | go build -gcflags=-m main.go
47 | # command-line-arguments
48 | ./main.go:7:6: can inline foo
49 | ./main.go:13:10: inlining call to foo
50 | ./main.go:16:13: inlining call to fmt.Println
51 | /var/folders/45/qx9lfw2s2zzgvhzg3mtzkwzc0000gn/T/go-build409982591/b001/_gomod_.go:6:6: can inline init.0
52 | ./main.go:7:10: leaking param: s
53 | ./main.go:8:10: new(A) escapes to heap
54 | ./main.go:16:13: io.Writer(os.Stdout) escapes to heap
55 | ./main.go:16:13: c escapes to heap
56 | ./main.go:15:9: b + "!" escapes to heap
57 | ./main.go:13:10: main new(A) does not escape
58 | ./main.go:14:11: main a.s + " world" does not escape
59 | ./main.go:16:13: main []interface {} literal does not escape
60 | :1: os.(*File).close .this does not escape
61 | ```
62 |
63 | - `./main.go:8:10: new(A) escapes to heap` 说明 `new(A)` 逃逸了,符合上述提到的常见情况中的第一种。
64 | - `./main.go:14:11: main a.s + " world" does not escape` 说明 b 变量没有逃逸,因为它只在方法内存在,会在方法结束时被回收。
65 | - `./main.go:15:9: b + "!" escapes to heap` 说明 c 变量逃逸,通过`fmt.Println(a ...interface{})`打印的变量,都会发生逃逸,感兴趣的朋友可以去查查为什么。
66 |
67 | 以上操作其实就叫逃逸分析。下篇文章,跟大家聊聊怎么用一个比较trick的方法使变量不逃逸。方便大家在面试官面前秀一波。
68 |
69 |
70 | >原文
71 |
72 |
73 |
74 |
75 |
76 |
77 |
78 |
79 |
--------------------------------------------------------------------------------
/interview/q020.md:
--------------------------------------------------------------------------------
1 | # 字符串转成byte数组,会发生内存拷贝吗?
2 |
3 | ## 问题
4 |
5 | 字符串转成byte数组,会发生内存拷贝吗?
6 |
7 | ## 回答
8 |
9 | 字符串转成切片,会产生拷贝。严格来说,只要是发生类型强转都会发生内存拷贝。那么问题来了。
10 |
11 | 频繁的内存拷贝操作听起来对性能不大友好。有没有什么办法可以在字符串转成切片的时候不用发生拷贝呢?
12 |
13 | ## 解释
14 |
15 | ```go
16 | package main
17 |
18 | import (
19 | "fmt"
20 | "reflect"
21 | "unsafe"
22 | )
23 |
24 | func main() {
25 | a :="aaa"
26 | ssh := *(*reflect.StringHeader)(unsafe.Pointer(&a))
27 | b := *(*[]byte)(unsafe.Pointer(&ssh))
28 | fmt.Printf("%v",b)
29 | }
30 | ```
31 |
32 | **`StringHeader` 是字符串在go的底层结构。**
33 |
34 | ```go
35 | type StringHeader struct {
36 | Data uintptr
37 | Len int
38 | }
39 | ```
40 |
41 | **`SliceHeader` 是切片在go的底层结构。**
42 |
43 | ```go
44 | type SliceHeader struct {
45 | Data uintptr
46 | Len int
47 | Cap int
48 | }
49 | ```
50 |
51 | 那么如果想要在底层转换二者,只需要把 StringHeader 的地址强转成 SliceHeader 就行。那么go有个很强的包叫 unsafe 。
52 |
53 | 1. `unsafe.Pointer(&a)`方法可以得到变量a的地址。
54 | 2. `(*reflect.StringHeader)(unsafe.Pointer(&a))` 可以把字符串a转成底层结构的形式。
55 | 3. `(*[]byte)(unsafe.Pointer(&ssh))` 可以把ssh底层结构体转成byte的切片的指针。
56 | 4. 再通过 `*`转为指针指向的实际内容。
57 |
58 |
59 |
60 |
61 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 |
71 |
72 |
73 |
--------------------------------------------------------------------------------
/interview/q022.md:
--------------------------------------------------------------------------------
1 | # sync.Map 的用法
2 |
3 | ## 问题
4 |
5 | ```go
6 | package main
7 |
8 | import (
9 | "fmt"
10 | "sync"
11 | )
12 |
13 | func main(){
14 | var m sync.Map
15 | m.Store("address",map[string]string{"province":"江苏","city":"南京"})
16 | v,_ := m.Load("address")
17 | fmt.Println(v["province"])
18 | }
19 | ```
20 |
21 | - A,江苏;
22 | - B`,v["province"]`取值错误;
23 | - C,`m.Store`存储错误;
24 | - D,不知道
25 |
26 | ## 解析
27 |
28 | `invalid operation: v["province"] (type interface {} does not support indexing)`
29 | 因为 `func (m *Map) Store(key interface{}, value interface{})`
30 | 所以 `v`类型是 `interface {}` ,这里需要一个类型断言
31 |
32 | ```go
33 | fmt.Println(v.(map[string]string)["province"]) //江苏
34 | ```
35 |
--------------------------------------------------------------------------------
/pic/Go Web 开发.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/yongxinz/gopher/030046cf44fb5b2a2c90112b3d179ae9ba9df000/pic/Go Web 开发.png
--------------------------------------------------------------------------------
/pic/Go 书籍推荐.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/yongxinz/gopher/030046cf44fb5b2a2c90112b3d179ae9ba9df000/pic/Go 书籍推荐.png
--------------------------------------------------------------------------------
/pic/Go 入门教程.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/yongxinz/gopher/030046cf44fb5b2a2c90112b3d179ae9ba9df000/pic/Go 入门教程.png
--------------------------------------------------------------------------------
/pic/Go 学习路线.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/yongxinz/gopher/030046cf44fb5b2a2c90112b3d179ae9ba9df000/pic/Go 学习路线.png
--------------------------------------------------------------------------------
/pic/Go 实用工具.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/yongxinz/gopher/030046cf44fb5b2a2c90112b3d179ae9ba9df000/pic/Go 实用工具.png
--------------------------------------------------------------------------------
/pic/Go 技术社区.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/yongxinz/gopher/030046cf44fb5b2a2c90112b3d179ae9ba9df000/pic/Go 技术社区.png
--------------------------------------------------------------------------------
/pic/Go 练手项目.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/yongxinz/gopher/030046cf44fb5b2a2c90112b3d179ae9ba9df000/pic/Go 练手项目.png
--------------------------------------------------------------------------------
/pic/Go 进阶教程.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/yongxinz/gopher/030046cf44fb5b2a2c90112b3d179ae9ba9df000/pic/Go 进阶教程.png
--------------------------------------------------------------------------------
/pic/biancheng.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/yongxinz/gopher/030046cf44fb5b2a2c90112b3d179ae9ba9df000/pic/biancheng.png
--------------------------------------------------------------------------------
/pic/draveness.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/yongxinz/gopher/030046cf44fb5b2a2c90112b3d179ae9ba9df000/pic/draveness.png
--------------------------------------------------------------------------------
/pic/eddycjy.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/yongxinz/gopher/030046cf44fb5b2a2c90112b3d179ae9ba9df000/pic/eddycjy.png
--------------------------------------------------------------------------------
/pic/gobook-1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/yongxinz/gopher/030046cf44fb5b2a2c90112b3d179ae9ba9df000/pic/gobook-1.png
--------------------------------------------------------------------------------
/pic/gobook-2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/yongxinz/gopher/030046cf44fb5b2a2c90112b3d179ae9ba9df000/pic/gobook-2.png
--------------------------------------------------------------------------------
/pic/gobook-3.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/yongxinz/gopher/030046cf44fb5b2a2c90112b3d179ae9ba9df000/pic/gobook-3.png
--------------------------------------------------------------------------------
/pic/gobook-4.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/yongxinz/gopher/030046cf44fb5b2a2c90112b3d179ae9ba9df000/pic/gobook-4.png
--------------------------------------------------------------------------------
/pic/gobook-5.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/yongxinz/gopher/030046cf44fb5b2a2c90112b3d179ae9ba9df000/pic/gobook-5.png
--------------------------------------------------------------------------------
/pic/gobook-6.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/yongxinz/gopher/030046cf44fb5b2a2c90112b3d179ae9ba9df000/pic/gobook-6.png
--------------------------------------------------------------------------------
/pic/gobook-7.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/yongxinz/gopher/030046cf44fb5b2a2c90112b3d179ae9ba9df000/pic/gobook-7.png
--------------------------------------------------------------------------------
/pic/gobook-8.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/yongxinz/gopher/030046cf44fb5b2a2c90112b3d179ae9ba9df000/pic/gobook-8.png
--------------------------------------------------------------------------------
/pic/gobyexample.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/yongxinz/gopher/030046cf44fb5b2a2c90112b3d179ae9ba9df000/pic/gobyexample.png
--------------------------------------------------------------------------------
/pic/mojitv.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/yongxinz/gopher/030046cf44fb5b2a2c90112b3d179ae9ba9df000/pic/mojitv.png
--------------------------------------------------------------------------------
/pic/playground.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/yongxinz/gopher/030046cf44fb5b2a2c90112b3d179ae9ba9df000/pic/playground.png
--------------------------------------------------------------------------------
/pic/rego.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/yongxinz/gopher/030046cf44fb5b2a2c90112b3d179ae9ba9df000/pic/rego.png
--------------------------------------------------------------------------------
/pic/runoob.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/yongxinz/gopher/030046cf44fb5b2a2c90112b3d179ae9ba9df000/pic/runoob.png
--------------------------------------------------------------------------------
/pic/topgoer.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/yongxinz/gopher/030046cf44fb5b2a2c90112b3d179ae9ba9df000/pic/topgoer.png
--------------------------------------------------------------------------------
/pic/w3c.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/yongxinz/gopher/030046cf44fb5b2a2c90112b3d179ae9ba9df000/pic/w3c.png
--------------------------------------------------------------------------------
/sc/00-开发环境搭建以及开发工具 VS Code 配置.md:
--------------------------------------------------------------------------------
1 | 
2 |
3 | **原文链接:** [Go 专栏|开发环境搭建以及开发工具 VS Code 配置](https://mp.weixin.qq.com/s/x1OW--3mwSTjgB2HaKGVVA)
4 |
5 | Go 专栏的第一篇,想学 Go 的同学们,走起~
6 |
7 | ### Go 安装
8 |
9 | 我的个人电脑是 Mac,然后工作主要使用 Linux,所以在这里主要介绍在这两个系统下的安装。
10 |
11 | **下载地址:**
12 |
13 | - Go 官网下载地址:https://golang.org/dl/
14 | - Go 官方镜像站(推荐):https://golang.google.cn/dl/
15 |
16 | 
17 |
18 | 直接安装最新版本 go1.16.6,后续文章都会在此版本下开发,测试。
19 |
20 | #### Mac 下安装
21 |
22 | 可以通过 `brew` 方式安装,也可以直接在官网下载可执行文件,然后双击安装包,不停下一步就可以了。
23 |
24 | 
25 |
26 | #### Linux 下安装
27 |
28 | 下载安装包:
29 | ```shell
30 | $ wget https://golang.google.cn/dl/go1.16.6.linux-amd64.tar.gz
31 | ```
32 |
33 | 解压到 `/usr/local` 目录:
34 | ```shell
35 | $ sudo tar -zxvf go1.16.6.linux-amd64.tar.gz -C /usr/local
36 | ```
37 |
38 | 然后配置环境变量,打开 `$HOME/.bash_profile` 文件,增加下面两行代码:
39 |
40 | ```shell
41 | export GOROOT=/usr/local/go
42 | export PATH=$PATH:$GOROOT/bin
43 | ```
44 |
45 | 最后使环境变量生效:
46 |
47 | ```shell
48 | $ source $HOME/.bash_profile
49 | ```
50 |
51 | 安装完成后,在终端执行查看版本命令,如果能正确输出版本信息,那就说明安装成功了。
52 |
53 | ```shell
54 | $ go version
55 | go version go1.16.6 linux/amd64
56 | ```
57 |
58 | ### 配置环境变量
59 |
60 | `GOROOT` 和 `GOPATH` 都是环境变量,其中 `GOROOT` 是我们安装 Go 开发包的路径,`GOPATH` 会有一个默认目录。
61 |
62 | 由于 go1.11 之后使用 go mod 来管理依赖包,不再强制我们必须把代码写在 `GOPATH/src` 目录下,所以使用默认即可,无需修改。
63 |
64 | 默认 `GOPROXY` 配置是 `GOPROXY=https://proxy.golang.org,direct`,由于国内访问不到,所以我们需要换一个 PROXY,这里推荐使用:
65 |
66 | 1. https://goproxy.io
67 | 2. https://goproxy.cn
68 |
69 | 配置 `GOPROXY`:
70 |
71 | ```shell
72 | $ go env -w GO111MODULE=on
73 | $ go env -w GOPROXY=https://goproxy.cn,direct
74 | ```
75 |
76 | go mod 先这样配置就可以了,后续再来写文章详细介绍。
77 |
78 | ### 开发工具 VS Code
79 |
80 | 开发工具可以根据自己的喜好来,可以用 Goland,VS Code,当然 Vim 也可以。
81 |
82 | 我比较喜欢 VS Code,插件丰富,而且免费。
83 |
84 | 官方下载地址:https://code.visualstudio.com/Download
85 |
86 | 安装 Go 插件,并重启:
87 |
88 | 
89 |
90 | ### 第一个 Go 程序
91 |
92 | 好了,一切准备就绪,让我们开始 Hello World 吧。
93 |
94 | ```go
95 | // 00_hello.go
96 |
97 | package main // 声明 main 包
98 |
99 | import "fmt" // 导入内置 fmt 包
100 |
101 | func main(){ // main函数,程序执行入口
102 | fmt.Println("Hello World!") // 在终端打印 Hello World!
103 | }
104 | ```
105 |
106 | 使用 `go build` 命令编译:
107 | ```shell
108 | $ go build 00_hello.go
109 | $ ls
110 | 00_hello 00_hello.go go.mod
111 | ```
112 |
113 | 可以看到在目录下生成了可执行文件 `00_hello`,然后运行一下试试:
114 | ```shell
115 | $ ./00_hello
116 | Hello World!
117 | ```
118 |
119 | 成功输出!
120 |
121 | 还可以直接使用 `go run` 命令来执行代码,在调试的时候更加方便。
122 | ```shell
123 | $ go run 00_hello.go
124 | Hello World!
125 | ```
126 |
127 | 我可真厉害,又学会了一门编程语言。
128 |
129 |
130 | ---
131 | 文章中的脑图和源码都上传到了 GitHub,有需要的同学可自行下载。
132 |
133 | **地址:** https://github.com/yongxinz/gopher/tree/main/sc
134 |
135 | 关注公众号 **AlwaysBeta**,回复「**goebook**」领取 Go 编程经典书籍。
136 |
137 |
138 |
139 |
140 |
--------------------------------------------------------------------------------
/sc/02-基础数据类型:整数、浮点数、复数、布尔值和字符串.md:
--------------------------------------------------------------------------------
1 | 
2 |
3 | **原文链接:** [Go 专栏|基础数据类型:整数、浮点数、复数、布尔值和字符串](https://mp.weixin.qq.com/s/aotpxglSGRFfl6A1xPN-dw)
4 |
5 | Go 专栏的第三篇,本文内容依旧很基础,很简单。如果有编程经验的话,可能扫一眼就了然于胸了。但如果刚开始接触编程,建议还是好好看看,把文中的代码 demo 都自己跑一遍。只有基础打好了,才能向更高的目标迈进。
6 |
7 | 话不多说,走起~
8 |
9 | > 本文所有代码基于 go1.16.6 编写。
10 |
11 | Go 的数据类型分四大类:
12 |
13 | 1. **基础类型:** 数字 number,字符串 string 和布尔型 boolean。
14 | 2. **聚合类型:** 数组 array 和结构体 struct。
15 | 3. **引用类型:** 指针 pointer,切片 slice,字典 map,函数 func 和通道 channel。
16 | 4. **接口类型:** 接口 interface。
17 |
18 | 其中,基础类型又分为:
19 |
20 | 1. **整型:** int8、uint8、byte、int16、uint16、int32、uint32、int64、uint64、int、uint、uintptr。
21 | 2. **浮点型:** float32,float64。
22 | 3. **复数类型:** complex64、complex128。
23 | 4. **布尔型:** bool。
24 | 5. **字符串:** string。
25 | 6. **字符型:** rune。
26 |
27 | ### 整数
28 |
29 | 整数一共有 12 种类型,分为有符号整数和无符号整数,为了方便查看,我在这里整理了一个表格:
30 |
31 |
32 | 类型 | 长度(字节) | 范围
33 | ---|---|---
34 | int8 | 1 | -128 ~ 127
35 | uint8 | 1 | 0~255
36 | byte | 1 | 0~255
37 | int16 | 2 | -32768~32767
38 | uint16 | 2 | 0~65535
39 | int32 | 4 | -2147483648~2147483647
40 | uint32 | 4 | 0~4294967295
41 | int64 | 8 | -9223372036854775808~9223372036854775807
42 | uint64 | 8 | 0~18446744073709551615
43 | int | 4/8 | 同上
44 | uint | 4/8 | 同上
45 | uintptr | 4/8 | 同上,足以存储指针的 uint
46 |
47 | 一般我们在开发的时候,使用 int 和 uint 即可。除非有明确需要指定长度的需求,才用 int8 这种类型。
48 |
49 | #### 类型转换
50 |
51 | 不管是算术运算,还是逻辑运算,Go 要求操作数的类型必须一致,如果不一致的话,会报错。
52 |
53 | ```go
54 | var a int = 10
55 | var b int32 = 20
56 |
57 | // fmt.Println(a + b) // 报错 invalid operation: a + b (mismatched types int and int32)
58 | ```
59 |
60 | 这一点在写代码的时候,开发工具就会提醒了,而且还会把强制类型转换的代码给写好,还是很棒的。
61 |
62 | ```go
63 | var a int = 10
64 | var b int32 = 20
65 | fmt.Println(a + int(b)) // 输出 30
66 | ```
67 |
68 | 但有一点需要注意,当浮点型转整型的话,可能会有精度损失。
69 |
70 | ```go
71 | var c float32 = 10.23
72 | fmt.Println(int(c)) // 输出 10
73 | ```
74 |
75 | #### 数值运算
76 |
77 | 算数运算符包括:`+`、`-`、`*`、`/` 和 `%`。
78 |
79 | `%` 取模运算符只能用于整数,取模余数的正负号与被除数一致。
80 |
81 | ```go
82 | // 取模
83 | fmt.Println(5 % 3) // 输出 2
84 | fmt.Println(-5 % -3) // 输出 -2
85 | ```
86 |
87 | `/` 除法运算符,如果操作数都是整数,则结果也是整数。
88 |
89 | ```go
90 | // 除法
91 | fmt.Println(5 / 3) // 输出 1
92 | fmt.Println(5.0 / 3.0) // 输出 1.6666666666666667
93 | ```
94 |
95 | #### 比较运算
96 |
97 | 比较运算符包括:`>`、`>=`、`<`、`<=`、`==` 和 `!=`。
98 |
99 | 不同类型之间不可比较,但整型可以与字面量直接比较。
100 |
101 | ```go
102 | // 比较运算
103 | var i int32
104 | var j int64
105 | i, j = 1, 2
106 |
107 | // if i == j { // 报错 invalid operation: i == j (mismatched types int32 and int64)
108 | // fmt.Println("i and j are equal.")
109 | // }
110 | if i == 1 || j == 2 {
111 | fmt.Println("equal.")
112 | }
113 | ```
114 |
115 | #### 位运算
116 |
117 | 位运算符包括:`&`、`|`、`^`、`&^`、`<<` 和 `>>`。
118 |
119 | 这个就不过多介绍了,平时开发过程中用到的也比较少。
120 |
121 | ### 浮点数
122 |
123 | 浮点类型包括两种,分别是 float32 和 float64。
124 |
125 | 浮点数字面量会被自动推断为 float64。
126 |
127 | ```go
128 | f := 10.0 // 自动推断为 float64
129 | ```
130 |
131 | 当对浮点数进行比较运算时,不能直接使用 `==` 和 `!=`,结果会不稳定。应该使用 math 标准库。
132 |
133 | ### 复数
134 |
135 | 复数类型包括两种,分别是 complex64 和 complex128。
136 |
137 | 操作复数的内置函数一共有 3 个,分别是:
138 |
139 | 1. `complex`:构造一个复数。
140 | 2. `real`:获取复数的实部。
141 | 3. `imag`:获取复数的虚部。
142 |
143 | ```go
144 | // 复数
145 | var x complex64 = 3 + 5i
146 | var y complex128 = complex(3.5, 10)
147 | // 分别打印实部和虚部
148 | fmt.Println(real(x), imag(x)) // 输出 3 5
149 | fmt.Println(real(y), imag(y)) // 输出 3.5 10
150 | ```
151 |
152 | ### 布尔值
153 |
154 | 布尔类型的关键字是 bool,有两个值,分别是: true 和 false。
155 |
156 | ```go
157 | ok := true
158 | fmt.Println(ok)
159 | ```
160 |
161 | 布尔型和整型不能相互转换。
162 |
163 | ```go
164 | var e bool
165 | e = bool(1) // 报错 cannot convert 1 (type untyped int) to type bool
166 | ```
167 |
168 | `if` 和 `for` 语句的条件部分一定是布尔类型的值或表达式。如果之前写 Python 比较多的话,一定要注意这点,我就在这翻过车。
169 |
170 | ```go
171 | m := 1
172 | // if m { // 报错 non-bool m (type int) used as if condition
173 | // fmt.Println("is true")
174 | // }
175 | if m == 1 {
176 | fmt.Println("m is 1")
177 | }
178 | ```
179 |
180 | ### 字符串
181 |
182 | 字符串关键字是 string,也属于一种基本类型。
183 |
184 | 字符串可以直接通过字面量来初始化:
185 |
186 | ```go
187 | // 字符串
188 | s1 := "hello"
189 | s2 := "world"
190 | ```
191 |
192 | 使用 `` ` `` 定义不做转义的原始字符串,支持换行:
193 |
194 | ```go
195 | // 原始字符串
196 | s := `row1\r\n
197 | row2`
198 | fmt.Println(s)
199 | ```
200 |
201 | 拼接字符串:
202 |
203 | ```go
204 | // 字符串拼接
205 | s3 := s1 + s2
206 | fmt.Println(s3)
207 | ```
208 |
209 | 获取字符串长度:
210 |
211 | ```go
212 | // 取字符串长度
213 | fmt.Println(len(s3))
214 | ```
215 |
216 | 按索引下标来获取字符:
217 |
218 | ```go
219 | // 取单个字符
220 | fmt.Println(s3[4])
221 | ```
222 |
223 | 字符串切片:
224 |
225 | ```go
226 | // 字符串切片
227 | fmt.Println(s3[2:4])
228 | fmt.Println(s3[:4])
229 | fmt.Println(s3[2:])
230 | fmt.Println(s3[:])
231 | ```
232 |
233 | 字符串是不可修改的,所以如果给字符串赋值的话,会报错:
234 |
235 | ```go
236 | // 修改报错
237 | s3[0] = "H" // cannot assign to s3[0] (strings are immutable)
238 | ```
239 |
240 | 遍历字符串:
241 |
242 | ```go
243 | s4 := "hello 世界"
244 |
245 | // 遍历字节数组
246 | for i := 0; i < len(s4); i++ {
247 | fmt.Println(i, s4[i])
248 | }
249 |
250 | // 输出
251 | // 0 104
252 | // 1 101
253 | // 2 108
254 | // 3 108
255 | // 4 111
256 | // 5 32
257 | // 6 228
258 | // 7 184
259 | // 8 150
260 | // 9 231
261 | // 10 149
262 | // 11 140
263 |
264 | // 遍历 rune 数组
265 | for i, v := range s4 {
266 | fmt.Println(i, v)
267 | }
268 |
269 | // 输出
270 | // 0 104
271 | // 1 101
272 | // 2 108
273 | // 3 108
274 | // 4 111
275 | // 5 32
276 | // 6 19990
277 | // 9 30028
278 | ```
279 |
280 | 从结果可以看出两者的区别,第一种是以字节数组方式遍历;第二种是以 Unicode 字符方式遍历。
281 |
282 | 以字节数组方式遍历,字符类型是 byte,长度是 1。虽然字符串直观上看长度是 8,但中文字符在 UTF-8 编码中占 3 个字符,所以总长度是 12。
283 |
284 | 以 Unicode 方式遍历,字符类型是 rune。
285 |
286 | Go 中支持两种字符类型,一个是 byte,uint8 的别名,表示 UTF-8 字符串的单个字节的值;另一个是 rune,int32 的别名,表示单个 Unicode 字符。
287 |
288 | 最后再说一点,Go 的源文件是按 UTF-8 编码的,所以我们在选择编码格式上一定要选 UTF-8,否则可能会有一些莫名其妙的错误出现。
289 |
290 | ### 总结
291 |
292 | 本文主要介绍了五种基础数据类型,分别是:整数,浮点数,复数,布尔值和字符串。
293 |
294 | 其中复数用的比较少,整数和浮点数主要用在需要数值类型的场景,布尔值大多用在条件语句。
295 |
296 | 字符串的使用还是挺多的,而且也有一些标准库的支持,比如 bytes、strings、strconv、unicode。
297 |
298 | 有时间的话还是要多学学标准库才行。
299 |
300 | ---
301 |
302 | 文章中的脑图和源码都上传到了 GitHub,有需要的同学可自行下载。
303 |
304 | **地址:** https://github.com/yongxinz/gopher/tree/main/sc
305 |
306 | 关注公众号 **AlwaysBeta**,回复「**goebook**」领取 Go 编程经典书籍。
307 |
308 |
309 |
310 |
311 |
312 | **Go 专栏文章列表:**
313 |
314 | 1. [开发环境搭建以及开发工具 VS Code 配置]()
315 | 2. [变量和常量的声明与赋值]()
--------------------------------------------------------------------------------
/sc/06-函数那些事.md:
--------------------------------------------------------------------------------
1 | 
2 |
3 | **原文链接:** [Go 专栏|函数那些事](https://mp.weixin.qq.com/s/RKpyVrhtSk9pXMWNVpWYjQ)
4 |
5 | 曾经很长一段时间,我都为自己是互联网科技公司的一员而感到自豪,我觉得我们与众不同。
6 |
7 | 我们的管理更扁平化,没有那么多官僚主义,充满活力,朝气蓬勃。而且我们的产品正在改变大家的衣食住行,我们正在改变世界。
8 |
9 | 但近几年发生的一系列事件,都让我的信心产生动摇,不停在捶打我:醒醒吧,兄弟,事实不是你想象的那样。
10 |
11 | 我能做些什么呢?不知道。
12 |
13 | 还是努力更文吧,争取早日不做打工人。
14 |
15 | ### 函数定义
16 |
17 | 函数包括以下几个部分:关键词 `func`,函数名,参数列表,返回列表和函数体。
18 |
19 | ```go
20 | func name(param-list) ret-list {
21 | body
22 | }
23 | ```
24 |
25 | 函数可以没有参数,也可以没有返回值。
26 |
27 | ```go
28 | func funcA() {
29 | fmt.Println("i am funcA") // i am funcA
30 | }
31 | ```
32 |
33 | 函数的类型称作函数签名,当两个函数的参数列表和返回列表相同时,则两个函数的类型或签名就相同。
34 |
35 | ```go
36 | func add(x int, y int) int {
37 | return x + y
38 | }
39 |
40 | func sub(x int, y int) (z int) {
41 | z = x - y
42 | return
43 | }
44 |
45 | fmt.Printf("%T\n", add) // func(int, int) int
46 | fmt.Printf("%T\n", sub) // func(int, int) int
47 | ```
48 |
49 | ### 参数
50 |
51 | 多个相邻类型的参数可以使用简写模式,所以刚才的 `add` 和 `sub` 函数还可以这样写:
52 |
53 | ```go
54 | func add(x, y int) int {
55 | return x + y
56 | }
57 |
58 | func sub(x, y int) (z int) {
59 | z = x - y
60 | return
61 | }
62 | ```
63 |
64 | 支持不定参数,使用 `...type` 语法。注意不定参数必须是函数的最后一个参数。
65 |
66 | ```go
67 | func funcSum(args ...int) (ret int) {
68 | for _, arg := range args {
69 | ret += arg
70 | }
71 | return
72 | }
73 |
74 | // 不定参数
75 | fmt.Println(funcSum(1, 2)) // 3
76 | fmt.Println(funcSum(1, 2, 3)) // 6
77 | ```
78 |
79 | 也可以使用 slice 作为实参传入,需要使用 `...` 将 slice 展开:
80 |
81 | ```go
82 | // slice 参数
83 | s := []int{1, 2, 3, 4}
84 | fmt.Println(funcSum(s...)) // 10
85 | ```
86 |
87 | 其实,使用 slice 作为形参同样可以达到相同的效果,但区别就是传参的时候,必须要构造出来一个 slice 才行,没有不定参数使用起来方便。
88 |
89 | ```go
90 | func funcSum1(args []int) (ret int) {
91 | for _, arg := range args {
92 | ret += arg
93 | }
94 | return
95 | }
96 |
97 | fmt.Println(funcSum1(s)) // 10
98 | ```
99 |
100 | ### 返回值
101 |
102 | 函数可以返回一个值,也可以返回多个值。
103 |
104 | ```go
105 | // 多返回值
106 | func swap(x, y int) (int, int) {
107 | return y, x
108 | }
109 |
110 | // 多返回值
111 | fmt.Println(swap(1, 2)) // 2 1
112 | ```
113 |
114 | 如果有不需要的返回值,使用 `_` 将其忽略:
115 |
116 | ```go
117 | x, _ := swap(1, 2)
118 | fmt.Println(x) // 2
119 | ```
120 |
121 | 支持命名返回值。使用命名返回值的话,直接使用 `return` 即可,后面不用跟返回值名。
122 |
123 | 前面不定参数的例子就是通过这种方式来写的:
124 |
125 | ```go
126 | func funcSum(args ...int) (ret int) {
127 | for _, arg := range args {
128 | ret += arg
129 | }
130 | return
131 | }
132 | ```
133 |
134 | 再来对比一下,如果不是采用命名返回值,应该怎么写:
135 |
136 | ```go
137 | func funcSum(args ...int) int {
138 | ret := 0
139 | for _, arg := range args {
140 | ret += arg
141 | }
142 | return ret
143 | }
144 | ```
145 |
146 | ### 匿名函数
147 |
148 | 匿名函数是指不需要定义函数名的一种函数实现方式。可以直接赋值给函数变量,可以当作实参,也可以作为返回值,还可以直接调用。
149 |
150 | ```go
151 | // 匿名函数
152 | sum := func(a, b int) int { return a + b }
153 | fmt.Println(sum(1, 2)) // 3
154 | ```
155 |
156 | **作为参数:**
157 |
158 | ```go
159 | // 匿名函数作为参数
160 | func funcSum2(f func(int, int) int, x, y int) int {
161 | return f(x, y)
162 | }
163 |
164 | fmt.Println(funcSum2(sum, 3, 5)) // 8
165 | ```
166 |
167 | **作为返回值:**
168 |
169 | ```go
170 | // 匿名函数作为返回值
171 | func wrap(op string) func(int, int) int {
172 | switch op {
173 | case "add":
174 | return func(a, b int) int {
175 | return a + b
176 | }
177 | case "sub":
178 | return func(a, b int) int {
179 | return a + b
180 | }
181 |
182 | default:
183 | return nil
184 | }
185 | }
186 |
187 | f := wrap("add")
188 | fmt.Println(f(2, 4)) // 6
189 | ```
190 |
191 | **直接调用:**
192 |
193 | ```go
194 | // 直接调用
195 | fmt.Println(func(a, b int) int { return a + b }(4, 5)) // 9
196 | ```
197 |
198 | ### 总结
199 |
200 | 函数在之前的文章中已经使用过了,这篇再系统全面总结一下都有哪些需要注意的点。
201 |
202 | 包括函数定义,参数,返回和匿名函数。其实还有一个闭包,通过匿名函数来实现。但我感觉闭包使用的并不是很多,就没有写,感兴趣的同学可以自己搜搜看。
203 |
204 | 函数可以把复杂的程序分成更小的模块,使程序可读性更强,复用性更高,维护性更好。在开发过程中一定要具备将特定功能抽象成函数的能力,而不是将所有代码都写在一起,代码堆成一坨。这样的代码除了不好维护,重点是时间长了自己都不想看。
205 |
206 | ---
207 |
208 | 文章中的脑图和源码都上传到了 GitHub,有需要的同学可自行下载。
209 |
210 | **地址:** https://github.com/yongxinz/gopher/tree/main/sc
211 |
212 | 关注公众号 **AlwaysBeta**,回复「**goebook**」领取 Go 编程经典书籍。
213 |
214 |
215 |
216 |
217 |
218 | **Go 专栏文章列表:**
219 |
220 | 1. [开发环境搭建以及开发工具 VS Code 配置]()
221 |
222 | 2. [变量和常量的声明与赋值]()
223 |
224 | 3. [基础数据类型:整数、浮点数、复数、布尔值和字符串]()
225 |
226 | 4. [复合数据类型:数组和切片 slice]()
227 |
228 | 5. [复合数据类型:字典 map 和 结构体 struct]()
229 | 6. [流程控制,一网打尽]()
--------------------------------------------------------------------------------
/sc/08-说说方法.md:
--------------------------------------------------------------------------------
1 | 
2 |
3 | **原文链接:** [Go 专栏|说说方法](https://mp.weixin.qq.com/s/qvFipY0pnmqxok6CVKquvg)
4 |
5 | 最近又搬家了,已经记不清这是第几次搬家了。搬到了公司附近,走路十分钟,以后加班可方便了。
6 |
7 | 这一篇来说一说方法,方法可以看作是某种特定类型的函数,是 Go 面向对象编程的第一步。用好方法,具备面向对象编程思想是关键。
8 |
9 | ### 声明
10 |
11 | 方法的声明和函数类似,他们的区别是:方法在定义的时候,会在 `func` 和方法名之间增加一个参数,这个参数就是接收者,这样我们定义的这个方法就和接收者绑定在了一起,称之为这个接收者的方法。
12 |
13 | ```go
14 | type Person struct {
15 | name string
16 | }
17 |
18 | func (p Person) String() string {
19 | return "person name is " + p.name
20 | }
21 | ```
22 |
23 | `func` 和方法名之间增加的参数 `(p Person)` 就是接收者。现在我们说,类型 `Person` 有了一个 `String` 方法。
24 |
25 | 调用方法非常简单,使用类型的变量和 `.` 操作符进行调用即可。
26 |
27 | ```go
28 | p := Person{name: "zhangsan"}
29 |
30 | // 调用方法
31 | fmt.Println(p.String()) // person name is zhangsan
32 | ```
33 |
34 | ### 值语义和引用语义
35 |
36 | Go 语言里有两种类型的接收者:值接收者和指针接收者。
37 |
38 | 使用值类型接收者定义的方法,在调用的时候,使用的其实是值接收者的一个副本,所以对该值的任何操作,不会影响原来的类型变量。
39 |
40 | ```go
41 | func main() {
42 | p := Person{name: "zhangsan"}
43 |
44 | // 调用方法
45 | fmt.Println(p.String()) // person name is zhangsan
46 |
47 | // 值接收者
48 | p.Modify()
49 | fmt.Println(p.String()) // person name is zhangsan
50 | }
51 |
52 | // 值接收者
53 | func (p Person) Modify() {
54 | p.name = "lisi"
55 | }
56 | ```
57 |
58 | 接下来再看一下使用指针接收者的效果:
59 |
60 | ```go
61 | func main() {
62 | p := Person{name: "zhangsan"}
63 |
64 | // 调用方法
65 | fmt.Println(p.String()) // person name is zhangsan
66 |
67 | // 指针接收者
68 | p.ModifyP()
69 | fmt.Println(p.String()) // person name is lisi
70 | }
71 |
72 | // 指针接收者
73 | func (p *Person) ModifyP() {
74 | p.name = "lisi"
75 | }
76 | ```
77 |
78 | 可以看到,改变了原始值,其实这一点和函数传参是一样的。
79 |
80 | 有没有发现,我们在调用指针接收者方法的时候,使用的也是一个值的变量,并不是一个指针,正常来说应该这么写:
81 |
82 | ```go
83 | (&p).ModifyP()
84 | fmt.Println(p.String())
85 | ```
86 |
87 | 同样的,如果是一个值接收者的方法,使用指针也是可以调用的:
88 |
89 | ```go
90 | (&p).Modify()
91 | fmt.Println(p.String())
92 | ```
93 |
94 | 原因是编译器帮我们自动转义了,这一点大大的方便了我们开发者。
95 |
96 | ### 方法变量和表达式
97 |
98 | 上文中已经介绍了一种调用方法,直接使用 `.` 操作符,比如:`p.String()`。
99 |
100 | 接下来再介绍两种调用方法:
101 |
102 | #### 方法变量
103 |
104 | `p.Add` 可以赋值给一个方法变量,它相当于一个函数,把方法绑定到一个接收者上。然后函数只需要提供实参而不需要提供接收者即可调用。
105 |
106 | ```go
107 | type Point struct {
108 | x, y int
109 | }
110 |
111 | func main() {
112 | // 方法变量
113 | p1 := Point{1, 2}
114 | q1 := Point{3, 4}
115 | f := p1.Add
116 | fmt.Println(f(q1)) // {4 6}
117 | }
118 |
119 | func (p Point) Add(q Point) Point {
120 | return Point{p.x + q.x, p.y + q.y}
121 | }
122 | ```
123 |
124 | #### 方法表达式
125 |
126 | 方法表达式写成 `T.f` 或者 `(*T).f`,其中 `T` 是类型,是一种函数变量。
127 |
128 | 因为调用方法必须要提供接收者,这种方法相当于把接收者替换成了函数的第一个形参,因此它可以像函数一样调用。
129 |
130 | ```go
131 | // 方法表达式
132 | f1 := Point.Add
133 | fmt.Println(f1(p1, q1)) // {4 6}
134 | ```
135 |
136 | ### 总结
137 |
138 | 本文主要学习了 Go 的方法,方法的声明和函数类似,他们的区别是:方法在定义的时候,会在 `func` 和方法名之间增加一个参数,这个参数就是接收者。
139 |
140 | 接收者有两种类型:值接收者和指针接收者。不管是使用值接收者,还是指针接收者,一定要搞清楚类型的本质:对类型进行操作的时候,是要改变当前值,还是要创建一个新值进行返回?这些就可以决定我们是采用值传递,还是指针传递。
141 |
142 | 最后就是方法的调用,可以直接使用 `.` 操作符调用,还可以使用方法变量和方法表达式。
143 |
144 | 只有基于面向对象编程思想,才能使用好方法。在后面要学习的接口中,方法还有更多的应用。
145 |
146 | ---
147 |
148 | 文章中的脑图和源码都上传到了 GitHub,有需要的同学可自行下载。
149 |
150 | **地址:** https://github.com/yongxinz/gopher/tree/main/sc
151 |
152 | 关注公众号 **AlwaysBeta**,回复「**goebook**」领取 Go 编程经典书籍。
153 |
154 |
155 |
156 |
157 |
158 | **Go 专栏文章列表:**
159 |
160 | 1. [开发环境搭建以及开发工具 VS Code 配置]()
161 |
162 | 2. [变量和常量的声明与赋值]()
163 |
164 | 3. [基础数据类型:整数、浮点数、复数、布尔值和字符串]()
165 |
166 | 4. [复合数据类型:数组和切片 slice]()
167 |
168 | 5. [复合数据类型:字典 map 和 结构体 struct]()
169 | 6. [流程控制,一网打尽]()
170 | 7. [函数那些事]()
171 | 8. [错误处理:defer,panic 和 recover]()
--------------------------------------------------------------------------------
/sc/example/00_hello.go:
--------------------------------------------------------------------------------
1 | package main // 声明 main 包
2 |
3 | import "fmt" // 导入内置 fmt 包
4 |
5 | func main() { // main函数,程序执行入口
6 | fmt.Println("Hello World!") // 在终端打印 Hello World!
7 | }
8 |
--------------------------------------------------------------------------------
/sc/example/01_constant.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "fmt"
5 | )
6 |
7 | // 全局变量
8 | const s string = "constant"
9 |
10 | func main() {
11 | fmt.Println(s)
12 |
13 | // 无类型整型常量
14 | const n = 500000000
15 |
16 | // 用编译阶段即可计算出值的表达式来赋值
17 | const d = 3e20 / n
18 | fmt.Println(d)
19 |
20 | // 类型转换
21 | fmt.Println(int64(d))
22 |
23 | const Pi float64 = 3.14159265358979323846
24 | // 无类型浮点常量
25 | const zero = 0.0
26 |
27 | // 无类型整型和字符串常量
28 | const a, b, c = 3, 4, "foo"
29 | fmt.Println(a, b, c)
30 |
31 | // 多个常量
32 | const (
33 | size int64 = 1024
34 | eof = -1 // 无类型整型常量
35 | )
36 | fmt.Println(size, eof)
37 | }
38 |
--------------------------------------------------------------------------------
/sc/example/01_iota.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "fmt"
5 | )
6 |
7 | func main() {
8 | // 从 0 值开始,逐项加 1
9 | const (
10 | a0 = iota // 0
11 | a1 = iota // 1
12 | a2 = iota // 2
13 | )
14 | fmt.Println(a0, a1, a2)
15 |
16 | // 简写,表达式相同,可以省略后面的
17 | const (
18 | b0 = iota // 0
19 | b1 // 1
20 | b2 // 2
21 | )
22 | fmt.Println(b0, b1, b2)
23 |
24 | const (
25 | b = iota // 0
26 | c float32 = iota * 10 // 10
27 | d = iota // 2
28 | )
29 | fmt.Println(b, c, d)
30 |
31 | // iota 在每个 const 开头被重置为 0
32 | const x = iota // 0
33 | fmt.Println(x)
34 |
35 | // 同上
36 | const y = iota // 0
37 | fmt.Println(y)
38 |
39 | // 枚举
40 | const (
41 | Sunday = iota // 0
42 | Monday // 1
43 | Tuesday // 2
44 | Wednesday // 3
45 | Thursday // 4
46 | Friday // 5
47 | Saturday // 6
48 | )
49 | fmt.Println(Sunday, Monday, Tuesday, Wednesday, Thursday, Friday, Saturday)
50 | }
51 |
--------------------------------------------------------------------------------
/sc/example/01_variables.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "fmt"
5 | "os"
6 | )
7 |
8 | // 全局变量
9 | var gg = "global"
10 |
11 | func main() {
12 |
13 | // 声明单个变量
14 | var a = "initial"
15 | fmt.Println(a)
16 |
17 | // 声明多个变量
18 | var b, c int = 1, 2
19 | fmt.Println(b, c)
20 |
21 | // 以组的方式声明多个变量
22 | var (
23 | b1, c1 int
24 | b2, c2 = 3, 4
25 | )
26 | fmt.Println(b1, c1)
27 | fmt.Println(b2, c2)
28 |
29 | // 声明布尔值变量
30 | var d = true
31 | fmt.Println(d)
32 |
33 | // 没有初始值,会赋默认零值
34 | var v1 int
35 | var v2 string
36 | var v3 bool
37 | var v4 [10]int // 数组
38 | var v5 []int // 数组切片
39 | var v6 struct {
40 | e int
41 | }
42 | var v7 *int // 指针
43 | var v8 map[string]int // map,key 为 string 类型,value 为 int 类型
44 | var v9 func(e int) int
45 | fmt.Println(v1, v2, v3, v4, v5, v6, v7, v8, v9)
46 |
47 | // 短变量声明方式
48 | f := "short"
49 | fmt.Println(f)
50 |
51 | // 声明赋值多个变量
52 | g, h := 5, "alwaysbeta"
53 | fmt.Println(g, h)
54 |
55 | var i int
56 | // i := 100 // 报错 no new variables on left side of :=
57 | i, j := 100, 101 // 有新值 j,不报错
58 | fmt.Println(i, j)
59 |
60 | // 指针
61 | k := 6
62 | l := &k // l 为整型指针,指向 k
63 | fmt.Println(*l) // 输出 6
64 | *l = 7
65 | fmt.Println(k) // 输出 7
66 |
67 | // 使用内置函数 new 声明
68 | var p = new(int)
69 | fmt.Println(*p) // 输出整型默认值 0
70 | *p = 8
71 | fmt.Println(*p) // 输出 8
72 |
73 | // 变量赋值
74 | var m, n int
75 | m = 9
76 | n = 10
77 | fmt.Println(m, n)
78 |
79 | // 多重赋值
80 | m, n = n, m
81 | fmt.Println(m, n)
82 |
83 | // 空标识符
84 | r := [5]int{1, 2, 3, 4, 5}
85 | for _, v := range r {
86 | // fmt.Println(i, v)
87 | // fmt.Println(v) // 定义 i 但不用会报错 i declared but not used
88 | fmt.Println(v) // 忽略索引
89 | }
90 |
91 | // 作用域
92 | fmt.Println(gg) // 输出 global
93 | gg = "local"
94 | fmt.Println(gg) // 输出 local
95 |
96 | // 条件分支下的作用域
97 | // if f, err := os.Open("./00_hello.go"); err != nil {
98 | // fmt.Println(err)
99 | // }
100 | // f.Close() // 报错 f.Close undefined (type string has no field or method Close)
101 |
102 | // 正确写法
103 | file, err := os.Open("00_hello.go")
104 | if err != nil {
105 | fmt.Println(err)
106 | }
107 | file.Close()
108 | }
109 |
--------------------------------------------------------------------------------
/sc/example/02_basic_type.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import "fmt"
4 |
5 | func main() {
6 | var a int = 10
7 | var b int32 = 20
8 |
9 | // fmt.Println(a + b) // 报错 invalid operation: a + b (mismatched types int and int32)
10 | // 需要强制类型转换
11 | fmt.Println(a + int(b)) // 输出 30
12 |
13 | // 浮点型转整型
14 | var c float32 = 10.23
15 | fmt.Println(int(c)) // 输出 10
16 |
17 | // 取模
18 | fmt.Println(5 % 3) // 输出 2
19 | fmt.Println(-5 % -3) // 输出 -2
20 |
21 | // 除法
22 | fmt.Println(5 / 3) // 输出 1
23 | fmt.Println(5.0 / 3.0) // 输出 1.6666666666666667
24 |
25 | // 比较运算
26 | var i int32
27 | var j int64
28 | i, j = 1, 2
29 |
30 | // if i == j { // 报错 invalid operation: i == j (mismatched types int32 and int64)
31 | // fmt.Println("i and j are equal.")
32 | // }
33 | if i == 1 || j == 2 {
34 | fmt.Println("equal.")
35 | }
36 |
37 | // 复数
38 | var x complex64 = 3 + 5i
39 | var y complex128 = complex(3.5, 10)
40 | // 分别打印实部和虚部
41 | fmt.Println(real(x), imag(x)) // 输出 3 5
42 | fmt.Println(real(y), imag(y)) // 输出 3.5 10
43 |
44 | // 布尔
45 | ok := true
46 | fmt.Println(ok)
47 |
48 | // 类型转换
49 | // var e bool
50 | // e = bool(1) // 报错 cannot convert 1 (type untyped int) to type bool
51 |
52 | m := 1
53 | // if m { // 报错 non-bool m (type int) used as if condition
54 | // fmt.Println("is true")
55 | // }
56 | if m == 1 {
57 | fmt.Println("m is 1")
58 | }
59 |
60 | // 字符串
61 | s1 := "hello"
62 | s2 := "world"
63 |
64 | // 原始字符串
65 | s := `row1\r\n
66 | row2`
67 | fmt.Println(s)
68 |
69 | // 字符串拼接
70 | s3 := s1 + s2
71 | fmt.Println(s3)
72 | // 取字符串长度
73 | fmt.Println(len(s3))
74 | // 取单个字符
75 | fmt.Println(s3[4])
76 | // 字符串切片
77 | fmt.Println(s3[2:4])
78 | fmt.Println(s3[:4])
79 | fmt.Println(s3[2:])
80 | fmt.Println(s3[:])
81 |
82 | // 修改报错
83 | // s3[0] = "H" // cannot assign to s3[0] (strings are immutable)
84 |
85 | s4 := "hello 世界"
86 |
87 | // 遍历字节数组
88 | for i := 0; i < len(s4); i++ {
89 | fmt.Println(i, s4[i])
90 | }
91 |
92 | // 遍历 rune 数组
93 | for i, v := range s4 {
94 | fmt.Println(i, v)
95 | }
96 | }
97 |
--------------------------------------------------------------------------------
/sc/example/03_array.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import "fmt"
4 |
5 | func main() {
6 | var a [3]int
7 | // 输出数组第一个元素
8 | fmt.Println(a[0]) // 0
9 | // 输出数组长度
10 | fmt.Println(len(a)) // 3
11 |
12 | // 数组字面量初始化
13 | var b [3]int = [3]int{1, 2, 3}
14 | var c [3]int = [3]int{1, 2}
15 | fmt.Println(b) // [1 2 3]
16 | fmt.Println(c[2]) // 0
17 |
18 | // 使用 ...
19 | d := [...]int{1, 2, 3, 4, 5}
20 | fmt.Printf("%T\n", d) // [5]int
21 |
22 | // 指定索引位置初始化
23 | e := [4]int{5, 2: 10}
24 | f := [...]int{2, 4: 6}
25 | fmt.Println(e) // [5 0 10 0]
26 | fmt.Println(f) // [2 0 0 0 6]
27 |
28 | // 二维数组
29 | var g [4][2]int
30 | h := [4][2]int{{10, 11}, {20, 21}, {30, 31}, {40, 41}}
31 | // 声明并初始化外层数组中索引为 1 和 3 的元素
32 | i := [4][2]int{1: {20, 21}, 3: {40, 41}}
33 | // 声明并初始化外层数组和内层数组的单个元素
34 | j := [...][2]int{1: {0: 20}, 3: {1: 41}}
35 | fmt.Println(g, h, i, j)
36 |
37 | // 数组比较
38 | a1 := [2]int{1, 2}
39 | a2 := [...]int{1, 2}
40 | a3 := [2]int{1, 3}
41 | // a4 := [3]int{1, 2}
42 | fmt.Println(a1 == a2, a1 == a3, a2 == a3) // true false false
43 | // fmt.Println(a1 == a4) // invalid operation: a1 == a4 (mismatched types [2]int and [3]int)
44 |
45 | // 数组遍历
46 | for i, n := range e {
47 | fmt.Println(i, n)
48 | }
49 |
50 | // 数组复制
51 | x := [2]int{10, 20}
52 | y := x
53 | fmt.Printf("x: %p, %v\n", &x, x) // x: 0xc00012e020, [10 20]
54 | fmt.Printf("y: %p, %v\n", &y, y) // y: 0xc00012e030, [10 20]
55 | test(x)
56 |
57 | // 传参
58 | modify(x)
59 | fmt.Println("main: ", x) // main: [10 20]
60 | }
61 |
62 | func test(a [2]int) {
63 | fmt.Printf("a: %p, %v\n", &a, a) // a: 0xc00012e060, [10 20]
64 | }
65 |
66 | func modify(a [2]int) {
67 | a[0] = 30
68 | fmt.Println("modify: ", a) // modify: [30 20]
69 | }
70 |
--------------------------------------------------------------------------------
/sc/example/03_slice.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import "fmt"
4 |
5 | func main() {
6 | // 基于数组创建切片
7 | var array = [...]int{1, 2, 3, 4, 5, 6, 7, 8}
8 |
9 | s1 := array[3:6]
10 | s2 := array[:5]
11 | s3 := array[4:]
12 | s4 := array[:]
13 |
14 | fmt.Printf("s1: %v\n", s1) // s1: [4 5 6]
15 | fmt.Printf("s2: %v\n", s2) // s2: [1 2 3 4 5]
16 | fmt.Printf("s3: %v\n", s3) // s3: [5 6 7 8]
17 | fmt.Printf("s4: %v\n", s4) // s4: [1 2 3 4 5 6 7 8]
18 |
19 | // 使用 make 创建切片
20 | // len: 10, cap: 10
21 | a := make([]int, 10)
22 | // len: 10, cap: 15
23 | b := make([]int, 10, 15)
24 |
25 | fmt.Printf("a: %v, len: %d, cap: %d\n", a, len(a), cap(a))
26 | fmt.Printf("b: %v, len: %d, cap: %d\n", b, len(b), cap(b))
27 |
28 | // 切片遍历
29 | for i, n := range s1 {
30 | fmt.Println(i, n)
31 | }
32 |
33 | // 比较
34 | var s []int
35 | fmt.Println(len(s) == 0, s == nil) // true true
36 | s = nil
37 | fmt.Println(len(s) == 0, s == nil) // true true
38 | s = []int(nil)
39 | fmt.Println(len(s) == 0, s == nil) // true true
40 | s = []int{}
41 | fmt.Println(len(s) == 0, s == nil) // true false
42 |
43 | // 追加
44 | s5 := append(s4, 9)
45 | fmt.Printf("s5: %v\n", s5) // s5: [1 2 3 4 5 6 7 8 9]
46 | s6 := append(s4, 10, 11)
47 | fmt.Printf("s6: %v\n", s6) // s5: [1 2 3 4 5 6 7 8 10 11]
48 |
49 | // 追加另一个切片
50 | s7 := []int{12, 13}
51 | s7 = append(s7, s6...)
52 | fmt.Printf("s7: %v\n", s7) // s7: [12 13 1 2 3 4 5 6 7 8 10 11]
53 |
54 | // 复制
55 | s8 := []int{1, 2, 3, 4, 5}
56 | s9 := []int{5, 4, 3}
57 | s10 := []int{6}
58 |
59 | copy(s8, s9)
60 | fmt.Printf("s8: %v\n", s8) // s8: [5 4 3 4 5]
61 | copy(s10, s9)
62 | fmt.Printf("s10: %v\n", s10) // s10: [5]
63 |
64 | // 传参
65 | modify(s9)
66 | fmt.Println("main: ", s9) // main: [30 4 3]
67 | }
68 |
69 | func modify(a []int) {
70 | a[0] = 30
71 | fmt.Println("modify: ", a) // modify: [30 4 3]
72 | }
73 |
--------------------------------------------------------------------------------
/sc/example/04_map.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import "fmt"
4 |
5 | func main() {
6 | // 字面量方式创建
7 | var m = map[string]int{"a": 1, "b": 2}
8 | fmt.Println(m) // map[a:1 b:2]
9 |
10 | // 使用 make 创建
11 | m1 := make(map[string]int)
12 | fmt.Println(m1)
13 |
14 | // 指定长度
15 | m2 := make(map[string]int, 10)
16 | fmt.Println(m2)
17 |
18 | // 零值是 nil
19 | var m3 map[string]int
20 | fmt.Println(m3 == nil, len(m3) == 0) // true true
21 | // nil 赋值报错
22 | // m3["a"] = 1
23 | // fmt.Println(m3) // panic: assignment to entry in nil map
24 |
25 | // 赋值
26 | m["c"] = 3
27 | m["d"] = 4
28 | fmt.Println(m) // map[a:1 b:2 c:3 d:4]
29 |
30 | // 取值
31 | fmt.Println(m["a"], m["d"]) // 1 4
32 | fmt.Println(m["k"]) // 0
33 |
34 | // 删除
35 | delete(m, "c")
36 | delete(m, "f") // key 不存在也不报错
37 | fmt.Println(m) // map[a:1 b:2 d:4]
38 |
39 | // 获取长度
40 | fmt.Println(len(m)) // 3
41 |
42 | // 判断键是否存在
43 | if value, ok := m["d"]; ok {
44 | fmt.Println(value) // 4
45 | }
46 |
47 | // 遍历
48 | for k, v := range m {
49 | fmt.Println(k, v)
50 | }
51 |
52 | // 传参
53 | modify(m)
54 | fmt.Println("main: ", m) // main: map[a:1 b:2 d:4 e:10]
55 | }
56 |
57 | func modify(a map[string]int) {
58 | a["e"] = 10
59 | fmt.Println("modify: ", a) // modify: map[a:1 b:2 d:4 e:10]
60 | }
61 |
--------------------------------------------------------------------------------
/sc/example/04_struct.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import "fmt"
4 |
5 | // 声明结构体
6 | type user struct {
7 | name string
8 | age int
9 | }
10 |
11 | type admin struct {
12 | u user
13 | isAdmin bool
14 | }
15 |
16 | type leader struct {
17 | u user
18 | isLeader bool
19 | }
20 |
21 | type admin1 struct {
22 | user
23 | isAdmin bool
24 | }
25 |
26 | func main() {
27 | // 初始化
28 | u1 := user{"zhangsan", 18}
29 | fmt.Println(u1) // {zhangsan 18}
30 |
31 | // 更好的方式
32 | // u := user{
33 | // age: 20,
34 | // }
35 | // fmt.Println(u) // { 20}
36 | u := user{
37 | name: "zhangsan",
38 | age: 18,
39 | }
40 | fmt.Println(u) // {zhangsan 18}
41 |
42 | // 访问结构体成员
43 | fmt.Println(u.name, u.age) // zhangsan 18
44 | u.name = "lisi"
45 | fmt.Println(u.name, u.age) // lisi 18
46 |
47 | // 结构体比较
48 | u2 := user{
49 | age: 18,
50 | name: "zhangsan",
51 | }
52 | fmt.Println(u1 == u) // false
53 | fmt.Println(u1 == u2) // true
54 |
55 | // 结构体嵌套
56 | a := admin{
57 | u: u,
58 | isAdmin: true,
59 | }
60 | fmt.Println(a) // {{lisi 18} true}
61 | a.u.name = "wangwu"
62 | fmt.Println(a.u.name) // wangwu
63 | fmt.Println(a.u.age) // 18
64 | fmt.Println(a.isAdmin) // true
65 |
66 | l := leader{
67 | u: u,
68 | isLeader: false,
69 | }
70 | fmt.Println(l) // {{lisi 18} false}
71 |
72 | // 匿名成员
73 | a1 := admin1{
74 | user: u,
75 | isAdmin: true,
76 | }
77 | a1.age = 20
78 | a1.isAdmin = false
79 |
80 | fmt.Println(a1) // {{lisi 20} false}
81 | fmt.Println(a1.name) // lisi
82 | fmt.Println(a1.age) // 20
83 | fmt.Println(a1.isAdmin) // false
84 | }
85 |
--------------------------------------------------------------------------------
/sc/example/05_for.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "fmt"
5 | )
6 |
7 | func main() {
8 | i := 1
9 | // 只有条件
10 | for i <= 3 {
11 | fmt.Println(i)
12 | i = i + 1
13 | }
14 |
15 | // 有变量初始化和条件
16 | for j := 7; j <= 9; j++ {
17 | fmt.Println(j)
18 | }
19 |
20 | // 死循环
21 | for {
22 | fmt.Println("loop")
23 | break
24 | }
25 |
26 | // 遍历数组
27 | a := [...]int{10, 20, 30, 40}
28 | for i := range a {
29 | fmt.Println(i)
30 | }
31 | for i, v := range a {
32 | fmt.Println(i, v)
33 | }
34 |
35 | // 遍历切片
36 | s := []string{"a", "b", "c"}
37 | for i := range s {
38 | fmt.Println(i)
39 | }
40 | for i, v := range s {
41 | fmt.Println(i, v)
42 | }
43 |
44 | // 遍历字典
45 | m := map[string]int{"a": 10, "b": 20, "c": 30}
46 | for k := range m {
47 | fmt.Println(k)
48 | }
49 | for k, v := range m {
50 | fmt.Println(k, v)
51 | }
52 | }
53 |
--------------------------------------------------------------------------------
/sc/example/05_goto.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "fmt"
5 | )
6 |
7 | func main() {
8 | // 跳出循环
9 | for i := 0; ; i++ {
10 | if i == 2 {
11 | goto L1
12 | }
13 | fmt.Println(i)
14 | }
15 | L1:
16 | fmt.Println("Done")
17 |
18 | // 跳过变量声明,不允许
19 | // goto L2
20 | // j := 1
21 | // L2:
22 |
23 | // break 跳转到标签处,然后跳过 for 循环
24 | L3:
25 | for i := 0; ; i++ {
26 | for j := 0; ; j++ {
27 | if i >= 2 {
28 | break L3
29 | }
30 | if j > 4 {
31 | break
32 | }
33 | fmt.Println(i, j)
34 | }
35 | }
36 |
37 | // continue 跳转到标签处,然后执行 i++
38 | L4:
39 | for i := 0; ; i++ {
40 | for j := 0; j < 6; j++ {
41 | if i > 4 {
42 | break L4
43 | }
44 | if i >= 2 {
45 | continue L4
46 | }
47 | if j > 4 {
48 | continue
49 | }
50 | fmt.Println(i, j)
51 | }
52 | }
53 | }
54 |
--------------------------------------------------------------------------------
/sc/example/05_if_else.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import "fmt"
4 |
5 | func main() {
6 | if 7%2 == 0 {
7 | fmt.Println("7 is even")
8 | } else {
9 | fmt.Println("7 is odd") // 7 is odd
10 | }
11 |
12 | if 8%4 == 0 {
13 | fmt.Println("8 is divisible by 4") // 8 is divisible by 4
14 | }
15 |
16 | if num := 9; num < 0 {
17 | fmt.Println(num, "is negative")
18 | } else if num < 10 {
19 | fmt.Println(num, "has 1 digit") // 9 has 1 digit
20 | } else {
21 | fmt.Println(num, "has multiple digits")
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/sc/example/05_switch.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "fmt"
5 | "time"
6 | )
7 |
8 | func main() {
9 | i := 2
10 | fmt.Print("write ", i, " as ")
11 | switch i {
12 | case 1:
13 | fmt.Println("one")
14 | case 2:
15 | fmt.Println("two") // write 2 as two
16 | fallthrough
17 | case 3:
18 | fmt.Println("three") // three
19 | case 4, 5, 6:
20 | fmt.Println("four, five, six")
21 | }
22 |
23 | switch num := 9; num {
24 | case 1:
25 | fmt.Println("one")
26 | default:
27 | fmt.Println("nine") // nine
28 | }
29 |
30 | switch time.Now().Weekday() {
31 | case time.Saturday, time.Sunday:
32 | fmt.Println("it's the weekend")
33 | default:
34 | fmt.Println("it's a weekday") // it's a weekday
35 | }
36 |
37 | t := time.Now()
38 | switch {
39 | case t.Hour() < 12:
40 | fmt.Println("it's before noon")
41 | default:
42 | fmt.Println("it's after noon") // it's after noon
43 | }
44 | }
45 |
--------------------------------------------------------------------------------
/sc/example/06_func.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "fmt"
5 | )
6 |
7 | func main() {
8 | funcA() // i am funcA
9 |
10 | // 函数签名
11 | fmt.Printf("%T\n", add) // func(int, int) int
12 | fmt.Printf("%T\n", sub) // func(int, int) int
13 |
14 | // 不定参数
15 | fmt.Println(funcSum(1, 2)) // 3
16 | fmt.Println(funcSum(1, 2, 3)) // 6
17 |
18 | // slice 参数
19 | s := []int{1, 2, 3, 4}
20 | fmt.Println(funcSum(s...)) // 10
21 | fmt.Println(funcSum1(s)) // 10
22 |
23 | // 多返回值
24 | fmt.Println(swap(1, 2)) // 2 1
25 |
26 | x, _ := swap(1, 2)
27 | fmt.Println(x) // 2
28 |
29 | // 匿名函数
30 | sum := func(a, b int) int { return a + b }
31 | fmt.Println(sum(1, 2)) // 3
32 |
33 | // 作为参数
34 | fmt.Println(funcSum2(sum, 3, 5)) // 8
35 |
36 | // 作为返回值
37 | f := wrap("add")
38 | fmt.Println(f(2, 4)) // 6
39 |
40 | // 直接调用
41 | fmt.Println(func(a, b int) int { return a + b }(4, 5)) // 9
42 | }
43 |
44 | func funcA() {
45 | fmt.Println("i am funcA")
46 | }
47 |
48 | func add(x int, y int) int {
49 | return x + y
50 | }
51 |
52 | func sub(x int, y int) (z int) {
53 | z = x - y
54 | return
55 | }
56 |
57 | // 简写形式
58 | func add1(x, y int) int {
59 | return x + y
60 | }
61 |
62 | func sub1(x, y int) (z int) {
63 | z = x - y
64 | return
65 | }
66 |
67 | // 不定参数
68 | func funcSum(args ...int) (ret int) {
69 | for _, arg := range args {
70 | ret += arg
71 | }
72 | return
73 | }
74 |
75 | // slice 参数
76 | func funcSum1(args []int) (ret int) {
77 | for _, arg := range args {
78 | ret += arg
79 | }
80 | return
81 | }
82 |
83 | // 多返回值
84 | func swap(x, y int) (int, int) {
85 | return y, x
86 | }
87 |
88 | // 匿名函数作为参数
89 | func funcSum2(f func(int, int) int, x, y int) int {
90 | return f(x, y)
91 | }
92 |
93 | // 匿名函数作为返回值
94 | func wrap(op string) func(int, int) int {
95 | switch op {
96 | case "add":
97 | return func(a, b int) int {
98 | return a + b
99 | }
100 | case "sub":
101 | return func(a, b int) int {
102 | return a + b
103 | }
104 |
105 | default:
106 | return nil
107 | }
108 | }
109 |
--------------------------------------------------------------------------------
/sc/example/07_defer.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "fmt"
5 | )
6 |
7 | func main() {
8 | defer func() {
9 | fmt.Println("first")
10 | }()
11 |
12 | defer func() {
13 | fmt.Println("second")
14 | }()
15 |
16 | fmt.Println("done")
17 |
18 | fmt.Println(triple(4)) // 12
19 | }
20 |
21 | func double(x int) (result int) {
22 | defer func() {
23 | fmt.Printf("double(%d) = %d\n", x, result)
24 | }()
25 |
26 | return x + x
27 | }
28 |
29 | func triple(x int) (result int) {
30 | defer func() {
31 | result += x
32 | }()
33 |
34 | return double(x)
35 | }
36 |
--------------------------------------------------------------------------------
/sc/example/07_error.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "fmt"
5 | )
6 |
7 | func main() {
8 | n, err := echo(10)
9 | if err != nil {
10 | fmt.Println("error: " + err.Error())
11 | } else {
12 | fmt.Println(n)
13 | }
14 | }
15 |
16 | func echo(param int) (int, error) {
17 | return param, nil
18 | }
19 |
--------------------------------------------------------------------------------
/sc/example/07_panic_recover.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "fmt"
5 | )
6 |
7 | func main() {
8 | G()
9 | }
10 |
11 | func G() {
12 | defer func() {
13 | fmt.Println("c")
14 | }()
15 | F()
16 | fmt.Println("继续执行")
17 | }
18 |
19 | func F() {
20 | defer func() {
21 | if err := recover(); err != nil {
22 | fmt.Println("捕获异常:", err)
23 | }
24 | fmt.Println("b")
25 | }()
26 | panic("a")
27 | }
28 |
--------------------------------------------------------------------------------
/sc/example/08_method.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "fmt"
5 | )
6 |
7 | type Person struct {
8 | name string
9 | }
10 |
11 | type Point struct {
12 | x, y int
13 | }
14 |
15 | func main() {
16 | p := Person{name: "zhangsan"}
17 |
18 | // 调用方法
19 | fmt.Println(p.String()) // person name is zhangsan
20 |
21 | // 值接收者
22 | p.Modify()
23 | fmt.Println(p.String()) // person name is zhangsan
24 |
25 | // 指针接收者
26 | p.ModifyP()
27 | fmt.Println(p.String()) // person name is lisi
28 | // 等价于
29 | (&p).ModifyP()
30 | fmt.Println(p.String())
31 |
32 | (&p).Modify()
33 | fmt.Println(p.String())
34 |
35 | // 方法变量
36 | p1 := Point{1, 2}
37 | q1 := Point{3, 4}
38 | f := p1.Add
39 | fmt.Println(f(q1)) // {4 6}
40 |
41 | // 方法表达式
42 | f1 := Point.Add
43 | fmt.Println(f1(p1, q1)) // {4 6}
44 | }
45 |
46 | func (p Person) String() string {
47 | return "person name is " + p.name
48 | }
49 |
50 | // 值接收者
51 | func (p Person) Modify() {
52 | p.name = "lisi"
53 | }
54 |
55 | // 指针接收者
56 | func (p *Person) ModifyP() {
57 | p.name = "lisi"
58 | }
59 |
60 | func (p Point) Add(q Point) Point {
61 | return Point{p.x + q.x, p.y + q.y}
62 | }
63 |
--------------------------------------------------------------------------------
/sc/example/09_interface.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import "fmt"
4 |
5 | // 定义接口,包含 Eat 方法
6 | type Duck interface {
7 | Eat()
8 | }
9 |
10 | type Duck1 interface {
11 | Eat()
12 | Walk()
13 | }
14 |
15 | // 定义 Cat 结构体,并实现 Eat 方法
16 | type Cat struct{}
17 |
18 | func (c *Cat) Eat() {
19 | fmt.Println("cat eat")
20 | }
21 |
22 | // 定义 Dog 结构体,并实现 Eat 方法
23 | type Dog struct{}
24 |
25 | func (d *Dog) Eat() {
26 | fmt.Println("dog eat")
27 | }
28 |
29 | func (d *Dog) Walk() {
30 | fmt.Println("dog walk")
31 | }
32 |
33 | func main() {
34 | var c Duck = &Cat{}
35 | c.Eat()
36 |
37 | var d Duck = &Dog{}
38 | d.Eat()
39 |
40 | s := []Duck{
41 | &Cat{},
42 | &Dog{},
43 | }
44 | for _, n := range s {
45 | n.Eat()
46 | }
47 |
48 | var c1 Duck1 = &Dog{}
49 | var c2 Duck = c1
50 | c2.Eat()
51 |
52 | // 类型断言
53 | var n interface{} = 55
54 | assert(n) // 55
55 | var n1 interface{} = "hello"
56 | // assert(n1) // panic: interface conversion: interface {} is string, not int
57 | assertFlag(n1)
58 |
59 | assertInterface(c) // &{}
60 |
61 | // 类型查询
62 | searchType(50) // Int: 50
63 | searchType("zhangsan") // String: zhangsan
64 | searchType(c1) // dog eat
65 | searchType(50.1) // Unknown type
66 |
67 | // 空接口
68 | s1 := "Hello World"
69 | i := 50
70 | strt := struct {
71 | name string
72 | }{
73 | name: "AlwaysBeta",
74 | }
75 | test(s1)
76 | test(i)
77 | test(strt)
78 | }
79 |
80 | func assert(i interface{}) {
81 | s := i.(int)
82 | fmt.Println(s)
83 | }
84 |
85 | func assertInterface(i interface{}) {
86 | s := i.(Duck)
87 | fmt.Println(s)
88 | }
89 |
90 | func assertFlag(i interface{}) {
91 | if s, ok := i.(int); ok {
92 | fmt.Println(s)
93 | }
94 | }
95 |
96 | func searchType(i interface{}) {
97 | switch v := i.(type) {
98 | case string:
99 | fmt.Printf("String: %s\n", i.(string))
100 | case int:
101 | fmt.Printf("Int: %d\n", i.(int))
102 | case Duck:
103 | v.Eat()
104 | default:
105 | fmt.Printf("Unknown type\n")
106 | }
107 | }
108 |
109 | func test(i interface{}) {
110 | fmt.Printf("Type = %T, value = %v\n", i, i)
111 | }
112 |
--------------------------------------------------------------------------------
/sc/example/10_channel.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import "fmt"
4 |
5 | func Add(x, y int, ch chan int) {
6 | z := x + y
7 | ch <- z
8 | }
9 |
10 | func counter(out chan<- int) {
11 | for x := 0; x < 10; x++ {
12 | out <- x
13 | }
14 | close(out)
15 | }
16 |
17 | func squarer(out chan<- int, in <-chan int) {
18 | for v := range in {
19 | out <- v * v
20 | }
21 | close(out)
22 | }
23 |
24 | func printer(in <-chan int) {
25 | for v := range in {
26 | fmt.Println(v)
27 | }
28 | }
29 |
30 | func main() {
31 | n := make(chan int)
32 | s := make(chan int)
33 |
34 | go counter(n)
35 | go squarer(s, n)
36 | printer(s)
37 |
38 | ch := make(chan int)
39 | for i := 0; i < 10; i++ {
40 | go Add(i, i, ch)
41 | }
42 |
43 | for i := 0; i < 10; i++ {
44 | fmt.Println(<-ch)
45 | }
46 | }
47 |
--------------------------------------------------------------------------------
/sc/example/10_goroutine.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "fmt"
5 | "time"
6 | )
7 |
8 | func main() {
9 | go spinner(100 * time.Millisecond)
10 | const n = 45
11 | fibN := fib(n)
12 | fmt.Printf("\rFibonacci(%d) = %d\n", n, fibN) // Fibonacci(45) = 1134903170
13 | }
14 |
15 | func spinner(delay time.Duration) {
16 | for {
17 | for _, r := range `-\|/` {
18 | fmt.Printf("\r%c", r)
19 | time.Sleep(delay)
20 | }
21 | }
22 | }
23 |
24 | func fib(x int) int {
25 | if x < 2 {
26 | return x
27 | }
28 | return fib(x-1) + fib(x-2)
29 | }
30 |
31 | func Add(x, y int) {
32 | z := x + y
33 | fmt.Println(z)
34 | }
35 |
--------------------------------------------------------------------------------
/sc/example/10_sync_mutex.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "fmt"
5 | "sync"
6 | "time"
7 | )
8 |
9 | func main() {
10 | var mutex sync.Mutex
11 | wg := sync.WaitGroup{}
12 |
13 | // 主 goroutine 先获取锁
14 | fmt.Println("Locking (G0)")
15 | mutex.Lock()
16 | fmt.Println("locked (G0)")
17 |
18 | wg.Add(3)
19 | for i := 1; i < 4; i++ {
20 | go func(i int) {
21 | // 由于主 goroutine 先获取锁,程序开始 5 秒会阻塞在这里
22 | fmt.Printf("Locking (G%d)\n", i)
23 | mutex.Lock()
24 | fmt.Printf("locked (G%d)\n", i)
25 |
26 | time.Sleep(time.Second * 2)
27 | mutex.Unlock()
28 | fmt.Printf("unlocked (G%d)\n", i)
29 |
30 | wg.Done()
31 | }(i)
32 | }
33 |
34 | // 主 goroutine 5 秒后释放锁
35 | time.Sleep(time.Second * 5)
36 | fmt.Println("ready unlock (G0)")
37 | mutex.Unlock()
38 | fmt.Println("unlocked (G0)")
39 |
40 | wg.Wait()
41 | }
42 |
--------------------------------------------------------------------------------
/sc/example/10_sync_rwmutex.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "fmt"
5 | "sync"
6 | "time"
7 | )
8 |
9 | func main() {
10 | var rwMutex sync.RWMutex
11 | wg := sync.WaitGroup{}
12 |
13 | Data := 0
14 | wg.Add(20)
15 | for i := 0; i < 10; i++ {
16 | go func(t int) {
17 | // 第一次运行后,写解锁。
18 | // 循环到第二次时,读锁定后,goroutine 没有阻塞,同时读成功。
19 | fmt.Println("Locking")
20 | rwMutex.RLock()
21 | defer rwMutex.RUnlock()
22 | fmt.Printf("Read data: %v\n", Data)
23 | wg.Done()
24 | time.Sleep(2 * time.Second)
25 | }(i)
26 | go func(t int) {
27 | // 写锁定下是需要解锁后才能写的
28 | rwMutex.Lock()
29 | defer rwMutex.Unlock()
30 | Data += t
31 | fmt.Printf("Write Data: %v %d \n", Data, t)
32 | wg.Done()
33 | time.Sleep(2 * time.Second)
34 | }(i)
35 | }
36 |
37 | wg.Wait()
38 | }
39 |
--------------------------------------------------------------------------------
/sc/example/11_defer.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "fmt"
5 | "time"
6 | )
7 |
8 | func a() (r int) {
9 | defer func() {
10 | r++
11 | }()
12 | return 0
13 | }
14 |
15 | func b() (r int) {
16 | t := 5
17 | defer func() {
18 | t = t + 5
19 | }()
20 | return t
21 | }
22 |
23 | func c() (r int) {
24 | defer func(r int) {
25 | r = r + 5
26 | }(r)
27 | return 1
28 | }
29 |
30 | func aa() (r int) {
31 | r = 0
32 | // 在 return 之前,执行 defer 函数
33 | func() {
34 | r++
35 | }()
36 | return
37 | }
38 |
39 | func bb() (r int) {
40 | t := 5
41 | // 赋值
42 | r = t
43 | // 在 return 之前,执行 defer 函数
44 | // defer 函数没有对返回值 r 进行修改,只是修改了变量 t
45 | func() {
46 | t = t + 5
47 | }()
48 | return
49 | }
50 |
51 | func cc() (r int) {
52 | // 赋值
53 | r = 1
54 | // 这里修改的 r 是函数形参的值
55 | // 值拷贝,不影响实参值
56 | func(r int) {
57 | r = r + 5
58 | }(r)
59 | return
60 | }
61 |
62 | func d() int {
63 | r := 0
64 | defer func() {
65 | r++
66 | }()
67 | return r
68 | }
69 |
70 | func e() int {
71 | r := 0
72 | defer func(i int) {
73 | i++
74 | }(r)
75 | return 0
76 | }
77 |
78 | func G() {
79 | defer func() {
80 | // goroutine 外进行 recover
81 | if err := recover(); err != nil {
82 | fmt.Println("捕获异常:", err)
83 | }
84 | fmt.Println("c")
85 | }()
86 | // 创建 goroutine 调用 F 函数
87 | go F()
88 | time.Sleep(time.Second)
89 | }
90 |
91 | func F() {
92 | defer func() {
93 | fmt.Println("b")
94 | }()
95 | // goroutine 内部抛出panic
96 | panic("a")
97 | }
98 |
99 | func main() {
100 | fmt.Println("a = ", a())
101 | fmt.Println("b = ", b())
102 | fmt.Println("c = ", c())
103 | fmt.Println("aa = ", aa())
104 | fmt.Println("bb = ", bb())
105 | fmt.Println("cc = ", cc())
106 | fmt.Println("d = ", d())
107 | fmt.Println("e = ", e())
108 |
109 | // defer func() {
110 | // fmt.Println("b")
111 | // }()
112 | // panic("a")
113 |
114 | // G()
115 |
116 | defer func() {
117 | if err := recover(); err != nil {
118 | fmt.Println("捕获异常:", err)
119 | }
120 | }()
121 | panic("a")
122 | }
123 |
--------------------------------------------------------------------------------
/sc/pic/.DS_Store:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/yongxinz/gopher/030046cf44fb5b2a2c90112b3d179ae9ba9df000/sc/pic/.DS_Store
--------------------------------------------------------------------------------
/sc/pic/00_GO安装与配置.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/yongxinz/gopher/030046cf44fb5b2a2c90112b3d179ae9ba9df000/sc/pic/00_GO安装与配置.png
--------------------------------------------------------------------------------
/sc/pic/00_go_dl.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/yongxinz/gopher/030046cf44fb5b2a2c90112b3d179ae9ba9df000/sc/pic/00_go_dl.png
--------------------------------------------------------------------------------
/sc/pic/00_mac_install_go.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/yongxinz/gopher/030046cf44fb5b2a2c90112b3d179ae9ba9df000/sc/pic/00_mac_install_go.png
--------------------------------------------------------------------------------
/sc/pic/00_vs_code_install_go.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/yongxinz/gopher/030046cf44fb5b2a2c90112b3d179ae9ba9df000/sc/pic/00_vs_code_install_go.png
--------------------------------------------------------------------------------
/sc/pic/01_变量和常量.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/yongxinz/gopher/030046cf44fb5b2a2c90112b3d179ae9ba9df000/sc/pic/01_变量和常量.png
--------------------------------------------------------------------------------
/sc/pic/02_基础数据类型.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/yongxinz/gopher/030046cf44fb5b2a2c90112b3d179ae9ba9df000/sc/pic/02_基础数据类型.png
--------------------------------------------------------------------------------
/sc/pic/03_slice_00.drawio:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
--------------------------------------------------------------------------------
/sc/pic/03_slice_00.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/yongxinz/gopher/030046cf44fb5b2a2c90112b3d179ae9ba9df000/sc/pic/03_slice_00.png
--------------------------------------------------------------------------------
/sc/pic/03_slice_01.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/yongxinz/gopher/030046cf44fb5b2a2c90112b3d179ae9ba9df000/sc/pic/03_slice_01.png
--------------------------------------------------------------------------------
/sc/pic/03_数组和切片.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/yongxinz/gopher/030046cf44fb5b2a2c90112b3d179ae9ba9df000/sc/pic/03_数组和切片.png
--------------------------------------------------------------------------------
/sc/pic/04_字典和结构体.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/yongxinz/gopher/030046cf44fb5b2a2c90112b3d179ae9ba9df000/sc/pic/04_字典和结构体.png
--------------------------------------------------------------------------------
/sc/pic/05_流程控制.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/yongxinz/gopher/030046cf44fb5b2a2c90112b3d179ae9ba9df000/sc/pic/05_流程控制.png
--------------------------------------------------------------------------------
/sc/pic/06_函数.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/yongxinz/gopher/030046cf44fb5b2a2c90112b3d179ae9ba9df000/sc/pic/06_函数.png
--------------------------------------------------------------------------------
/sc/pic/07_错误处理.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/yongxinz/gopher/030046cf44fb5b2a2c90112b3d179ae9ba9df000/sc/pic/07_错误处理.png
--------------------------------------------------------------------------------
/sc/pic/08_方法.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/yongxinz/gopher/030046cf44fb5b2a2c90112b3d179ae9ba9df000/sc/pic/08_方法.png
--------------------------------------------------------------------------------
/sc/pic/09_接口interface.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/yongxinz/gopher/030046cf44fb5b2a2c90112b3d179ae9ba9df000/sc/pic/09_接口interface.png
--------------------------------------------------------------------------------
/sc/pic/10_并发编程.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/yongxinz/gopher/030046cf44fb5b2a2c90112b3d179ae9ba9df000/sc/pic/10_并发编程.png
--------------------------------------------------------------------------------
/weixinpay.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/yongxinz/gopher/030046cf44fb5b2a2c90112b3d179ae9ba9df000/weixinpay.jpg
--------------------------------------------------------------------------------
/whygo/为什么 Go for-range 的 value 值地址每次都一样?.md:
--------------------------------------------------------------------------------
1 | **原文链接:** [为什么 Go for-range 的 value 值地址每次都一样?](https://mp.weixin.qq.com/s/OoJ42UVYe72492mRUGtdvA)
2 |
3 | 循环语句是一种常用的控制结构,在 Go 语言中,除了 `for` 关键字以外,还有一个 `range` 关键字,可以使用 `for-range` 循环迭代数组、切片、字符串、map 和 channel 这些数据类型。
4 |
5 | 但是在使用 `for-range` 循环迭代数组和切片的时候,是很容易出错的,甚至很多老司机一不小心都会在这里翻车。
6 |
7 | 具体是怎么翻的呢?我们接着看。
8 |
9 | ## 现象
10 |
11 | 先来看两段很有意思的代码:
12 |
13 | ### 无限循环
14 |
15 | 如果我们在遍历数组的同时向数组中添加元素,能否得到一个永远都不会停止的循环呢?
16 |
17 | 比如下面这段代码:
18 |
19 | ```go
20 | func main() {
21 | arr := []int{1, 2, 3}
22 | for _, v := range arr {
23 | arr = append(arr, v)
24 | }
25 | fmt.Println(arr)
26 | }
27 | ```
28 |
29 | 程序输出:
30 |
31 | ```go
32 | $ go run main.go
33 | 1 2 3 1 2 3
34 | ```
35 |
36 | 上述代码的输出意味着循环只遍历了原始切片中的三个元素,我们在遍历切片时追加的元素并没有增加循环的执行次数,所以循环最终还是停了下来。
37 |
38 | ### 相同地址
39 |
40 | 第二个例子是使用 Go 语言经常会犯的一个错误。
41 |
42 | 当我们在遍历一个数组时,如果获取 `range` 返回变量的地址并保存到另一个数组或者哈希时,会遇到令人困惑的现象:
43 |
44 | ```go
45 | func main() {
46 | arr := []int{1, 2, 3}
47 | newArr := []*int{}
48 | for _, v := range arr {
49 | newArr = append(newArr, &v)
50 | }
51 | for _, v := range newArr {
52 | fmt.Println(*v)
53 | }
54 | }
55 | ```
56 |
57 | 程序输出:
58 |
59 | ```go
60 | $ go run main.go
61 | 3 3 3
62 | ```
63 |
64 | 上述代码并没有输出 `1 2 3`,而是输出 `3 3 3`。
65 |
66 | 正确的做法应该是使用 `&arr[i]` 替代 `&v`,像这种编程中的细节是很容易出错的。
67 |
68 | ## 原因
69 |
70 | 具体原因也并不复杂,一句话就能解释。
71 |
72 | 对于数组、切片或字符串,每次迭代,`for-range` 语句都会将原始值的副本传递给迭代变量,而非原始值本身。
73 |
74 | 口说无凭,具体是不是这样,还得靠源码说话。
75 |
76 | Go 编译器会将 `for-range` 语句转换成类似 C 语言的[**三段式循环**](https://github.com/golang/gofrontend/blob/e387439bfd24d5e142874b8e68e7039f74c744d7/go/statements.cc#L5384)结构,就像这样:
77 |
78 | ```go
79 | // Arrange to do a loop appropriate for the type. We will produce
80 | // for INIT ; COND ; POST {
81 | // ITER_INIT
82 | // INDEX = INDEX_TEMP
83 | // VALUE = VALUE_TEMP // If there is a value
84 | // original statements
85 | // }
86 | ```
87 |
88 | 迭代[**数组**](https://github.com/golang/gofrontend/blob/e387439bfd24d5e142874b8e68e7039f74c744d7/go/statements.cc#L5501)时,是这样:
89 |
90 | ```go
91 | // The loop we generate:
92 | // len_temp := len(range)
93 | // range_temp := range
94 | // for index_temp = 0; index_temp < len_temp; index_temp++ {
95 | // value_temp = range_temp[index_temp]
96 | // index = index_temp
97 | // value = value_temp
98 | // original body
99 | // }
100 | ```
101 |
102 | [**切片**](https://github.com/golang/gofrontend/blob/e387439bfd24d5e142874b8e68e7039f74c744d7/go/statements.cc#L5593):
103 |
104 | ```go
105 | // for_temp := range
106 | // len_temp := len(for_temp)
107 | // for index_temp = 0; index_temp < len_temp; index_temp++ {
108 | // value_temp = for_temp[index_temp]
109 | // index = index_temp
110 | // value = value_temp
111 | // original body
112 | // }
113 | ```
114 |
115 | 从上面的代码片段,可以总结两点:
116 |
117 | 1. 在循环开始前,会将数组或切片赋值给一个新变量,在赋值过程中就发生了拷贝,迭代的实际上是副本,这也就解释了现象 1。
118 | 2. 在循环过程中,会将迭代元素赋值给一个临时变量,这又发生了拷贝。如果取地址的话,每次都是一样的,都是临时变量的地址。
119 |
120 | 以上就是本文的全部内容,如果觉得还不错的话欢迎**点赞**,**转发**和**关注**,感谢支持。
121 |
122 | ***
123 |
124 | **参考文章:**
125 |
126 | -
127 | -
128 |
129 | **推荐阅读:**
130 |
131 | - [为什么 Go 不支持 []T 转换为 []interface](https://mp.weixin.qq.com/s/cwDEgnicK4jkuNpzulU2bw)
132 | - [为什么 Go 语言 struct 要使用 tags](https://mp.weixin.qq.com/s/L7-TJ-CzYfuVrIBWP7Ebaw)
--------------------------------------------------------------------------------
/whygo/为什么 Go 语言 struct 要使用 tags.md:
--------------------------------------------------------------------------------
1 | **原文链接:**[为什么 Go 语言 struct 要使用 tags](https://mp.weixin.qq.com/s/L7-TJ-CzYfuVrIBWP7Ebaw)
2 |
3 | 在 Go 语言中,struct 是一种常见的数据类型,它可以用来表示复杂的数据结构。在 struct 中,我们可以定义多个字段,每个字段可以有不同的类型和名称。
4 |
5 | 除了这些基本信息之外,Go 还提供了 struct tags,它可以用来指定 struct 中每个字段的元信息。
6 |
7 | 在本文中,我们将探讨为什么 Go 语言中需要使用 struct tags,以及 struct tags 的使用场景和优势。
8 |
9 | ## struct tags 的使用
10 |
11 | struct tags 使用还是很广泛的,特别是在 json 序列化,或者是数据库 ORM 映射方面。
12 |
13 | 在定义上,它以 `key:value` 的形式出现,跟在 struct 字段后面,除此之外,还有以下几点需要注意:
14 |
15 | ### 使用反引号
16 |
17 | 在声明 struct tag 时,使用反引号 `` ` `` 包围 tag 的值,可以防止转义字符的影响,使 tag 更容易读取和理解。例如:
18 |
19 | ```go
20 | type User struct {
21 | ID int `json:"id" db:"id"`
22 | Name string `json:"name" db:"name"`
23 | Email string `json:"email" db:"email"`
24 | }
25 | ```
26 |
27 | ### 避免使用空格
28 |
29 | 在 struct tag 中,应该避免使用空格,特别是在 tag 名称和 tag 值之间。使用空格可能会导致编码或解码错误,并使代码更难以维护。例如:
30 |
31 | ```go
32 | // 不规范的写法
33 | type User struct {
34 | ID int `json: "id" db: "id"`
35 | Name string `json: "name" db: "name"`
36 | Email string `json: "email" db: "email"`
37 | }
38 |
39 | // 规范的写法
40 | type User struct {
41 | ID int `json:"id" db:"id"`
42 | Name string `json:"name" db:"name"`
43 | Email string `json:"email" db:"email"`
44 | }
45 | ```
46 |
47 | ### 避免重复
48 |
49 | 在 struct 中,应该避免重复使用同一个 tag 名称。如果重复使用同一个 tag 名称,编译器可能会无法识别 tag,从而导致编码或解码错误。例如:
50 |
51 | ```go
52 | // 不规范的写法
53 | type User struct {
54 | ID int `json:"id" db:"id"`
55 | Name string `json:"name" db:"name"`
56 | Email string `json:"email" db:"name"`
57 | }
58 |
59 | // 规范的写法
60 | type User struct {
61 | ID int `json:"id" db:"id"`
62 | Name string `json:"name" db:"name"`
63 | Email string `json:"email" db:"email"`
64 | }
65 | ```
66 |
67 | ### 使用标准化的 tag 名称
68 |
69 | 为了使 struct tag 更加标准化和易于维护,应该使用一些标准化的 tag 名称。
70 |
71 | 例如,对于序列化和反序列化,可以使用 `json`、`xml`、`yaml` 等;对于数据库操作,可以使用 `db`。
72 |
73 | ```go
74 | type User struct {
75 | ID int `json:"id" db:"id"`
76 | Name string `json:"name" db:"name"`
77 | Password string `json:"-" db:"password"` // 忽略该字段
78 | Email string `json:"email" db:"email"`
79 | }
80 | ```
81 |
82 | 其中,`Password` 字段后面的 `-` 表示忽略该字段,也就是说该字段不会被序列化或反序列化。
83 |
84 | ### 多个 tag 值
85 |
86 | 如果一个字段需要指定多个 tag 值,可以使用 `,` 将多个 tag 值分隔开。例如:
87 |
88 | ```go
89 | type User struct {
90 | ID int `json:"id" db:"id"`
91 | Name string `json:"name" db:"name"`
92 | Email string `json:"email,omitempty" db:"email,omitempty"`
93 | }
94 | ```
95 |
96 | 其中 `omitempty` 表示如果该字段值为空,则不序列化该字段。
97 |
98 | ## struct tags 的原理
99 |
100 | Go 的反射库提供了一些方法,可以让我们在程序运行时获取和解析结构体标签。
101 |
102 | 介绍这些方法之前,先来看看 `reflect.StructField` ,它是描述结构体字段的数据类型。定义如下:
103 |
104 | ```go
105 | type StructField struct {
106 | Name string // 字段名
107 | Type Type // 字段类型
108 | Tag StructTag // 字段标签
109 | }
110 | ```
111 |
112 | 结构体中还有一些其他字段,被我省略了,只保留了和本文相关的。
113 |
114 | 在结构体的反射中,我们经常使用 `reflect.TypeOf` 获取类型信息,然后使用 `Type.Field` 或 `Type.FieldByName()` 获取结构体字段的 `reflect.StructField`,然后根据 `StructField` 中的信息做进一步处理。
115 |
116 | 例如,可以通过 `StructField.Tag.Get` 方法获取结构体字段的标签值。
117 |
118 | 下面看一段代码:
119 |
120 | ```go
121 | package main
122 |
123 | import (
124 | "fmt"
125 | "reflect"
126 | )
127 |
128 | type User struct {
129 | Name string `json:"name"`
130 | Age int `json:"age"`
131 | }
132 |
133 | type Manager struct {
134 | Title string `json:"title"`
135 | User
136 | }
137 |
138 | func main() {
139 | m := Manager{Title: "Manager", User: User{Name: "Alice", Age: 25}}
140 |
141 | mt := reflect.TypeOf(m)
142 |
143 | // 获取 User 字段的 reflect.StructField
144 | userField, _ := mt.FieldByName("User")
145 | fmt.Println("Field 'User' exists:", userField.Name, userField.Type)
146 |
147 | // 获取 User.Name 字段的 reflect.StructField
148 | nameField, _ := userField.Type.FieldByName("Name")
149 | tag := nameField.Tag.Get("json")
150 | fmt.Println("User.Name tag:", tag)
151 | }
152 | ```
153 |
154 | 运行以上代码,输出结果如下:
155 |
156 | ```go
157 | Field 'User' exists: User {string int}
158 | User.Name tag: "name"
159 | ```
160 |
161 | ## struct tags 的优势
162 |
163 | 使用 struct tag 的主要优势之一是可以在**运行时通过反射来访问和操作 struct 中的字段**。
164 |
165 | 比如在 Go Web 开发中,常常需要将 HTTP 请求中的参数绑定到一个 struct 中。这时,我们可以使用 struct tag 指定每个字段对应的参数名称、验证规则等信息。在接收到 HTTP 请求时,就可以使用反射机制读取这些信息,并根据信息来验证参数是否合法。
166 |
167 | 另外,在将 struct 序列化为 JSON 或者其他格式时,我们也可以使用 struct tag 来指定每个字段在序列化时的名称和规则。
168 |
169 | 此外,使用 struct tag 还可以提高代码的**可读性**和**可维护性**。在一个大型的项目中,struct 中的字段通常会包含很多不同的元信息,比如数据库中的表名、字段名、索引、验证规则等等。
170 |
171 | 如果没有 struct tag,我们可能需要将这些元信息放在注释中或者在代码中进行硬编码。这样会让代码变得难以维护和修改。而使用 struct tag 可以将这些元信息与 struct 字段紧密关联起来,使代码更加清晰和易于维护。
172 |
173 | ## 常用的 struct tags
174 |
175 | 在 Go 的官方 wiki 中,有一个常用的 struct tags 的库的列表,我复制在下面了,感兴趣的同学可以看看源码,再继续深入学习。
176 |
177 | Tag | Documentation
178 | ----------|---------------
179 | xml | https://pkg.go.dev/encoding/xml
180 | json | https://pkg.go.dev/encoding/json
181 | asn1 | https://pkg.go.dev/encoding/asn1
182 | reform | https://pkg.go.dev/gopkg.in/reform.v1
183 | dynamodb | https://docs.aws.amazon.com/sdk-for-go/api/service/dynamodb/dynamodbattribute/#Marshal
184 | bigquery | https://pkg.go.dev/cloud.google.com/go/bigquery
185 | datastore | https://pkg.go.dev/cloud.google.com/go/datastore
186 | spanner | https://pkg.go.dev/cloud.google.com/go/spanner
187 | bson | https://pkg.go.dev/labix.org/v2/mgo/bson, https://pkg.go.dev/go.mongodb.org/mongo-driver/bson/bsoncodec
188 | gorm | https://pkg.go.dev/github.com/jinzhu/gorm
189 | yaml | https://pkg.go.dev/gopkg.in/yaml.v2
190 | toml | https://pkg.go.dev/github.com/pelletier/go-toml
191 | validate | https://github.com/go-playground/validator
192 | mapstructure | https://pkg.go.dev/github.com/mitchellh/mapstructure
193 | parser | https://pkg.go.dev/github.com/alecthomas/participle
194 | protobuf | https://github.com/golang/protobuf
195 | db | https://github.com/jmoiron/sqlx
196 | url | https://github.com/google/go-querystring
197 | feature | https://github.com/nikolaydubina/go-featureprocessing
198 |
199 | 以上就是本文的全部内容,如果觉得还不错的话欢迎**点赞**,**转发**和**关注**,感谢支持。
200 |
201 | ***
202 |
203 | **参考文章:**
204 |
205 | - https://github.com/golang/go/wiki/Well-known-struct-tags
206 |
207 | **推荐阅读:**
208 |
209 | - [为什么 Go 不支持 []T 转换为 []interface](https://mp.weixin.qq.com/s/cwDEgnicK4jkuNpzulU2bw)
--------------------------------------------------------------------------------
/whygo/为什么说 Go 语言字符串是不可变的?.md:
--------------------------------------------------------------------------------
1 | **原文链接:** [为什么说 Go 语言字符串是不可变的?](https://mp.weixin.qq.com/s/AOb6AjKwyTwLeAUou0AU-Q)
2 |
3 | 最近有读者留言说,平时在写代码的过程中,是会对字符串进行修改的,但网上都说 Go 语言字符串是不可变的,这是为什么呢?
4 |
5 | 这个问题本身并不困难,但对于新手来说确实容易产生困惑,今天就来回答一下。
6 |
7 | 首先来看看它的底层结构:
8 |
9 | ```go
10 | type stringStruct struct {
11 | str unsafe.Pointer
12 | len int
13 | }
14 | ```
15 |
16 | 和切片的结构很像,只不过少了一个表示容量的 `cap` 字段。
17 |
18 | * `str`:指向一个 `[]byte` 类型的指针
19 | * `len`:字符串的长度
20 |
21 | 所以,当我们定义一个字符串:
22 |
23 | ```go
24 | s := "Hello World"
25 | ```
26 |
27 | 那么它在内存中存储是这样的:
28 |
29 | 
30 |
31 | 当我们在程序中对字符串进行重新赋值时,比如这样:
32 |
33 | ```go
34 | s := "Hello World"
35 |
36 | s = "Hello AlwaysBeta"
37 | ```
38 |
39 | 底层的存储就变成了这样:
40 |
41 | .png)
42 |
43 | Go 实际上是重新创建了一个 `[]byte{}` 切片,然后让指针指向了新的地址。
44 |
45 | 更直接一点,我们直接修改字符串中的单个字符,比如:
46 |
47 | ```go
48 | s := "Hello World"
49 | s[0] = 'h'
50 | ```
51 |
52 | 这样做的话,会直接报错:
53 |
54 | ```go
55 | cannot assign to s[0] (strings are immutable)
56 | ```
57 |
58 | 如果一定要这么做的话,需要对字符串进行一个转换,转换成 `[]byte` 类型,修改之后再转换回 `string` 类型:
59 |
60 | ```go
61 | s := "Hello World"
62 | sBytes := []byte(s)
63 | sBytes[0] = 'h'
64 | s = string(sBytes)
65 | ```
66 |
67 | 这样就可以了。
68 |
69 | 以上就是本文的全部内容,如果觉得还不错的话欢迎**点赞**,**转发**和**关注**,感谢支持。
70 |
71 | ***
72 |
73 | **推荐阅读:**
74 |
75 | * [Go 语言 map 如何顺序读取?](https://mp.weixin.qq.com/s/iScSgfpSE2y14GH7JNRJSA)
76 | * [Go 语言 map 是并发安全的吗?](https://mp.weixin.qq.com/s/4mDzMdMbunR_p94Du65QOA)
77 | * [Go 语言切片是如何扩容的?](https://mp.weixin.qq.com/s/VVM8nqs4mMGdFyCNJx16_g)
78 | * [Go 语言数组和切片的区别](https://mp.weixin.qq.com/s/esaAmAdmV4w3_qjtAzTr4A)
79 | * [Go 语言 new 和 make 关键字的区别](https://mp.weixin.qq.com/s/NBDkI3roHgNgW1iW4e_6cA)
80 | * [为什么 Go 不支持 \[\]T 转换为 \[\]interface](https://mp.weixin.qq.com/s/cwDEgnicK4jkuNpzulU2bw)
81 | * [为什么 Go 语言 struct 要使用 tags](https://mp.weixin.qq.com/s/L7-TJ-CzYfuVrIBWP7Ebaw)
82 |
83 |
--------------------------------------------------------------------------------
/xmind/Go Web 开发.xmind:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/yongxinz/gopher/030046cf44fb5b2a2c90112b3d179ae9ba9df000/xmind/Go Web 开发.xmind
--------------------------------------------------------------------------------
/xmind/Go 书籍推荐.xmind:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/yongxinz/gopher/030046cf44fb5b2a2c90112b3d179ae9ba9df000/xmind/Go 书籍推荐.xmind
--------------------------------------------------------------------------------
/xmind/Go 入门教程.xmind:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/yongxinz/gopher/030046cf44fb5b2a2c90112b3d179ae9ba9df000/xmind/Go 入门教程.xmind
--------------------------------------------------------------------------------
/xmind/Go 学习路线.xmind:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/yongxinz/gopher/030046cf44fb5b2a2c90112b3d179ae9ba9df000/xmind/Go 学习路线.xmind
--------------------------------------------------------------------------------
/xmind/Go 实用工具.xmind:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/yongxinz/gopher/030046cf44fb5b2a2c90112b3d179ae9ba9df000/xmind/Go 实用工具.xmind
--------------------------------------------------------------------------------
/xmind/Go 技术社区.xmind:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/yongxinz/gopher/030046cf44fb5b2a2c90112b3d179ae9ba9df000/xmind/Go 技术社区.xmind
--------------------------------------------------------------------------------
/xmind/Go 练手项目.xmind:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/yongxinz/gopher/030046cf44fb5b2a2c90112b3d179ae9ba9df000/xmind/Go 练手项目.xmind
--------------------------------------------------------------------------------
/xmind/Go 进阶教程.xmind:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/yongxinz/gopher/030046cf44fb5b2a2c90112b3d179ae9ba9df000/xmind/Go 进阶教程.xmind
--------------------------------------------------------------------------------