├── .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 | ![](https://cdn.jsdelivr.net/gh/yongxinz/picb@main/data/001-byte-slice-to-io-reader.png) 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 | ![](https://cdn.jsdelivr.net/gh/yongxinz/picb@main/data/io-reader-writer.png) 89 | 90 | 下面就来分别说说这两个接口: 91 | 92 | #### Reader 接口 93 | 94 | `io.Reader` 表示一个读取器,它将数据从某个资源读取到传输缓冲区。在缓冲区中,数据可以被流式传输和使用。 95 | 96 | ![](https://cdn.jsdelivr.net/gh/yongxinz/picb@main/data/io-reader.png) 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 | ![](https://cdn.jsdelivr.net/gh/yongxinz/picb@main/data/io-writer.drawio.png) 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 | ![](https://cdn.jsdelivr.net/gh/yongxinz/picb@main/data/io-bytes-strings.png) 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 | ![](https://cdn.jsdelivr.net/gh/yongxinz/picb@main/data/errors.png) 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 | ![](https://cdn.jsdelivr.net/gh/yongxinz/picb@main/data/dumels-1.png) 42 | 43 | 使用起来很方便,首先在页面最上方输入框输入项目地址,然后在左侧输入要分析的代码目录就可以了。默认生成的图中会包括 Fields 和 Methods。 44 | 45 | 填写好信息之后就可以生成 UML 图了。比如我输入的 `src/sync`,就得到了下面这张图,有了这张图,对代码结构之间的关系就更清晰了。 46 | 47 | ![](https://cdn.jsdelivr.net/gh/yongxinz/picb@main/data/dumels-2.png) 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 | ![](https://github.com/yongxinz/gopher/blob/main/blog/pic/05-id-maker.png) 85 | 86 | 下面对各目录做一个简要说明: 87 | 88 | - **cmd**:程序入口 89 | - **config**:配置文件 90 | - **docs**:生成的项目文档 91 | - **integration-test**:整合测试 92 | - **internal**:业务代码 93 | - **pkg**:一些调用的包 94 | 95 | 借用官方的两张图: 96 | 97 | ![](https://github.com/yongxinz/gopher/blob/main/blog/pic/05-go-clean-template-1.png) 98 | 99 | 整体的层次关系是这样的,最里面是 models,定义我们的表结构,然后中间是业务逻辑层,业务逻辑层会提供接口,给最外层的 API 来调用,最外层就是一些工具和调用入口。 100 | 101 | 这样做的最大好处就是解耦,不管最外层如何变化,只要在业务逻辑层实现对应接口即可,核心代码可能根本不需要改变。 102 | 103 | 所以,它们之间的调用关系看起来是这样的: 104 | 105 | ![](https://github.com/yongxinz/gopher/blob/main/blog/pic/05-go-clean-template-2.png) 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 | ![](https://cdn.jsdelivr.net/gh/yongxinz/picb@main/data/periodlimit1.png) 25 | 26 | 固定窗口计数器是最为简单的算法,但这个算法有时会让通过请求量允许为限制的两倍。 27 | 28 | ![](https://cdn.jsdelivr.net/gh/yongxinz/picb@main/data/periodlimit2.png) 29 | 30 | 考虑如下情况:限制 1 秒内最多通过 5 个请求,在第一个窗口的最后半秒内通过了 5 个请求,第二个窗口的前半秒内又通过了 5 个请求。这样看来就是在 1 秒内通过了 10 个请求。 31 | 32 | ## 滑动窗口计数器 33 | 34 | 算法概念如下: 35 | 36 | - 将时间划分为多个区间; 37 | - 在每个区间内每有一次请求就将计数器加一维持一个时间窗口,占据多个区间; 38 | - 每经过一个区间的时间,则抛弃最老的一个区间,并纳入最新的一个区间; 39 | - 如果当前窗口内区间的请求计数总和超过了限制数量,则本窗口内所有的请求都被丢弃。 40 | 41 | ![](https://cdn.jsdelivr.net/gh/yongxinz/picb@main/data/periodlimit3.png) 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 | ![](https://cdn.jsdelivr.net/gh/yongxinz/picb@main/data/array.png) 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 | ![](https://cdn.jsdelivr.net/gh/yongxinz/picb@main/data/arrayparams.png) 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 | ![](https://cdn.jsdelivr.net/gh/yongxinz/picb@main/data/slice.png) 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 | ![](../images/17188139512ef9a8.jpg) 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 | ![](../images/17188139b47a27ca.jpg) 139 | 140 | 这个时候,一个Goroutine结构体被创建,在进行函数体地址、参数起始地址、参数长度等信息以及调度相关属性更新之后,它就要进到一个处理器的队列等待发车。 141 | 142 | ![](../images/17188139f32b2558.jpg) 143 | 144 | 啥,又创建了一个G?那就轮流往其他P里面放呗,相信你排队取号的时候看到其他窗口没人排队也会过去的。 145 | 146 | ![](../images/1718813a3359a1c0.jpg) 147 | 148 | 假如有很多G,都塞满了怎么办呢?那就不把G塞到处理器的私有队列里了,而是把它塞到全局队列里(候车大厅)。 149 | 150 | ![](../images/1718813a71f700a1.jpg) 151 | 152 | 除了往里塞之外,M这边还要疯狂往外取,首先去处理器的私有队列里取G执行,如果取完的话就去全局队列取,如果全局队列里也没有的话,就去其他处理器队列里偷,哇,这么饥渴,简直是恶魔啊! 153 | 154 | ![](../images/1718813abb679031.jpg) 155 | 156 | 如果哪里都没找到要执行的G呢?那M就会因为太失望和P断开关系,然后去睡觉(idle)了。 157 | 158 | ![](../images/1718813afa26977e.jpg) 159 | 160 | 那如果两个Goroutine正在通过channel做一些恩恩爱爱的事阻塞住了怎么办,难道M要等他们完事了再继续执行?显然不会,M并不稀罕这对Go男女,而会转身去找别的G执行。 161 | 162 | ![](../images/1718813b326b7c73.jpg) 163 | 164 | 165 | ## 系统调用 166 | 167 | 如果G进行了系统调用syscall,M也会跟着进入系统调用状态,那么这个P留在这里就浪费了,怎么办呢?这点精妙之处在于,P不会傻傻的等待G和M系统调用完成,而会去找其他比较闲的M执行其他的G。 168 | 169 | ![](../images/1718813b73c47064.jpg) 170 | 171 | 当G完成了系统调用,因为要继续往下执行,所以必须要再找一个空闲的处理器发车。 172 | 173 | ![](../images/1718813bad18d998.jpg) 174 | 175 | 如果没有空闲的处理器了,那就只能把G放回全局队列当中等待分配。 176 | 177 | ![](../images/1718813bec3d6674.jpg) 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 | ![](https://github.com/yongxinz/gopher/blob/main/sc/pic/00_GO%E5%AE%89%E8%A3%85%E4%B8%8E%E9%85%8D%E7%BD%AE.png) 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 | ![](https://github.com/yongxinz/gopher/blob/main/sc/pic/00_go_dl.png) 17 | 18 | 直接安装最新版本 go1.16.6,后续文章都会在此版本下开发,测试。 19 | 20 | #### Mac 下安装 21 | 22 | 可以通过 `brew` 方式安装,也可以直接在官网下载可执行文件,然后双击安装包,不停下一步就可以了。 23 | 24 | ![](https://github.com/yongxinz/gopher/blob/main/sc/pic/00_mac_install_go.png) 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 | ![](https://github.com/yongxinz/gopher/blob/main/sc/pic/00_vs_code_install_go.png) 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 | ![](https://github.com/yongxinz/gopher/blob/main/sc/pic/02_%E5%9F%BA%E7%A1%80%E6%95%B0%E6%8D%AE%E7%B1%BB%E5%9E%8B.png) 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 | ![](https://github.com/yongxinz/gopher/blob/main/sc/pic/06_%E5%87%BD%E6%95%B0.png) 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 | ![](https://github.com/yongxinz/gopher/blob/main/sc/pic/08_%E6%96%B9%E6%B3%95.png) 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 | ![](https://cdn.jsdelivr.net/gh/yongxinz/picb@main/data/string.drawio.png) 30 | 31 | 当我们在程序中对字符串进行重新赋值时,比如这样: 32 | 33 | ```go 34 | s := "Hello World" 35 | 36 | s = "Hello AlwaysBeta" 37 | ``` 38 | 39 | 底层的存储就变成了这样: 40 | 41 | ![](https://cdn.jsdelivr.net/gh/yongxinz/picb@main/data/string.drawio%20\(1\).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 --------------------------------------------------------------------------------