Go 的源码在安装包的 src/ 目录下。怎么看它的源码呢?直接看吧!没人教的情况下,只能自己撸了。当然,这种内容一般也不会有人教。
怎么撸?
Go 源码中,应该可分为与语言息息相关的部分,和官方提供的标准库。与语言实现相关的肯定是最难的,不是那么容易理解。可以先主要看标准库,其他的可以先大概了解下,待准备充足,再开始艰难的任务。
第二步,先把源码目录整体扫一遍,大概看看涉及了哪些模块,再挑自己喜欢的部分进行更深一步的学习与研究。建议每个库都看下官方文档,简单写个 hello world,才会体悟更深。如果连 hello world 都写不出来,这个模块的源码暂时就没必要研究了,先学好基础吧。毕竟,包的使用不仅与语言相关,还涉及具体场景和实现原理,这都是要学习的。
第二步,对包的使用熟悉理解后,就可以阅读源码了,但此时最好还是不要太抠细节,大概理解涉及设计思想,整体流程。源码阅读可以通过画 UML 的方式辅助,从纵向和横向帮助理解。代码设计时,一般最容易想到的就是按顺序方式写,很快就能搞定。但当项目变大,抽象的模块会越来越多,抽象出接口和具体的实现,实现可能包含其他类型的组合。搞明白这些关系,对于理解源码实现会较有帮助。我觉得,经过这一步,包的使用就很得心应手了。
第三步,如果顺利经过前面两步,如果该包没有太多领域知识,接下来的源码阅读就比较简单了。Go 语言的特点就是简洁易读,没什么语法糖。当然,如果是一些实现比较复杂的包,你还需知道它们的底层原理,就比如 net/http 包,你得对 http 协议熟悉到一定程度,才可能从细节研究源码实现。 是否要到第三步,视自己的情况而定,能到这一步应该就能达到源码随意改,基本没啥后顾之忧的层次了。
推荐两本书,分别是 Go 语言学习笔记,后半部分有源码的解读。另外一本,Go 语言高级编程,对 cgo 和汇编有非常仔细的介绍。
可能是我闲的蛋疼,准备试着先从第一步出发,整体撸一下 Go 的源码中包含的模块,没事的时候就更新一点进去。等把这些大致撸完一遍,感觉我的 Golang 之旅 专栏又可以多出很多写作素材了。
我的环境是 Go 1.11。关于每个模块,我会把读过的一些文章放在下面,由于只是粗略阅读,并不能保证读过的每篇文章都是精品。
补充:
2019年8月8日 凌晨 01:13, 大概花了两个多星期的零碎时间,简单撸完了一版。总的感觉,还是有很多地方理解不够,希望后面可以按前面说的思路,按包逐步进行源码解剖。
archive
包含了文件归档的相关内容,其中涉及了两个包,分别是 tar 和 zip。
archive/tar,即归档,如果了解 Linux 下的 tar 命令,可与之对应理解。如果要在归档基础上进行压缩,还要借助 compress 下的相关包。提醒一点,是使用时要注意理解归档与压缩的区别。
相关阅读:
archive/zip,与 zip 格式压缩文件操作相关的包,使用方法与 tar 很类似。在寻找与 zip 包相关的资料时,了解到 zip 的作者年仅 37 岁就逝世了,而全世界所有使用 zip 压缩的文件开头部分都有他的名字 "PK",而我们识别一个文件是否是 zip 正是通过这种方法。
相关阅读:
bufio
实现了缓冲 IO 的功能,通过包裹 io.Reader 或 io.Writer 函数创建新的 Reader 或 Writer 实例,并且这些新创建的实例提供了缓冲的能力。使用方法非常简单,达到指定缓冲大小,触发写或读操作,如未达到要求,可用 Flush 方法刷新。
相关阅读:
builtin
Go 语言中的内置类型、函数、变量、常量的声明。暂时看来,没啥可深入阅读的,应该结合 Go 的内部实现进行阅读。
bytes
主要是关于 byte slice 操作的一些函数。由于 []byte 也可用于表示 string,故其中的函数、方法与 strings 很类似,比如 Join、Split、Trim、 Contains 等。
相关阅读:
cmd
Go 命令工具集的实现代码,如 go、gofmt、godoc、pprof 等,应该主要是和 Go 语言实现相关性较大,比较底层。每个命令都够研究一段时间了,特别是 go 命令,并且前提是你的计算机底层原理的功底要足够优秀。
网上搜索下,关于它的资料比较少。
相关阅读:
compress
之前提到 archive 包中是归档相关操作,而相对的 compress 包主要与压缩相关。主要实现了几种主流的压缩格式,如 bzip2、flate、gzip、lzw、zlib。
compress/bzip2,常见的 .bz2 结尾的压缩文件格式基本可用这个包操作,要与 tar 结合使用。
compress/gzip,常见的 .gz 结尾的压缩文件格式基本可用这个包操作,要与 tar 结合使用。
compress/flate,flate 应该主要是 zip 用的压缩算法,如果阅读了前面的 archive/zip 的源码,就会发现其中导了这个包。
compress/zlib, compress/lzw 基本与上面同理,应该都是某种压缩算法实现。因为我对压缩算法没什么太深的研究,暂时了解个大概就好了,希望没有介绍错误。
相关阅读:
container
我们知道,Go 内置的数据结构很少,只有数组、切片和映射。除此以外,其实还有部分的结构放在了 container 包中,heap 堆、list 双端队列,ring 回环队列。
它们的使用非常简单,基本就是增删改查。
相关阅读:
context
读这个包之前,得首先熟悉 Go 的并发代码如何编写,了解 Done channel 如何实现向所有 goroutine 发送广播信号。Go 的并发单元称为 goroutine,但是不同 goroutine 之间并没有父子兄弟关系,为了更好地并发控制,context 包就诞生了。它可以实现在不同 goroutine 间安全地传递数据以及超时管理等。
相关阅读:
crypto
加密相关,涉及内容有点多,包含了各种常用的加密算法实现,比如对称加密啊 AES、DES 等,公私钥加密 rsa、dsa 等,散列算法 sha1、sha256 等,随机数 rand 也有,不知道和 math 的随机有什么区别。没有找到一篇综合性介绍的文章,毕竟比较复杂了,如果要看它们的源码,得先要大概了解下每个加密算法的原理,才好逐一突破。
相关阅读:
database
封装了一套用于数据库操作的通用接口,实现了数据库连接管理,支持连接池功能。真正使用时,我们需要引入相应的驱动,才能实现指定类型数据库的操作。
一个简单的例子。
import (
"database/sql"
_ "github.com/go-sql-driver/mysql"
)
func main() {
db, err := sql.Open("mysql", "username:password@tcp(127.0.0.1:3306)/test")
if err != nil {
log.Fatal(err)
}
defer db.Close()
}
http://github.com/go-sql-driver/mysql 便是提供的 MySQL 驱动。具体的查询执行都是通过调用驱动实现的 db 接口中的方法。
相关阅读:
debug
和调试相关,具体内容比较复杂,我也不是很懂。内部有几个包,如 dwarf、elf、gosym、macho、pe、plan9obj。
dwarf,可用于访问可执行文件中的 DWARF 信息。具体什么是 DWARF 信息呢?官网有个 PDF,具体介绍了什么是 DWARF,有兴趣可以看看。它主要是为 UNIX 下的调试器提供必要的调试信息,例如 PC 地址对应的文件名行号等信息,以方便源码级调试。
相关阅读:
elf,用于访问 elf 类型文件。elf,即可执行与可连接格式,常被称为 ELF 格式,有三种类型:可重定位的对象文件(Relocatable file),由汇编器汇编生成的 .o 文件
可执行性的对象文件(Executable file),可执行应用程序
可被共享的对象文件(Shared object file),动态库文件,也即 .so 文件
相关阅读:
gosym,用于访问 Go 编译器生成的二进制文件之中的 Go 符号和行信息等,暂时还没怎么看。在 medium 发现个系列文章,介绍了 Go 中 debug 调试器的实现原理,相关阅读部分是系列的第二篇文章。
相关阅读:
macho,用于访问 Mach-O object 格式文件。要阅读这段源码,同样需要先了解什么是 Mach-O,它是 Mach object 文件格式的缩写,用于可执行文件、目标代码、内核转储的文件格式。
相关阅读:
pe,实现了访问 PE 格式文件,PE 是 Windows 系统可移植的可执行文件格式。
相关阅读:
plan9obj,用于访问 plan9 object 格式文件。
暂未找到关于 plan9object 的介绍文章。我们主要学习的话,主要应该是集中在 elf 和 gosym 两个格式。
相关阅读:
encoding
主要关于我们常用到的各种数据格式的转化操作,或也可称为编解码,比如 JSON、XML、CSV、BASE64 等,主要的模块有:
encoding/json,json 处理相关的模板,通用方式,我们可以将解析结果放到 map[string]interface{} 解析,也可以创建通用结构体,按 struct 方式进行。
encoding/xml,基本和 encoding/json 类似。但因为 XML 比 json 要复杂很多,还涉及一些高级用法,比如与元素属性相关等操作。
encoding/csv,csv 数据格式解析。
encoding/binary,可用于处理最底层的二进制数据流,按大小端实现 []byte 和整型数据之间的转化。
其他诸如 hex、gob、base64、base32、gob、pem、ascii84 等数据格式的操作都是类似的,有兴趣可以都尝试一下。
相关阅读:
errors
Go 的错误处理主要代码就是它。很遗憾的是,打开源码后发现,就几行代码哦。主要是因为 Go 的错误类型只是一个接口而已,它的源码非常简单。
package errors
// New returns an error that formats as the given text.func New(text string) error {
return &errorString{text}
}
// errorString is a trivial implementation of error.type errorString struct {
s string
}
func (e *errorString) Error() string {
return e.s
}
Go 默认只提供了最简单的实现,就上面这几行代码。真的是 awesome、amazing,哈哈。但正是因为简单,扩展出自己的 error 变得很简单。比如,有些开发者认为 Go 的错误处理太简单,开发了一些包含 call stack trace 的 error 包。
相关阅读:
expvar
主要是用于 Go 程序运行时的指标记录,如 HTTP 服务在加入 expvar 后,我们可以通过 /debug/vars 返回这些指标,返回的数据是 JSON 格式的。
它的源码不多,也就大约 300 行代码,重点在它使用方法。
相关阅读:
flag
用于命令行参数解析的包,比如类似命令参数 grep -v grep,具体操作的时候要获取 -v 后的参数值。很常用的功能,如果纯粹自己实现是比较繁琐的。
相关阅读:
fmt
从包名就可以知道,fmt 主要和格式化相关,关于什么的格式化呢?主要是字符串的格式化,它的用法和 C 中 printf 都很类似。当然,除了实现 C 的用法,还提供了一些 Go 特有的实现。
相关阅读:
go
似乎是核心工具使用的包。
hash
hash 包主要定义了不同的 hash 算法的统一接口。而具体的 hash 算法实现有的直接 hash 的下层,比如 crc32、crc64,即 32 位循环冗余校验算法和 64 位循环冗余校验算法。而 md5 hash 算法在 crypto/md5 下,同样实现了 hash 的相关接口。
相关阅读:
html
Go 标准库里的 html 包功能非常简单,大概了看下,主要是关于 html 文本的处理,例如该如何对 html 代码做转义。如果想支持 html 的解析,go 官方 github 下还提供了一个 net 仓库,其中有个 html 的工具包。而 goquery 也是基于它实现的。
标准库的 html 目录下还有 template,html 的模板渲染工具,通过与 net/http 相结合,再加上一个数据库 orm 包,简单的 web 开发就可以开始了。
相关阅读:
image
Go 2D 图像处理库,支持创建 2D 处理的方法函数,图片创建、像素、颜色设置,然后进行绘制。主要支持 png、jpeg、gif 图片格式。
相关阅读:
index
目录为 index,其中只有一个包 index/suffixarray,称为后缀数组。具体算法没仔细研究,大致是将子字符串查询的时间复杂度降低到了 $log_n$。
使用非常简单,官网已经提供了一个例子。
// create index for some dataindex := suffixarray.New(data)
// lookup byte slice soffsets1 := index.Lookup(s, -1) // the list of all indices where s occurs in dataoffsets2 := index.Lookup(s, 3) // the list of at most 3 indices where s occurs in data
相关阅读:
internal
内部实现,比较复杂。
io
Go 的标准库中,为 io 原语提供了基本的接口和实现,帮助字节流的读取。接口主要就是 io.Reader 和 io.Writer。io 包提供了一些常用资源的接口实现,比如内存、文件和网络连接等资源进行操作。
阅读 io 包的源码,会发现很多接口都是基于具体的能力定义,最简单的有 Reader(读)、Writer(写)、Closer(关闭)、Seeker(偏移),一个接口一个方法,非常灵活。组合的接口还有 ReaderWriter(读写)、ReadeCloser(读与关)、WriteCloser(读写关) 和 ReadWriteCloser(读写关)等。整体理解,我们将会对 Go 接口是基于是鸭子模型的说法更有体会,
相关阅读:
log
Go 的日志包,通过记录日志可以方便我们进行问题调试。log 包的核心源码并不多,总共也就三百多行,其中注释就占了差不多一百行。主要是因为它提供的功能很少,只有基础的日志格式化,还有 Print、Panic、Fatal 三种日志打印函数。连错误级别没提供。如果要使用的话,还需要借助一些第三方的包。相关阅读中提供了一个 "Go 日志库集合" 的文章,具体我也没有深入研究。
相关阅读:
math
主要是关于数学计算方面的函数,一些数学常量,比如 PI(圆周率)、E(自然对数)等,就在其中,还有如四舍五入方面的函数 Round、Floor、Ceil、最大值 Max、最小值 Min,复杂的数学运算,比如幂运算、对数、三角函数肯定也有的,其他诸如随机数之类的函数也在其中。打开 math 源码文件夹,发现里面有大量的汇编代码,数学相对片底层,对性能要求会比较高,有必要用汇编实现。
math 包,直接看官方文档就好了,一般看了就可以用,没什么业务场景、具体原理需要了解,毕竟大家都学过数学。如果要看汇编实现,那就复杂了。有兴趣可以研究一下。
相关阅读:
mime
要了解 mime 包的使用,得先了解什么是 MIME,全称 Multipurpose Internet Mail Extension,即多用途互联网邮箱扩展类型。最初设计的目标是为了在发送邮件时,附加多媒体内容。后来,MIME 在 HTML 中也得到了支持。
其中主要有四个函数,AddExtensionType、TypeByExtension、FormatMediaType 和 ParseMediaType。前后两组函数似乎都是针对 MediaType 的互操作。
相关阅读:
net
网络相关,涉及内容比较多,有种吃不消的感觉。
底层的实现 socket 就在 net 包下,主要是一些底层协议的实现,比如无连接的 ip、udp、unix(DGRAM),和有连接的 tcp、unix(STREAM) 都可以在 net 包找到。
应用层协议,http 协议实现在 net/http 包含客户端服务端,rpc 在 net/rpc,邮件相关的 net/mail、net/smtp 等。net/url 是与 url 处理相关的函数,比如 url 字符串解析,编码等。
相关阅读:
os
os 包主要实现与操作系统相关的函数,并且是与平台无关的。它的设计是 UNIX 风格的,并且采用 Go 错误处理风格。发生错误将返回的 error 类型变量。比如 Open、Stat 等操作相关的函数。
os 包的目标是统一不同操作系统的函数。如果大家读过那本 UNIX 环境高级编程,你会发现 os 包中的函数与 Unix 的系统调用函数都很相似。
除了 os 包,该目录下还有几个包,分别是 os/exec、os/signal 和 os/user,如下:
os/exec,帮助我们实现了方便执行外部命令的能力。
os/signal,Unix-Like 的系统信号处理相关函数,Linux 支持 64 中系统信号。
os/user,与系统用户相关的库,可用于获取登录用户、所在组等信息。
相关阅读:
path
path 包实现了路径处理(通过 / 分隔)相关的一些常用函数,常用于如文件路径、url 的 path。不适合 Windows 的 \ 和磁盘路径处理。
主要包含的函数有 Base、Clean、Dir、Ext、IsAbs、Join 等函数。如 Base 可用于获取路径的最后一个元素,Dir 获取路径目录,Ext 获取文件扩展、IsAbs 判断是否为绝对路径,Join 进行路径连接等。
相关阅读:
plugin
plugin 包是 Go 1.8 出现的包,为 Go 增加了动态库加载的能力,当前只支持 Linux 和 MacOS。但这个包的应用并不是很方便,生成和使用库文件的环境有一定的要求。
相关阅读:
reflect
与反射相关的函数函数,通过反射可以实现运行时动态创建、修改变量,进行函数方法的调用等操作,获得本属于解释语言的动态特性。要阅读反射包源码,重点在理解变量的两个组成,即类型和值,反射的核心操作基本都是围绕它们进行。reflect.ValueOf 与 reflect.TypeOf 是我们常用的两个方法。
相关阅读:
regexp
Go 的正则包,用于正则处理。基本是每种语言都会提供。其中涉及的方法大致可分为几个大类,分别是 Compile 编译、Match 匹配、Find 搜索、Replace 替换。
正则的源码实现还真是不想看。感觉正则都没有完全理清楚,扯源码有点坑。特别头大。
相关阅读:
runtime
runtime 是与 Go 运行时相关的实现,我们可以通过它提供的一些函数控制 goroutine。关于 Go 进程的启动流程、GC、goroutine 调度器等,也是在 runtime 中实现,同样需要我们好好阅读 runtime 代码了解。除此以为,cgo、builtin 包的实现也是在 runtime。
相关阅读:
sort
定义了排序的接口,一旦某个类型实现了排序的接口,就可以利用 sort 中的函数实现排序。通过阅读源码,我发现默认支持排序的类型包括 int、float64、string。sort 中还有个 search 文件,其中主要是已排序内容二分查找的实现。
我们都知道,排序算法很多,比如插入排序、堆排序与快速排序等,sort 包都已经实现了,并且不用我们决定使用哪种算法,而是会依据具体的数据决定使用什么算法,并且一次排序不一定只要了一种算法,而可能是多种算法的组合。如何做算法选择可以通过阅读 sort.go 文件中的 quickSort 函数了解。
相关阅读:
strconv
关于字符串与其他类型转化的包,名字全称应该是 string convert,即字符串转化。比如整型与字符串转化的 Itoa 与 Atoi,浮点型与字符串的转化 ParseFloat 与 FormatFloat,布尔型与字符串转化 ParseBool 与 FormatBool 等等。
相关阅读:
strings
针对字符串的操作函数,前面也提过到,因为 []byte 也可用于表示字符串,strings 中的很多函数在 bytes 包也有类似的实现,比如 Join、Split、Trim,大小写转化之类的函数等。
相关阅读:
sync
Go 推荐以通信方式(channel)实现并发同步控制,但传统机制也是支持的,比如锁机制、条件变量、WaitGroup、原子操作等,而它们都是由 sync 提供的。其中,原子操作在 sync/atomic 包下。
除此之外,sync 中还有个临时对象池,可以实现对象复用,并且它是可伸缩且并发安全的。
相关阅读:
syscall
系统调用,从名字就能知道,这个包很复杂。系统调用是实现应用层和操作底层的接口,不同系统之间的操作常常会有一定的差异,特别是类 Unix 与 Windows 系统之间的差异较大。
如果想要寻找 syscall 的使用案例,我们可以看看 net、os、time 这些包的源码。
如果要看这部分源码,当前的想法是,我们可以只看 Linux 的实现,架构的话,如果想看汇编,可以只看 x86 架构。
暂时研究不多,不敢妄言。
相关阅读:
testing
Go 中测试相关的实现,比如单元测试、基准测试等。Go 推荐的测试方式采用表格驱动的测试方式,即非每种情况都要写一个单独的用例,而是通过列举输入、期望输出,然后执行功能并比较期望输出与实际输出是否相同。
一个简单的测试用例。
func TestSum(t *testing.T) {
var sumTests = []struct {
a int
b int
expected int
}{
{1, 1, 2},
{2, 1, 3},
{3, 2, 5},
{4, 3, 7},
{5, 5, 10},
{6, 8, 14},
{7, 13, 20},
}
for _, tt := range sumTests {
actual := functions.Add(tt.a, tt.b)
if actual != tt.expected {
t.Errorf("Add(%d, %d) = %d; expected %d", tt.a, tt.b, actual, tt.expected)
}
}
}
相关阅读:
text
主要是关于文本分析解析的一些包,但又不同于字符串处理,主要涉及词法分析 scanner、模板引擎 template、tab 处理 tabwriter。
text/scanner,主要是做词法分析的,如果大家读过我的专栏翻译的几篇关于词法分析的文章,对它的理解会比较轻松。
text/template,用于文本的模板处理,相对于 html/template 的具体应用场景,text/template 更通用。要熟悉使用它,还需要掌握它的一些方法,比如 Action、Argument、Pipeline、Variable、Function。
text/tabwriter,感觉没啥介绍的,好像主要是根据 tab 进行文本对齐的。
相关阅读:
time
关于日期时间的包,Go 中的 unix timestamp 是 int64,表示的时间范围相应的也就有所扩大。其他的诸如睡眠、时区、定时控制等等都支持,Go 中有个逆人性的规则,那就是日期时间的格式化字符,比如传统语言的格式化字符串 YYYY-MM-DD 在 Go 却是 2006-01-02 的形式,奇葩不奇葩。
相关阅读:
unicode
unicode 编码相关的一些基本函数,读源码会发现,它通过把不同分类字符分别到不同的 RangeTable 中,实现提供函数判断字符类型,比如是否是控制字符、是否是字母等。另外两个包 unicode/utf8 和 unicode/utf16 可用于 unicode (rune) 与 utf8 (byte)、unicode (rune) 与 utf16 (int16) 之间的转化。
相关阅读:
unsafe
Go 语言限制了一些可能导致程序运行出错的用法,通过编译器就可以检查出这些问题。当然,也有部分问题是无法在编译时发现的,Go 给了比较优化的提示。但通过 unsafe 中提供的一些方法,我们可以完全突破这一层限制,从包名就可以知道,unsafe 中包含了一些不安全的操作,更加偏向于底层。一些比较低级的包会调用它,比如 runtime、os、syscall 等,它们都是和操作系统密切相关的。我们最好少用 unsafe,因为使用了它就不一定能保证程序的可移植性或未来的兼容性问题。
相关阅读:
vendor
标准库中依赖的第三方包,当然也都由 Go 官方所开发,默认包括的依赖有:golang_org/x/crypto
golang_org/x/net
golang_org/x/text
举个例子,加密相关的 crypto 包中实现就用到了 golang_org/x/crypto/curve25519 中的方法。
除了源码中自带的标准库,官方其实还提供了其他很多诸如 crypto、net、text 之类的包。具体可以查看 Go 官方 github 地址。
欢迎关注我的专栏,Golang 之旅,见证我的 Golang 学习历程。