原站地址:Go语言核心36讲_Golang_Go语言-极客时间
一、io包中的接口和工具
1. strings.Builder、strings.Reader 和 bytes.Buffer 这些类型实现了 io 包的很多接口,目的是什么?
是为了提高不同程序实体之间的互操作性。 程序实体是指比如网络和文件。
比如 io.Copy: func Copy(dst Writer, src Reader) (written int64, err error)
里面的 Writer 和 Reader 可以是网络,也可以是文件,只要实现了io.Reader接口和io.Writer接口就都可以了。可以把不同的实体抽象成一个统一的实体。
2. 扩展接口和实现类型的区别是什么?
实现类型是一个结构体; 扩展接口只是通过结构体的方式,嵌入了其他数据类型,达到扩展原有数据类型功能的目的,
扩展接口是多个接口的集合。要实现扩展接口,需要实现多个接口。
实现类型是把接口实现了,而扩展接口并没有把接口实现,它只是多个接口的集合。
3. 在io包中,io.Reader的扩展接口和实现类型都有哪些? (提升对io包的了解程度)
io.Reader的扩展接口有下面几种:
(1) io.ReadWriter:包含字节序列读取方法Read,和写入方法Write。
(2) io.ReadCloser:包含字节序列读取方法Read,和关闭方法Close。Close用于关闭数据读写的通路。这个接口是io.Reader和io.Closer的组合。
(3) io.ReadWriteCloser:io.Reader、io.Write 和io.Closer的组合。
(4) io.ReadSeeker:可以根据给定的偏移量去寻找新的位置,作为下一次读的起始索引。包含了寻找读写位置的基本方法Seek。io.Reader 和io.Seeker的组合。
(5) io.ReadWriteSeeker:io.Reader、io.Writer和io.Seeker的组合。
io.Reader接口的实现类型有下面几种:
(1) *io.LimitedReader:方法Read返回的总数据量会受到限制,无论被调用多少次。
(2) *io.SectionReader:Read方法只能够读取原始数据中的某一个部分。与切片类似,只暴露在窗口之中的数据。
(3) *io.teeReader:接受io.Reader和io.Writer两个类型的参数,把Reader读到的数据,通过字节切片中转的方式,写入io.Writer处。通常使用在数据流的处理中,比如计算下载速度。
(4) *io.multiReader:接受多个io.Reader类型的参数,并从中顺序地读取数据。
(5) *io.pipe:同步内存管道的核心实现
(6) * io.PipeReader:同步内存管道的读取端
4. io包中的接口都有哪些?
(1) 核心接口:io.Reader、io.Writer和io.Closer
(2) io.ByteReader:读取一个单一的字节。 strings.Reader和bytes.Buffer 是它的实现类型。
io.RuneReader:读取一个单一的Unicode 字符。strings.Reader和bytes.Buffer 是其实现类型
io.ByteScanner:读取和读回退单个字节
io.RuneScanner:读取和读回退单个Unicode 字符
io.ReaderAt:只读取数据,不修改已读计数的值。
io.ReaderFrom:从一个 Reader 中读取数据
io.WriteTo:将数据写入到一个 Writer 中
io.ReadWriter:读和写, *io.pipe 是其实现类型。
io.ReadWriteCloser:读写和关闭管道,net包有它的实现类型。
io.ByteWriter和io.WriterAt:功能和上面对应。实现类型是 *os.File
io.Seeker:寻找并设定下一次读取或写入时的起始索引位置。strings.Reader和io.SectionReader都是其实现类型。
io.Closer:关闭管道。io.PipeReader和io.PipeWriter 是其实现类型。
(3) io包中的简单接口共有 11 个。读取操作相关的5 个,写入操作相关的 4 个,关闭操作有关的1 个,读写位置设定相关的一个。此外,还包含了 9 个基于这些简单接口的扩展接口。
二、bufio包中的数据类型
1. bufio 包的程序实体,是在包装简单 I/O 接口类型值的基础上,添加了缓冲区。
2. bufio包中的数据类型主要有:(1) Reader (2) Scanner (3) Writer 和 ReadWriter。
3. bufio.Reader类型值中的缓冲区起着怎样的作用?
Reader值会预先从底层读取器里读出一部分数据,暂存于缓冲区之中。当Reader值下次读取数据时,先从缓冲区中读取。 减少了底层读取器的使用次数,降低读取的执行时间。
虽然,读取时会增加填充缓冲区的操作,但从总体上看,平均执行时间会有大幅度的缩短。
4. bufio.Reader类型通过私有的 fill方法 来整理缓冲区,把已读的空间腾出来,提供给底层读取器存放数据。
整理方法就是把 [已读计数, 已写计数) 范围内的数据往最前面搬运,如下图所示:
5. bufio.Reader类型读取方法分别有哪些?
(1) Peek方法:读取缓冲区中的n个未读字节,n大于缓冲区长度的话,转而直接从底层读取器中读出数据。
会从已读计数代表的索引位置开始读,读完不更改已读计数。
(2) Read方法:其他和Peek方法一样,但会更改已读计数。
(3) ReadSlice方法:持续地读取数据,直至遇到调用方给定的分隔符为止。
如果缓冲区满了仍然找不到分隔符,会把整个缓冲区作为第一个结果值,缓冲区已满错误作为第二个结果值 返回。
(4) ReadBytes方法: 其他和ReadSlice方法一样,但缓冲区满了仍然找不到分隔符的话,会再次调用ReadSlice方法,整理缓冲区之后继续从底层读取器中读出数据,直至少找到分隔符或者读完全部数据。
6. 内容泄露:Peek方法、ReadSlice方法和ReadLine方法都有可能会造成内容泄露,因为他们都是返回直接基于缓冲区的字节切片。 只有Read方法不会内容泄露。
三、使用os包中的API
1. os代码包中的 API,是对操作系统的某方面功能的高层次抽象,使我们可以用统一的方式,操纵不同的操作系统。
最有代表性的就是数据类型 os.File,它实现了io包3 个核心接口io.Reader、io.Writer和io.Closer,3 个简单接口,io.ReaderAt、io.Seeker和io.WriterAt,以及9 个扩展接口中的 7 个。
所以除 文本文件、二进制文件、压缩文件、目录这些常见的形式之外,还有符号链接、各种物理设备、命名管道,以及套接字(socket)都可以被视为文件。
2. 怎样才能获得一个os.File类型的指针值(File值)?
(1) os.Create函数:根据给定的路径创建一个新的文件。
会返回一个File值和一个错误值。可以通过File值进行读写,路径不存在的话会返回错误。
(2) os.Open函数:打开一个文件并返回包装了该文件的File值。
只能从该File值中读取内容,而不能写入内容。
(3) os.NewFile函数:依据已经存在的文件描述符,新建包装了该文件的File值。
(4) os.OpenFile函数:新建或打开文件。
可读可写。函数有 3 个参数,为name(文件路径)、flag(操作模式)和perm(权限模式)。
可以视为这是基础函数,上面3个函数只是这个基础函数的参数组合。
3. 文件描述符,作为某个文件的一个标识存在。由 I/O 相关的系统调用返回,是很小的非负整数。
任何文件的I/O 操作都需要这个文件描述符,它被存储在File值中。
4. File值的操作模式都有哪些?
os.O_APPEND:追加模式写入内容。
os.O_CREATE:路径不存在时创建文件。
os.O_SYNC:在打开的文件上实施同步 I/O,保证读写的内容总会与硬盘上的数据同步。
os.O_TRUNC:文件已存在时清空文件内容。
多个操作模式可以通过按位或操作符 " | " 组合起来的。
5. File值的权限模式都有哪些?
权限模式参数 是uint32类型的再定义类型,包含了 32 个比特位,每个比特位都有特定含义:
(1) 最高比特位,1 代表 目录。
(2) 第 26 个比特位,1代表 命名管道
(3) 最低的 9 个比特位才用于文件的权限,分别是 文件所有者、用户组、其他用户 对该文件的访问权限(读、写和执行)。
四、访问网络服务
1. 进程间通信,称为 IPC。主要方法包括:系统信号(signal)、管道(pipe)、套接字(socket)、文件锁、消息队列、信号量(semaphore)等。 socket 是最为通用和灵活的一种。
2. socket实例相关的API,是由一个名为 socket系统调用 代表的,它是连接应用程序和操作系统内核的桥梁。
syscall代码包中,有一个与这个 socket系统调用 相对应的函数。函数本身是平台不相关的。在其底层,Go 为每个操作系统都做了适配,所以无论在哪个平台上总是有效的。
net代码包中的很多程序实体,都会直接或间接地使用到syscall.Socket函数,比如net.Dial函数
3. net.Dial函数的第一个参数network有哪些可选值?
(1) TCP、TCP4、TCP6:代表 TCP 协议,自适应、第四版、第六版
(2) UDP、UDP4、UDP6:代表 UDP 协议,自适应、第四版、第六版
(3) unix、unixgram、unixpacket:代表 Unix 通信域下的内部 socket 协议,socket 类型分别为SOCK_STREAM、SOCK_DGRAM、SOCK_SEQPACKET。
4. 有消息边界,是指内核程序在发送或接收数据时,是以消息为单位的。
有逻辑连接,是指通信双方在收发数据之前必须先建立网络连接。
5. TCP 没有消息边界,有逻辑连接。 好处:保证可靠性,有序,双向传输。坏处:速度慢。
UDP 有消息边界,没有逻辑连接。好处:速度快。坏处:不保证可靠,无需,只能单向传输。
6. net.DialTimeout函数 调用时给定的超时时间意味着什么?
给定的超时时间,意味着函数为网络建立连接,可以等待的最长时间。
调用net.DialTimeout函数之后,时间主要花费在 “解析参数network和address的值”,以及“创建 socket 实例并建立网络连接”这两件事情上。
五、基于HTTP协议的网络服务
1. net/http代码包 可以使用 基于http协议的网络服务。比如 http.Get函数。
实例: resp, err := http.Get(url) 返回两个值:resp 和 err
resp:数据类型是*http.Response,响应内容的结构体。
err:数据类型是error,创建、发送请求、接收和解析、响应 全过程中,可能发生的错误。
2. http.Get函数
http.Get函数会在内部使用 缺省HTTP客户端,它是 net/http包中的公开变量DefaultClient代表的,其类型是 *http.Client, 它是开箱即用的。
3. http.Client类型中的 Transport字段 代表着什么?
(1) Transport字段 的数据类型是 *http.Transport,会在内部使用net.Dialer类型值,实现 连接 和 超时 的功能。
(2) http.Transport类型的值(以下简称Transport值)会对每一个网络服务的空闲连接的总数做出限定。 一个网络服务由网络地址、网络协议、代理 三方面来鉴定。
在默认情况下,空闲连接总数最大为100,而每个网络服务的最大空闲连接数为2。
(3) *http.Transport 是 http.RoundTripper接口 的实现类型。RoundTripper 内部有一个DefaultTransport的缺省值。 前面提到的 缺省HTTP客户端 DefaultClient ,其实也就是使用这个DefaultTransport。
(4) 总的来说 Transport字段代表着:向网络服务发送 HTTP 请求,并从网络服务接收 HTTP 响应的整个操作过程。
4. http.Server类型的ListenAndServe方法都做了哪些事情?
(1) 对一个基于 TCP 协议的网络地址进行监听(net.Listen),并对接收到的 HTTP 请求进行处理。
(2) 默认开启针对网络连接的存活探测机制,以保证连接是持久的。
(3) 该方法会一直执行,直到有严重的错误发生或者被外界关掉。
5. net.Listen函数都做了哪些事情?
(1) 解析参数值中网络地址包含的 IP 地址和端口号;
(2) 根据给定的网络协议,确定监听的方法,并开始进行监听。
6. http.Server类型的Serve方法是怎样接受和处理 HTTP 请求的?
(1) listen之后,进入一个for循环。
(2) for循环中,Accept方法会被不断地调用,该方法返回两个值:
net.Conn类型:代表包含了新到来的 HTTP 请求的网络连接
error类型:代表是否发生了错误。暂时性的错误的话for会继续执行,否则会for循环都会被终止
(3) 把 net.Conn类型的结果值包装成一个*http.conn类型,并启用新的 goroutine ,去调用这个conn值的serve方法,来对当前的 HTTP 请求进行处理。
六、程序性能分析基础
1. 性能分析 API 在这三个代码包中:
(1) runtime/pprof
(2) net/http/pprof
(3) runtime/trace
2. 概要文件(Profile)
runtime 代码包中包含更底层的 API,用来收集程序运行过程中的一些关键指标,并生成概要文件,提供分析使用。
go test 命令也可以在测试完成后生成 概要文件(Profile)。
3. 概要文件采样时刻的内容
分析程序性能的概要文件有CPU、内存、阻塞三种概要文件。文件里每段概要信息都记录着,某个采样时刻的内容分别是:
(1) CPU 概要文件:CPU 上正在执行的 Go 代码。
(2) 内存概要文件:内存的使用情况,已分配和已释放的字节数量和对象数量。
(3) 阻塞概要文件:goroutine 阻塞事件
4. 概要文件内容格式
这些概要文件是以二进制存储的,是通过 protocol buffers 生成的二进制字节流。
可以使用 go tool pprof 工具查看。
5. 怎样让程序对 CPU 概要信息进行采样?
(1) 进行采样调用StartCPUProfile函数;停止采样调用StopCPUProfile函数。
(2) StartCPUProfile函数,会设定 CPU 概要信息的采样频率,并在单独的 goroutine 中进行收集和输出。 CPU采样频率总是固定100赫兹的,经过大量实验证明最优。
(3) StopCPUProfile函数,会采样频率设为0,采样工作停止。
6. 怎样设定内存概要信息的采样频率?
(1) 为 runtime.MemProfileRate变量赋值即可。 含义是,每分配多少个字节,就对堆内存的使用情况进行一次采样。缺省值是512 KB。
(2) 越早设定越好,避免运行时造成不良影响。最好只在main函数的开始处设定一次。
(3) 想获取内存概要信息的时候, 调用WriteHeapProfile函数。但它并非实时数据,是在最近一次的内存垃圾收集工作完成时产生的。
(4) 可以调用runtime.ReadMemStats函数,获得实时数据。不过该函数会引起 Go 语言调度器的短暂停顿。
7. 怎样获取到阻塞概要信息?
(1) 调用runtime包中的SetBlockProfileRate函数,可对阻塞概要信息的采样频率进行设定。
(2) 函数有一个名叫rate的参数,int类型。含义是,只要发现一个阻塞事件的持续时间达到了多少纳秒,就对其进行采样。
(3) 在runtime包中,有一个名叫blockprofilerate的私有变量,uint64类型。含义是,只要发现一个阻塞事件的持续时间跨越了多少个 CPU 时钟周期,就对其进行采样。和(2)的区别仅仅在于单位不同。
(4) 缺省值是0,所以默认情况下并不会记录任何阻塞事件。
(5) 需要获取阻塞概要信息的时候,做两步操作:
调用runtime/pprof包中的 Lookup函数 并传入参数值"block",得到*runtime/pprof.Profile类型的值
调用Profile值的 WriteTo方法,把概要信息写进指定的写入器中。
8. runtime/pprof.Lookup函数的正确调用方式是什么?
(1) Lookup函数的功能是,提供 给定的名称 相对应的概要信息。
(2) 给定的名称 包括:
goroutine:收集当前正在使用的所有 goroutine 堆栈跟踪信息。会引起 Go 调度器的短暂停顿。
heap:收集与堆内存的分配和释放有关的采样信息。也就是前面的内存概要信息。
allocs:与heap大致一样,但 allocs 收集到的是已分配空间(已分配不管有否释放),heap是在用空间(已分配未释放)。
threadcreate:收集堆栈跟踪信息,描绘出代码调用链。
block:在代码同步竞争中被阻塞的代码的堆栈跟踪信息。就是前面的阻塞概要信息。
mutex:在代码同步竞争中,获得过执行的代码的堆栈跟踪信息。
9. 如何为基于 HTTP 协议的网络服务添加性能分析接口?
(1) 在程序中导入net/http/pprof代码包。
import _ "net/http/pprof"
(2) 启动网络服务并开始监听。
log.Println(http.ListenAndServe("localhost:8082", nil))
(3) 在浏览器中访问 http://localhost:8082/debug/pprof 看到一个简约的网页
(4) 在/debug/pprof/ 这个URL路径下还有很多可用的子路径,包括:profile,trace,allocs、block、goroutine、heap、mutex、threadcreate。
(5) 返回二进制内容时,需要使用 go tool pprof 工具去查看。
go tool pprof http://localhost:6060/debug/pprof/profile?seconds=60