Go语言学习笔记(七)——标准库
文章目录
- 八、ioutil
- NopCloser
- ReadAll
- ReadDir
- ReadFile
- WriteFile
- TempDir
- TempFile
- 九、bufio
- bufio包原理
- type Reader
- type Writer
- type ReadWriter
- type SplitFunc
- type Scanner
- 十、builtin
- append
- len
- print、println
- panic
- new和make
- 十一、json
- 核心函数
- Marshal
- Unmarshal
- 核心结构
- Decoder
- Encoder
- 十二、sort
- 排序接口
- 相关函数汇总
- 数据集合排序
- Sort排序方法
- sSorted是否已排序方法
- Reverse逆序排序方法
- Search查询位置方法
- sort包支持的内部数据类型
- `[]int`排序
- `[]float64`排序
- `[]string`排序
- 复杂结构: `[][]int`排序
- 复杂结构体排序
- 复杂结构体:`[]struct`排序
- 十三、math
- 常量
- 常用函数
- IsNaN函数
- Ceil函数
- Floor函数
- Trunc函数
- Abs函数
- Max函数
- Min函数
- Dim函数
- Mod函数
- Sqrt函数
- Cbrt函数
- Hypot函数
- Pow函数
- Sin函数
- Cos函数
- Tan函数
- Log函数
- Log2函数
- Log10函数
- Signbit函数
- 随机数math/rand
- 十四、flag
- os.Args
- flag包使用
- flag参数类型
- flag.Type()
- flag.TypeVar()
- flag.Parse()
- 其他函数
- 十五、runtime
- 环境变量
- runtime.Gosched
- runtime.Goexit
- runtime.GOMAXPROCS
- runtime.NumCPU
- runtime.GOOS
- 十六、context
- context类型
- 空 context
- CancelFunc 类型原型
- Context 类型原型
- Background() 方法原型
- TODO() 方法原型
- WithValue() 方法原型
- context函数
- WithCancel() 函数原型
- WithDeadline() 函数原型
- WithTimeout() 函数原型
八、ioutil
ioutil包封装了一些I/O实用程序函数。
函数 | 说明 |
---|---|
ReadAll | 读取数据,返回读到的字节slice |
ReadDir | 读取一个目录,返回目录入口数组[]os.FileInfo |
ReadFile | 读取一个文件,返回读到的文件内容字节slice |
WriteFile | 根据文件路径,写入字节slice |
TempDir | 在一个目录中创建指定前缀名的临时目录,返回新临时目录的路径 |
TempFile | 在一个目录中创建指定前缀名的临时文件,返回os.File |
NopCloser | 用一个无操作的Close方法包装文件并返回一个ReadCloser接口。 |
NopCloser
func NopCloser(r io.Reader) io.ReadCloser
就是将一个不带 Close
的 Reader
封装成 ReadCloser
。
ReadAll
func ReadAll(r io.Reader) ([]byte, error)
从r读取数据直到EOF或遇到error,返回读取的数据和可能的错误。成功的调用返回的err为nil而非EOF。因为本函数定义为读取r直到EOF,它不会将读取返回的EOF视为应报告的错误。
ReadDir
func ReadDir(dirname string) ([]fs.FileInfo, error)
读取dirname目录内的所有文件信息,注意此序列有序。
ReadFile
func ReadFile(filename string) ([]byte, error)
从filename指定的文件中读取数据并返回文件的内容。对err的判断和ReadAll一样。
WriteFile
func WriteFile(filename string, data []byte, perm fs.FileMode) error
函数向filename指定的文件中写入数据。如果文件不存在将按给出的perm权限创建文件,否则在写入数据之前清空文件
TempDir
func TempDir(dir, pattern string) (name string, err error)
在dir目录里创建一个新的、使用prefix作为前缀的临时文件夹,并返回文件夹的路径。如果dir是空字符串,TempDir使用默认用于临时文件的目录(参见os.TempDir函数)。 不同程序同时调用该函数会创建不同的临时目录,调用本函数的程序有责任在不需要临时文件夹时摧毁它。
TempFile
func TempFile(dir, pattern string) (f *os.File, err error)
在dir目录下创建一个新的、使用prefix为前缀的临时文件,以读写模式打开该文件并返回os.File指针。如果dir是空字符串,TempFile使用默认用于临时文件的目录(参见os.TempDir函数)。责任与TempDir相同。
九、bufio
bufio包实现了有缓冲的I/O。它包装一个io.Reader或io.Writer接口对象,创建另一个也实现了该接口,且同时还提供了缓冲和一些文本I/O的帮助函数的对象。
bufio包原理
io操作本身的效率并不低,低的是频繁的访问本地磁盘的文件。所以bufio就提供了缓冲区(分配一块内存),读和写都先在缓冲区中,最后再读写文件,来降低访问本地磁盘的次数,从而提高效率。
简单的说就是,把文件读取进缓冲(内存)之后再读取的时候就可以避免文件系统的io 从而提高速度。同理,在进行写操作时,先把文件写入缓冲(内存),然后由缓冲写入文件系统。看完以上解释有人可能会表示困惑了,直接把 内容->文件 和 内容->缓冲->文件相比, 缓冲区好像没有起到作用嘛。其实缓冲区的设计是为了存储多次的写入,最后一口气把缓冲区内容写入文件。
bufio 封装了io.Reader或io.Writer接口对象,并创建另一个也实现了该接口的对象。
type Reader
bufio.Reader
是bufio中对io.Reader 的封装
type Reader struct {
buf []byte
rd io.Reader // reader provided by the client
r, w int // buf read and write positions
err error
lastByte int // last byte read for UnreadByte; -1 means invalid
lastRuneSize int // size of last rune read for UnreadRune; -1 means invalid
}
bufio.Read(p []byte)
相当于读取大小len的内容,思路如下:
- 当缓存区有内容的时,将缓存区内容全部填入p并清空缓存区
- 当缓存区没有内容的时候且len>len(buf),即要读取的内容比缓存区还要大,直接去文件读取即可
- 当缓存区没有内容的时候且len<len(buf),即要读取的内容比缓存区小,缓存区从文件读取内容充满缓存区,并将p填满(此时缓存区有剩余内容)
- 以后再次读取时缓存区有内容,将缓存区内容全部填入p并清空缓存区(此时和情况1一样)
- reader内部通过维护一个r, w 即读入和写入的位置索引来判断是否缓存区内容被全部读出。
func NewReaderSize
func NewReaderSize(rd io.Reader, size int) *Reader
NewReaderSize 将 rd 封装成一个带缓存的 bufio.Reader 对象,缓存大小由 size 指定(如果小于 16 则会被设置为 16)。
如果 rd 的基类型就是有足够缓存的 bufio.Reader 类型,则直接将rd 转换为基类型返回。
func NewReader
func NewReader(rd io.Reader) *Reader
NewReader相当于NewReaderSize(rd, 4096)
func ( * Reader) Reset(r.io.Reader)
func (b *Reader) Reset(r io.Reader)
Reset丢弃缓冲中的数据,清除任何错误,将b重设为其下层从r读取数据。
func ( * Reader) Read
func (b *Reader) Read(p []byte) (n int, err error)
Read读取数据写入p。本方法返回写入p的字节数。本方法一次调用最多会调用下层Reader接口一次Read方法,因此返回值n可能小于len。读取到达结尾时,返回值n将为0而err将为io.EOF。
func (b * Reader) Peek
func (b *Reader) Peek(n int) ([]byte, error)
Peek 返回缓存的一个切片,该切片引用缓存中前 n 个字节的数据,该操作不会将数据读出,只是引用,引用的数据在下一次读取操作之前是有效的。如果切片长度小于 n,则返回一个错误信息说明原因。
如果 n 大于缓存的总大小,则返回 ErrBufferFull。
func ( * Reader) ReadByte
func (b *Reader) ReadByte() (c byte, err error)
ReadByte读取并返回一个字节。如果没有可用的数据,会返回错误。
func ( * Reader) UnreadByte
func (b *Reader) UnreadByte() error
UnreadByte吐出最近一次读取操作读取的最后一个字节。(只能吐出最后一个,多次调用会出问题)
func ( * Reader) ReadRune
func (b *Reader) ReadRune() (r rune, size int, err error)
ReadRune读取一个utf-8编码的unicode码值,返回该码值、其编码长度和可能的错误。如果utf-8编码非法,读取位置只移动1字节,返回U+FFFD,返回值size为1而err为nil。如果没有可用的数据,会返回错误。
func ( * Reader) UnreadRune
func (b *Reader) UnreadRune() error
UnreadRune吐出最后一次ReadRune调用读取的unicode码值。如果最近一次读取不是调用的ReadRune,会返回错误。(从这点看,UnreadRune比unreadByte严格很多)
func ( *Reader) ReadLine
func (b *Reader) ReadLine() (line []byte, isPrefix bool, err error)
ReadLine尝试返回一行数据,不包括行尾标志的字节。如果行太长超过了缓冲,返回值isPrefix会被设为true,并返回行的前面一部分。该行剩下的部分将在之后的调用中返回。返回值isPrefix会在返回该行最后一个片段时才设为false。返回切片是缓冲的子切片,只在下一次读取操作之前有效。ReadLine要么返回一个非nil的line,要么返回一个非nil的err,两个返回值至少一个非nil。
返回的文本不包含行尾的标志字节(“\r\n”或"\n")。如果输入流结束时没有行尾标志字节,方法不会出错,也不会指出这一情况。在调用ReadLine之后调用UnreadByte会总是吐出最后一个读取的字节(很可能是该行的行尾标志字节),即使该字节不是ReadLine返回值的一部分。
func ( * Reader) ReadSlice
func (b *Reader) ReadSlice(delim byte) (line []byte, err error)
ReadSlice读取直到第一次遇到delim字节,返回缓冲里的包含已读取的数据和delim字节的切片。该返回值只在下一次读取操作之前合法。如果ReadSlice放在在读取到delim之前遇到了错误,它会返回在错误之前读取的数据在缓冲中的切片以及该错误(一般是io.EOF)。如果在读取delim之前缓冲就写满了,ReadSlice失败并返回ErrBufferFull。因为ReadSlice的返回值会被下一次I/O操作重写,调用者应尽量使用ReadBytes或ReadString替代本法功法。当且仅当ReadBytes方法返回的切片不以delim结尾时,会返回一次非nil的错误。
func ( * Reader) ReadString
func (b *Reader) ReadString(delim byte) (line string, err error)
ReadString读取直到第一次遇到delim字节,返回一个包含已读取的数据和delim字节的字符串。如果ReadString方法在读取到delim之前遇到了错误,它会返回在错误之前读取的数据以及该错误(一般是io.EOF)。当且仅当ReadString方法返回的切片不以delim结尾时,会返回一个非nil的错误。
func ( * Reader) WriteTo
func (b *Reader) WriteTo(w io.Writer) (n int64, err error)
WriteTo方法实现了io.WriterTo接口。
type Writer
bufio.Writer
是bufio中对io.Writer 的封装
type Writer struct {
err error
buf []byte
n int
wr io.Writer
}
bufio.Write(p []byte)
的思路如下
- 判断buf中可用容量是否可以放下 p
- 如果能放下,直接把p拼接到buf后面,即把内容放到缓冲区
- 如果缓冲区的可用容量不足以放下,且此时缓冲区是空的,直接把p写入文件即可
- 如果缓冲区的可用容量不足以放下,且此时缓冲区有内容,则用p把缓冲区填满,把缓冲区所有内容写入文件,并清空缓冲区
- 判断p的剩余内容大小能否放到缓冲区,如果能放下(此时和步骤1情况一样)则把内容放到缓冲区
- 如果p的剩余内容依旧大于缓冲区,(注意此时缓冲区是空的,情况和步骤3一样)则把p的剩余内容直接写入文件
func NewWriter
func NewWriter(w io.Writer) *Writer
NewWriter创建一个具有默认大小缓冲、写入w的*Writer。NewWriter相当于NewWriterSize(wr, 4096)
func NewWriterSize
func NewWriterSize(w io.Writer, size int) *Writer
NewWriterSize创建一个具有最少有size尺寸的缓冲、写入w的Writer。如果参数w已经是一个具有足够大缓冲的Writer类型值,会返回w。
func ( * Writer) Reset
func (b *Writer) Reset(w io.Writer)
Reset丢弃缓冲中的数据,清除任何错误,将b重设为将其输出写入w。
func ( * Writer) Buffered
func (b *Writer) Buffered() int
Buffered返回缓冲中已使用的字节数。
func ( * Writer) Available
func (b *Writer) Available() int
Available返回缓冲中还有多少字节未使用。
func ( * Writer) Write
func (b *Writer) Write(p []byte) (nn int, err error)
Write将p的内容写入缓冲。返回写入的字节数。如果返回值nn < len,还会返回一个错误说明原因。
func ( * Writer) WriteString
func (b *Writer) WriteString(s string) (int, error)
WriteString写入一个字符串。返回写入的字节数。如果返回值m < len(s),还会返回一个错误说明原因。
func ( * Writer) WriteByte
func (b *Writer) WriteByte(c byte) error
WriteByte写入单个字节。
func ( * Writer) WriterRune
func (b *Writer) WriteRune(r rune) (size int, err error)
WriteRune写入一个unicode码值(的utf-8编码),返回写入的字节数和可能的错误。
func ( * Writer) Flush
func (b *Writer) Flush() error
Flush方法将缓冲中的数据写入下层的io.Writer接口。
func ( * Writer) ReadFrom
func (b *Writer) ReadFrom(r io.Reader) (n int64, err error)
ReadFrom实现了io.ReaderFrom接口。
type ReadWriter
ReadWriter类型保管了指向Reader和Writer类型的指针,(因此)实现了io.ReadWriter接口。
type ReadWriter struct {
*Reader
*Writer
}
func NewReadWriter
func NewReadWriter(r *Reader, w *Writer) *ReadWriter
NewReadWriter申请创建一个新的、将读写操作分派给r和w的ReadWriter。
type SplitFunc
type SplitFunc func(data []byte, atEOF bool) (advance int, token []byte, err error)
SplitFunc类型代表用于对输出作词法分析的分割函数。
参数data是尚未处理的数据的一个开始部分的切片,参数atEOF表示是否Reader接口不能提供更多的数据。返回值是解析位置前进的字节数,将要返回给调用者的token切片,以及可能遇到的错误。如果数据不足以(保证)生成一个完整的token,例如需要一整行数据但data里没有换行符,SplitFunc可以返回(0, nil, nil)来告诉Scanner读取更多的数据写入切片然后用从同一位置起始、长度更长的切片再试一次(调用SplitFunc类型函数)。
如果返回值err非nil,扫描将终止并将该错误返回给Scanner的调用者。
除非atEOF为真,永远不会使用空切片data调用SplitFunc类型函数。然而,如果atEOF为真,data却可能是非空的、且包含着未处理的文本。
SplitFunc的作用很简单,从data中找出你感兴趣的数据,然后返回并告诉调用者,data中有多少数据你已经处理过了。
type Scanner
type Scanner struct {
r io.Reader // The reader provided by the client.
split SplitFunc // The function to split the token.
maxTokenSize int // Maximum size of a token; modified by tests.
token []byte // Last token returned by split.
buf []byte // Buffer used an argument to split.
start int // First non-processed byte in buf.
end int // End of data in buf.
err error // Sticky error.
}
Scanner类型提供了方便的读取数据的接口,如从换行符分隔的文本里读取每一行。成功调用的Scan方法会逐步提供文件的token,跳过token之间的字节。token由SplitFunc类型的分割函数指定;默认的分割函数会将输入分割为多个行,并去掉行尾的换行标志。本包预定义的分割函数可以将文件分割为行、字节、unicode码值、空白分隔的word。调用者可以定制自己的分割函数。扫描会在抵达输入流结尾、遇到的第一个I/O错误、token过大不能保存进缓冲时,不可恢复的停止。当扫描停止后,当前读取位置可能会在最后一个获得的token后面。需要更多对错误管理的控制或token很大,或必须从reader连续扫描的程序,应使用buffio.Reader代替。
func ScanBytes
func ScanBytes(data []byte, atEOF bool) (advance int, token []byte, err error)
ScanBytes是用于Scanner类型的分割函数(符合SplitFunc),本函数会将每个字节作为一个token返回。
func ScanRunes
func ScanRunes(data []byte, atEOF bool) (advance int, token []byte, err error)
ScanRunes是用于Scanner类型的分割函数(符合SplitFunc),本函数将每个utf-8编码的unicode码值作为一个token返回。本函数返回的rune序列和range一个字符串的输出rune序列相同。错误的utf-8编码会翻译为U+FFFD=“\xef\xbf\xbd”,但只会消耗一个字节。调用者无法区分正确编码的rune和错误编码的rune。
func ScanWords
func ScanWords(data []byte, atEOF bool) (advance int, token []byte, err error)
ScanRunes是用于Scanner类型的分割函数(符合SplitFunc),本函数会将空白分隔的片段(去掉前后空白后)作为一个token返回。本函数永远不会返回空字符串。用来找出data中的单行数据并返回(包括空行)
func ScanLines
func ScanLines(data []byte, atEOF bool) (advance int, token []byte, err error)
ScanLines是用于Scanner类型的分割函数(符合SplitFunc),本函数会将每一行文本去掉末尾的换行标记作为一个token返回。返回的行可以是空字符串。换行标记为一个可选的回车后跟一个必选的换行符。最后一行即使没有换行符也会作为一个token返回。
func NewScanner
func NewScanner(r io.Reader) *Scanner
NewScanner创建并返回一个从r读取数据的Scanner,默认的分割函数时ScanLines。
func ( * Scanner) Split
func (s *Scanner) Split(split SplitFunc)
Split设置该Scanner的分割函数。本方法必须在Scan之前调用。
func ( * Scanner) Scan
func (s *Scanner) Scan() bool
Scan方法获取当前位置的token(该token可以通过Bytes或Text方法获得),并让Scanner的扫描位置移动到下一个token。当扫描因为抵达输入流结尾或者遇到错误而停止时,本方法会返回false。在Scan方法返回false后,Err方法将返回扫描时遇到的任何错误;除非是io.EOF,此时Err会返回nil。
func ( * Scanner) Bytes
func (s *Scanner) Bytes() []byte
Bytes方法返回最近一次Scan调用生成的token。底层数组指向的数据可能会被下一次Scan的调用重写。
func ( * Scanner) Text
func (s *Scanner) Text() string
Bytes方法返回最近一次Scan调用生成的token,会申请创建一个字符串保存token并返回该字符串。
func ( * Scanner) Err
func (s *Scanner) Err() error
Err返回Scanner遇到的第一个非EOF的错误。
十、builtin
builtin包提供了一些类型声明、变量和常量声明,还有一些便利函数,这个包不需要导入,这些变量和函数就可以直接使用
append
func append(slice []Type, elems ...Type) []Type
slice = append(slice, elem1, elem2)
直接在slice后面添加单个元素,添加元素类型可以和slice相同,也可以不同
slice = append(slice, anotherSlice)
直接将另外一个slice添加到slice后面,但其本质还是将anotherSlice中的元素一个一个添加到slice中,和上一种方式类似
len
返回,数组、切片、字符串、通道的长度
print、println
打印输出到控制台
panic
抛出一个panic异常
new和make
new和make区别:
- make只能用来分配及初始化类型为
slice
,map
,chan
的数据;new可以分配任意类型的数据; - new分配返回的是指针,即类型*T;make返回引用,即T;
- new分配的空间被清零,make分配后,会进行初始化。
内建函数make(T, args)与new(T)的用途不一样。它只用来创建slice、map和channel,并且返回一个初始化的(而不是置零),类型为T的值(而不是*T)。之所以有所不同,是因为这三个类型的背后引用了使用前必须初始化的数据结构。例如,slice是一个三元描述符,包含一个指向数据(在数组中)的指针,长度,以及容量,在这些项被初始化之前,slice都是nil的。对于slice,map和channel,make初始化这些内部数据结构,并准备好可用的值。
十一、json
核心函数
Marshal
func Marshal(v any) ([]byte, error)
将struct编码成json,可以接收任意类型
- 布尔型转换为 JSON 后仍是布尔型,如true -> true
- 浮点型和整数型转换后为JSON里面的常规数字,如 1.23 -> 1.23
- 字符串将以UTF-8编码转化输出为Unicode字符集的字符串,特殊字符比如<将会被转义为\u003c
- 数组和切片被转换为JSON 里面的数组,[]byte类会被转换为base64编码后的字符串,slice的零值被转换为null
- 结构体会转化为JSON对象,并且只有结构体里边以大写字母开头的可被导出的字段才会被转化输出,而这些可导出的字段会作为JSON对象的字符串索引
- 转化一个map 类型的数据结构时,该数据的类型必须是 map[string]T(T 可以是encoding/json 包支持的任意数据类型)
Unmarshal
func Unmarshal(data []byte, v any) error
将json转码为struct结构体
这个函数会把传入的 data 作为一个JSON来进行解析,解析后的数据存储在参数 v 中。这个参数 v 也是任意类型的参数(但一定是一个类型的指针),原因是我们在是以此函数进行JSON 解析的时候,这个函数不知道这个传入参数的具体类型,所以它需要接收所有的类型。
核心结构
Decoder
type Decoder struct {
// contains filtered or unexported fields
}
从输入流读取并解析json,应用于io流Reader Writer可以扩展到http websocket等场景
Encoder
type Encoder struct {
// contains filtered or unexported fields
}
写json到输出流,应用于io流Reader Writer可以扩展到http websocket等场景
十二、sort
sort包提供了排序切片和用户自定义数据集以及相关功能的函数。
sort包主要针对[]int
、[]float64
、[]string
、以及其他自定义切片的排序。
主要包括:
- 对基本数据类型切片的排序支持。
- 基本数据元素查找。
- 判断基本数据类型切片是否已经排好序。
- 对排好序的数据集合逆序
排序接口
type Interface interface {
Len() int // 获取数据集合元素个数
Less(i, j int) bool // 如果i索引的数据小于j索引的数据,返回true,Swap(),即数据升序排序,false 不调用swap。
Swap(i, j int) // 交换i和j索引的两个元素的位置
}
相关函数汇总
func Ints(a []int)
func IntsAreSorted(a []int) bool
func SearchInts(a []int, x int) int
func Float64s(a []float64)
func Float64sAreSorted(a []float64) bool
func SearchFloat64s(a []float64, x float64) int
func SearchFloat64s(a []float64, x float64) bool
func Strings(a []string)
func StringsAreSorted(a []string) bool
func SearchStrings(a []string, x string) int
func Sort(data Interface)
func Stable(data Interface)
func Reverse(data Interface) Interface
func IsSorted(data Interface) bool
func Search(n int, f func(int) bool) int
数据集合排序
Sort排序方法
对数据集合(包括自定义数据类型的集合)排序,需要实现sort.Interface
接口的三个方法,即:
type Interface interface {
Len() int // 获取数据集合元素个数
Less(i, j int) bool // 如果i索引的数据小于j索引的数据,返回true,Swap(),即数据升序排序。
Swap(i, j int) // 交换i和j索引的两个元素的位置
}
实现了这三个方法后,即可调用该包的Sort()方法进行排序。 Sort()方法定义如下:
func Sort(data Interface)
Sort()方法唯一的参数就是待排序的数据集合。
sSorted是否已排序方法
sort包提供了IsSorted方法,可以判断数据集合是否已经排好顺序。IsSorted方法的内部实现依赖于我们自己实现的Len()和Less()方法:
func IsSorted(data Interface) bool {
n := data.Len()
for i := n - 1; i > 0; i-- {
if data.Less(i, i-1) {
return false
}
}
return true
}
Reverse逆序排序方法
sort包提供了Reverse()方法,将数据按Less()定义的排序方式逆序排序,而不必修改Less()代码。方法定义如下:
func Reverse(data Interface) Interface
Search查询位置方法
sort包提供Search方法查询位置,其实现如下:
func Search(n int, f func(int) bool) int
Search()方法会使用“二分查找”算法,来搜索某指定切片[0:n],并返回能够使f(i)=true的最小i(0<=i<n)值,并且会假定:如果f(i)=true,则f(i+1)=true。即对于切片[0:n],i之前的切片元素会使f()函数返回false,i及i之后的元素会使f()函数返回true。但是,当在切片中无法找到时f(i)=true的i时(此时切片元素都不能使f()函数返回true),Search() 方法会返回n。
sort包支持的内部数据类型
[]int
排序
sort包定义了一个IntSlice类型,并且实现了sort.Interface接口:
type IntSlice []int
func (p IntSlice) Len() int { return len(p) }
func (p IntSlice) Less(i, j int) bool { return p[i] < p[j] }
func (p IntSlice) Swap(i, j int) { p[i], p[j] = p[j], p[i] }
//IntSlice类型定义了Sort()方法,包装了sort.Sort()函数
func (p IntSlice) Sort() { Sort(p) }
//IntSlice类型定义了Search()方法,包装了SearchInts()函数
func (p IntSlice) Search(x int) int { return SearchInts(p, x) }
并且,提供的sort.Ints()方法使用了该IntSlice类型:
func Ints(a []int) { Sort(IntSlice(a)) }
所以,对[]int切片升序排序,经常使用
sort.Ints()
,而不是直接使用IntSlice类型。注意,SearchInts()的使用条件为:切片a已经升序排序。
[]float64
排序
实现与Ints类似
内部实现:
type Float64Slice []float64
func (p Float64Slice) Len() int { return len(p) }
func (p Float64Slice) Less(i, j int) bool { return p[i] < p[j] || isNaN(p[i]) && !isNaN(p[j]) }
func (p Float64Slice) Swap(i, j int) { p[i], p[j] = p[j], p[i] }
func (p Float64Slice) Sort() { Sort(p) }
func (p Float64Slice) Search(x float64) int { return SearchFloat64s(p, x) }
与Sort()、IsSorted()、Search()相对应的三个方法:
func Float64s(a []float64)
func Float64sAreSorted(a []float64) bool
func SearchFloat64s(a []float64, x float64) int
其他如Search()方法与Ints类似,不再赘述。
需要注意:在上面Float64Slice类型定义的Less方法中,有一个内部函数isNaN()。 isNaN()与math包中IsNaN()实现完全相同,sort包之所以不使用math.IsNaN(),完全是基于包依赖性的考虑。应当看到,sort包的实现不依赖于其他任何包。
[]string
排序
两个string对象之间的大小比较是基于“字典序”的。
实现与Ints类似
内部实现:
type StringSlice []string
func (p StringSlice) Len() int { return len(p) }
func (p StringSlice) Less(i, j int) bool { return p[i] < p[j] }
func (p StringSlice) Swap(i, j int) { p[i], p[j] = p[j], p[i] }
func (p StringSlice) Sort() { Sort(p) }
func (p StringSlice) Search(x string) int { return SearchStrings(p, x) }
与Sort()、IsSorted()、Search()相对应的三个方法:
func Strings(a []string)
func StringsAreSorted(a []string) bool
func SearchStrings(a []string, x string) int
复杂结构: [][]int
排序
package main
import (
"fmt"
"sort"
)
type testSlice [][]int
func (l testSlice) Len() int { return len(l) }
func (l testSlice) Swap(i, j int) { l[i], l[j] = l[j], l[i] }
func (l testSlice) Less(i, j int) bool { return l[i][1] < l[j][1] }
func main() {
ls := testSlice{
{1, 4},
{9, 3},
{7, 5},
}
fmt.Println(ls)
sort.Sort(ls)
fmt.Println(ls)
}
复杂结构体排序
package main
import (
"fmt"
"sort"
)
type testSlice []map[string]float64
func (l testSlice) Len() int { return len(l) }
func (l testSlice) Swap(i, j int) { l[i], l[j] = l[j], l[i] }
func (l testSlice) Less(i, j int) bool { return l[i]["a"] < l[j]["a"] } // 按照"a"对应的值排序
func main() {
ls := testSlice{
{"a": 4, "b": 12},
{"a": 3, "b": 11},
{"a": 5, "b": 10},
}
fmt.Println(ls)
sort.Sort(ls)
fmt.Println(ls)
}
复杂结构体:[]struct
排序
package main
import (
"fmt"
"sort"
)
type People struct {
Name string
Age int
}
type testSlice []People
func (l testSlice) Len() int { return len(l) }
func (l testSlice) Swap(i, j int) { l[i], l[j] = l[j], l[i] }
func (l testSlice) Less(i, j int) bool { return l[i].Age < l[j].Age }
func main() {
ls := testSlice{
{Name: "n1", Age: 12},
{Name: "n2", Age: 11},
{Name: "n3", Age: 10},
}
fmt.Println(ls)
sort.Sort(ls)
fmt.Println(ls)
}
十三、math
常量
fmt.Printf("Float64的最大值: %.f\n", math.MaxFloat64)
fmt.Printf("Float64最小值: %.f\n", math.SmallestNonzeroFloat64)
fmt.Printf("Float32最大值: %.f\n", math.MaxFloat32)
fmt.Printf("Float32最小值: %.f\n", math.SmallestNonzeroFloat32)
fmt.Printf("Int8最大值: %d\n", math.MaxInt8)
fmt.Printf("Int8最小值: %d\n", math.MinInt8)
fmt.Printf("Uint8最大值: %d\n", math.MaxUint8)
fmt.Printf("Int16最大值: %d\n", math.MaxInt16)
fmt.Printf("Int16最小值: %d\n", math.MinInt16)
fmt.Printf("Uint16最大值: %d\n", math.MaxUint16)
fmt.Printf("Int32最大值: %d\n", math.MaxInt32)
fmt.Printf("Int32最小值: %d\n", math.MinInt32)
fmt.Printf("Uint32最大值: %d\n", math.MaxUint32)
fmt.Printf("Int64最大值: %d\n", math.MaxInt64)
fmt.Printf("Int64最小值: %d\n", math.MinInt64)
fmt.Printf("圆周率默认值: %v\n", math.Pi)
常量如下:
Float64的最大值: 179769313486231570814527423731704356798070567525844996598917476803157260780028538760589558632766878171540458953514382464234321326889464182768467546703537516986049910576551282076245490090389328944075868508455133942304583236903222948165808559332123348274797826204144723168738177180919299881250404026184124858368
Float64最小值: 0
Float32最大值: 340282346638528859811704183484516925440
Float32最小值: 0
Int8最大值: 127
Int8最小值: -128
Uint8最大值: 255
Int16最大值: 32767
Int16最小值: -32768
Uint16最大值: 65535
Int32最大值: 2147483647
Int32最小值: -2147483648
Uint32最大值: 4294967295
Int64最大值: 9223372036854775807
Int64最小值: -9223372036854775808
圆周率默认值: 3.141592653589793
常用函数
IsNaN函数
func IsNaN(f float64) (is bool)
报告f是否表示一个NaN(Not A Number)值,是数值返回一个false,不是数值则返回一个true。
func testIsNaN() {
fmt.Println(math.IsNaN(12321.321321)) //false
}
Ceil函数
func Ceil(x float64) float64
返回一个不小于x的最小整数,简单来说就是向上取整
func testCeil() {
fmt.Println(math.Ceil(1.13456)) //2
}
Floor函数
func Floor(x float64) float64
返回一个不大于x的最小整数,简单来说就是向下取整
func testFloor() {
fmt.Println(math.Floor(2.9999)) //2
}
Trunc函数
func Trunc(x float64) float64
返回x整数部分,与Floor一样
func testTrunc() {
fmt.Println(math.Trunc(2.9999)) //2
}
Abs函数
func Abs(x float64) float64
返回x的绝对值
func testAbs() {
fmt.Println(math.Abs(2.999312323132141665374)) //2.9993123231321417
fmt.Println(math.Abs(2.999312323132141465374)) //2.9993123231321412
}
Max函数
func Max(x, y float64) float64
返回x和y中最大值
func testMax() {
fmt.Println(math.Max(1000,200)) //1000
}
Min函数
func Min(x, y float64) float64
返回x和y中最小值
func testMin() {
fmt.Println(math.Min(1000,200)) //200
}
Dim函数
func Dim(x, y float64) float64
函数返回x-y和0中的最大值
func testDim() {
fmt.Println(math.Dim(1000,2000)) //0
fmt.Println(math.Dim(1000,200)) //800
}
Mod函数
func Mod(x, y float64) float64
取余运算,可以理解为 x-Trunc(x/y)*y,结果的正负号和x相同
func testMod() {
fmt.Println(math.Mod(123,0)) //NaN
fmt.Println(math.Mod(123,10)) //3
}
Sqrt函数
func Sqrt(x float64) float64
返回x的二次方根,平方根
func testSqrt() {
fmt.Println(math.Sqrt(144)) //12
}
Cbrt函数
func Cbrt(x float64) float64
返回x的三次方根,立方根
func testCbrt() {
fmt.Println(math.Cbrt(1728)) //12
}
Hypot函数
func Hypot(p, q float64) float64
返回Sqrt(p * p + q * q),注意要避免不必要的溢出或下溢。
func testHypot() {
fmt.Println(math.Hypot(12,12)) //16.970562748477143
}
Pow函数
func Pow(x, y float64) float64
求幂,x的y次方
func testPow() {
fmt.Println(math.Pow(2,3)) //8
}
Sin函数
func Sin(x float64) float64
求正弦
func testSin() {
fmt.Println(math.Sin(12)) //-0.5365729180004349
}
Cos函数
func Cos(x float64) float64
求余弦
func testCos() {
fmt.Println(math.Cos(12)) //0.8438539587324921
}
Tan函数
func Tan(x float64) float64
求正切
func testTan() {
fmt.Println(math.Tan(12)) //-0.6358599286615807
}
Log函数
func Log(x float64) float64
求自然对数
func testLog() {
fmt.Println(math.Log(2)) //0.6931471805599453
}
Log2函数
func Log2(x float64) float64
求2为底的对数
func testLog2() {
fmt.Println(math.Log2(128)) //7
}
Log10函数
func Log10(x float64) float64
求10为底的对数
func testLog10() {
fmt.Println(math.Log10(10000)) //4
}
Signbit函数
func Signbit(x float64) bool
如果x是一个负数或者负零,返回true
func testSignbit() {
fmt.Println(math.Signbit(10000)) //false
fmt.Println(math.Signbit(-200)) //true
}
随机数math/rand
math/rand包是go提供用来产生各种各样随机数的包,注意:rand生成的数值虽然说是随机数,但它其实是伪随机数。
rand实现的几个方法:
函数 | 说明 |
---|---|
func (r *Rand) Int() int | 返回一个非负的伪随机int值。 |
func (r *Rand) Int31() int32 | 返回一个int32类型的非负的31位伪随机数。 |
func (r *Rand) Intn(n int) int | 返回一个取值范围在[0,n)的伪随机int值,如果n<=0会panic。 |
func Int63() int64 | 返回一个int64类型的非负的63位伪随机数。 |
func Uint32() uint32 | 返回一个uint32类型的非负的32位伪随机数。 |
func Uint64() uint64 | 返回一个uint64类型的非负的32位伪随机数。 |
func Int31n(n int32) int32 | 返回一个取值范围在[0,n)的伪随机int32值,如果n<=0会panic。 |
func Int63n(n int64) int64 | 返回一个取值范围在[0, n)的伪随机int64值,如果n<=0会panic。 |
func Float32() float32 | 返回一个取值范围在[0.0, 1.0)的伪随机float32值。 |
func Float64() float64 | 返回一个取值范围在[0.0, 1.0)的伪随机float64值。 |
func Perm(n int) []int | 返回一个有n个元素的,[0,n)范围内整数的伪随机的切片。 |
func Read(p []byte) (n int, err error) | 生成len§个伪随机数,伪随机数的范围为0-255;并将伪随机数存入p,返回len§和可能发生的错误。 |
func NewSource(seed int64) Source | 使用给定的种子创建一个伪随机资源。 |
func New(src Source) *Rand | 返回一个使用src随机源生成一个Rand。 |
简单使用:
package main
import (
"fmt"
"math/rand"
)
func main() {
// 直接调用rand的方法生成伪随机int值
fmt.Println(rand.Int()) // 5577006791947779410
fmt.Println(rand.Int31()) // 2019727887
fmt.Println(rand.Intn(5)) // 2
}
但是当把代码运行多次发现,结果都是一样的。不管怎么运行代码,产生的结果都是这三个数,结果不会变。这是因为我们还没有设置随机数种子的原因。
func (r *Rand) Seed(seed int64)
使用给定的seed来初始化生成器到一个确定的状态,这就是设置随机种子。
修改之后的代码:
package main
import (
"fmt"
"math/rand"
)
func main() {
// 直接调用rand的方法生成伪随机int值
rand.Seed(time.Now().Unix()) // 设置种子,我们以当前时间的秒;当然也可以用毫秒,微秒等
fmt.Println(rand.Int())
fmt.Println(rand.Int31())
fmt.Println(rand.Intn(5))
}
上面的代码,多次运行,就会发现,结果是不一样的了。
十四、flag
Go语言内置的flag包实现了命令行参数的解析,flag包使得开发命令行工具更为简单。
os.Args
如果你只是简单的想要获取命令行参数,可以像下面的代码示例一样使用os.Args来获取命令行参数。
func main() {
// 获取命令行参数
// os.Args:[]string
if len(os.Args) > 0 {
for i, v := range os.Args {
fmt.Println(i, v)
}
}
}
os.Args是一个存储命令行参数的字符串切片,它的第一个元素是执行文件的名称。
flag包使用
flag参数类型
flag包支持的命令行参数类型有bool、int、int64、uint、uint64、float float64、string、duration。
flag参数 | 有效值 |
---|---|
字符串flag | 合法字符串 |
整数flag | 1234、0664、0x1234等类型,也可以是负数。 |
浮点数flag | 合法浮点数 |
bool类型flag | 1, 0, t, f, T, F, true, false, TRUE, FALSE, True, False。 |
时间段flag | 任何合法的时间段字符串。如”300ms”、”-1.5h”、”2h45m”。 合法的单位有”ns”、”us” /“µs”、”ms”、”s”、”m”、”h”。 |
flag.Type()
基本格式如下:
flag.Type(flag名, 默认值, 帮助信息)*Type
例如我们要定义姓名、年龄、婚否三个命令行参数,我们可以按如下方式定义:
func main() {
// flag.Type() 的使用
name := flag.String("name", "张三", "姓名")
age := flag.Int("age", 18, "年龄")
married := flag.Bool("married", false, "婚否")
delay := flag.Duration("d", 0, "时间间隔")
flag.Parse()
fmt.Println(*name, *age, *married, *delay)
}
flag.TypeVar()
基本格式如下:
flag.TypeVar(Type指针, flag名, 默认值, 帮助信息)
例如我们要定义姓名、年龄、婚否三个命令行参数,我们可以按如下方式定义:
func main() {
var name string
var age uint
var married bool
var d time.Duration
flag.StringVar(&name, "name", "王五", "姓名")
flag.UintVar(&age, "age", 18, "年龄")
flag.BoolVar(&married, "m", false, "婚否")
flag.DurationVar(&d, "duration", 0, "时间间隔")
flag.Parse()
fmt.Println(name, age, married, d)
}
flag.Parse()
通过以上两种方法定义好命令行flag参数后,需要通过调用flag.Parse()来对命令行参数进行解析。
支持的命令行参数格式有以下几种:
-flag xxx
(使用空格,一个-符号)--flag xxx
(使用空格,两个-符号)-flag=xxx
(使用等号,一个-符号)--flag=xxx
(使用等号,两个-符号)
Flag解析在第一个非flag参数(单个”-“不是flag参数)之前停止,或者在终止符”–“之后停止。
其他函数
- flag.Args() 返回命令行参数后的其他参数,以[]string类型
- flag.NArg() 返回命令行参数后的其他参数个数
- flag.NFlag() 返回使用的命令行参数个数
十五、runtime
runtime包提供和go运行时环境的互操作,如控制goroutine的函数。它也包括用于reflect包的低层次类型信息。
环境变量
-
环境变量
GOGC
设置最初的垃圾收集目标百分比。当新申请的数据和前次垃圾收集剩下的存活数据的比率达到该百分比时,就会触发垃圾收集。默认GOGC=100。设置GOGC=off 会完全关闭垃圾收集。runtime/debug包的SetGCPercent
函数允许在运行时修改该百分比。 -
环境变量
GODEBUG
控制运行时的debug输出。GODEBUG的值是逗号分隔的name=val对。支持的name如下:-
allocfreetrace
设置其为1,会导致每次分配都会记录每一个对象的分配、释放及其堆栈踪迹。
-
efence
设置其为1,会导致分配器运行模式为:每个对象申请在独立的页和地址,且永不循环利用
-
gctrace
设置其为1,会导致每次垃圾回收器触发一行日志,包含内存回收的概要信息和暂停的时间。设置其为2,会写入同样的概述,但会重复收集。
-
gcdead
设置其为1,会导致垃圾收集器摧毁任何它认为已经死掉的执行堆栈
-
schedtrace
设置其为X,会导致调度程序每隔X毫秒输出单行信息到标准错误输出
-
scheddetail
设置schedtrace为X并设置其为1,会导致调度程序每隔X毫秒输出详细的多行信息,描述调度、进程、线程和go程的状态
-
-
环境变量
GOMAXPROCS
限制可以同时运行用户层次的go代码的操作系统进程数。没有对代表go代码的、可以在系统调用中阻塞的go程数的限制;那些阻塞的goroutine不与GOMAXPROCS限制冲突。本包的GOMAXPROCS函数可以查询和修改该限制。 -
环境变量
GOTRACEBACK
控制当go程序因为不能恢复的panic或不期望的运行时情况失败时的输出。失败的程序默认会打印所有现存go程的堆栈踪迹(省略运行时系统中的函数),然后以状态码2退出。如果GOTRACEBACK为0,会完全忽略所有go程的堆栈踪迹。如果GOTRACEBACK为1,会采用默认行为。如果GOTRACEBACK为2,会打印所有现存go程包括运行时函数的堆栈踪迹。如果GOTRACEBACK为crash,会打印所有现存go程包括运行时函数的堆栈踪迹,并且如果可能会采用操作系统特定的方式崩溃,而不是退出。例如,在Unix系统里,程序会释放SIGABRT信号以触发核心信息转储。 -
环境变量
GOARCH
、GOOS
、GOPATH
和GOROOT
构成完整的go环境变量集合。它们影响go程序的构建, GOARCH、GOOS和GOROOT在编译时被记录并可用本包的常量和函数获取,但它们不会影响运行时环境
gctrace 每一行打印的日志格式如下:
gc {0} @{1}s {2}%: {3}+...+{4} ms clock, {5}+...+{6} ms cpu, {7}->{8}->{9} MB, {10} MB goal, {11} P
每一个变量的具体定义:
- {0}: gc 运行次数
- {1}: 程序已运行的时间
- {2}: gc 占用的 CPU 百分比
- {3}: 执行时间,包括程序延迟和资源等待
- {4}: 也是执行时间, 一般看这个
- {5}: CPU clock
- {6}: CPU clock
- {7}: GC 启动前的堆内存
- {8}: GC 运行后的堆内存
- {9}: 当前堆内存
- {10}: GC 目标
- {11}: 进程数
下面是一块存在内存泄露的代码段:
package main
import (
"os"
"os/signal"
)
func main() {
go func() {
m := make(map[int]int)
for i := 0; ; i++ {
m[i] = i
}
}()
sig := make(chan os.Signal, 1)
signal.Notify(sig)
<-sig
}
执行 GODEBUG=gctrace=1 go run main.go
, 查看运行时的内存情况:
可以看到程序在运行过程中, 每次 GC,堆内存都在不断增大, 这是一个很明显的内存泄露场景。
runtime.Gosched
让出CPU时间片,重新等待安排任务
runtime.Goexit
退出当前协程
runtime.GOMAXPROCS
Go运行时调度器使用runtime.GOMAXPROCS
参数来确定需要使用多少个os线程来同时执行go代码,
默认值是机器上的CPU核心数量,例如一个8核心的机器上,调度器会把go代码同时调度到8个os线程上
go语言中可以通过runtime.GOMAXPROCS()
函数来设置当前程序并发时占用的CPU逻辑核心数
go1.5版本之前默认使用的是单核心执行,1.5之后默认使用全部的cpu逻辑核心数
我们可以通过将任务分配到不同的CPU逻辑核心上,从而实现并行的效果 :
package main
import (
"fmt"
"runtime"
"sync"
"time"
)
var wg sync.WaitGroup
func a() {
defer wg.Done()
for i := 0; i < 100000000; i++ {
//fmt.Println("A", i)
}
}
func b() {
defer wg.Done()
for i := 0; i < 100000000; i++ {
//fmt.Println("B", i)
}
}
func main() {
startTime := time.Now()
//runtime.GOMAXPROCS(1) // 设置go运行时(runtime)的os线程数
// runtime.GOMAXPROCS设置为1os线程数时执行时间要比4os线程数用时更长
runtime.GOMAXPROCS(4) // 设置go运行时(runtime)的os线程数
wg.Add(1)
go a()
wg.Add(1)
go b()
wg.Add(1)
go a()
wg.Add(1)
go b()
wg.Wait()
fmt.Println(time.Now().Sub(startTime))
}
runtime.NumCPU
返回当前系统的 CPU 核数量
runtime.GOOS
目标操作系统
十六、context
context 翻译成中文是 上下文 的意思,它可以对 API 和进程之间传递截止日期、取消信号和其他请求范围的值。
使用上下文的程序应遵循以下规则:
- 保持包之间的接口一致
- 不要在结构类型中存储上下文
- 上下文应该是第一个参数,通常命名为ctx
- 上下文值仅用于传输进程和API的请求范围数据,而不用于向函数传递可选参数
context 是 Golang 开发常用的并发编程技术。
Context 实际上只定义了接口,凡是实现该接口的类都可称为是一种 context,官方包实现了几个常用的context,分别可用于不同的场景。
context类型
空 context
context包中定义了一个空的context,名为emptyCtx,用于 context 的根节点,空的 context 只是简单的实现了 context,本身不包含任何值,仅用于其他 context 的父节点。
CancelFunc 类型原型
// CancelFunc类型是一个停止工作的方法
// CancelFunc不会等待工作停止
// CancelFunc可以被多个goroutine同时调用, 在第一次调用之后, 对CancelFunc的后续调用什么也不做
type CancelFunc func()
Context 类型原型
type Context interface {
// Deadline返回的时间是代表该上下文所做的工作应该被取消的时间。如果没有设置截止日期,则返回ok==false。连续调用Deadline会返回相同的结果。
Deadline() (deadline time.Time, ok bool)
// Done返回一个channel通道,该通道代表完成工作时关闭取消上下文。需要在 select-case 语句中使用, case <-context.Done():
// 如果上下文未关闭,Done返回nil。
// 当context关闭后, Done返回一个被关闭的通道, 关闭仍然是可读的, goroutine可以接收到关闭请求
// 连续调用Done将返回相同的值。Done通道的关闭可能会异步发生,当cancel函数返回。
// 参考 https://blog.golang.org/pipelines 更多的示例
Done() <-chan struct{}
// 该方法描述 context 关闭的原因
// 如果Done未关闭,Err返回nil。
// 如果Done被关闭,Err返回一个非nil错误
Err() error
// 该方法根据 key 值查询map中 value
// Value返回与此上下文关联的Value for key,或nil 如果没有value与key相关联。连续调用Value相同的键返回相同的结果。
Value(key any) any
}
Context 一般携带一个截止日期、一个取消信号和其他跨越API边界的值。上下文的方法可以被多个 goroutine 同时调用。
Background() 方法原型
var background = new(emptyCtx)
func Background() Context{
return background
}
Background 函数返回一个非nil的空Context。它永远不会被取消,没有价值,也没有期限。它通常由主函数、初始化和测试使用,并作为传入请求的顶级上下文。
TODO() 方法原型
var todo = new(emptyCtx)
func TODO() Context {
return todo
}
TODO 函数返回一个非nil的空Context。代码应该使用上下文。当不清楚要使用哪个Context或者它还不可用时(因为周围的函数还没有扩展到接受Context参数)。
WithValue() 方法原型
func WithValue(parent Context, key, val any) Context
WithValue 函数,返回父对象的副本,其中与键关联的值为val。
上下文值只用于传递进程和api的请求范围内的数据,而不是传递可选参数给函数。
提供的键必须具有可比性,不应该是string类型或任何其他内置类型,以避免使用上下文的包之间的冲突。使用WithValue的用户应该定义自己的键类型。在给接口{}
赋值时,为了避免分配,上下文键通常有具体的类型struct{}
。另外,导出的上下文关键变量的静态类型应该是指针或接口。
context函数
WithCancel() 函数原型
func WithCancel(parent Context) (ctx Context, cancel CancelFunc) {
if parent == nil {
panic("cannot create context from nil parent")
}
c := newCancelCtx(parent)
propagateCancel(parent, &c)
return &c, func() { c.cancel(true, Canceled) }
}
WithCancel 函数,返回带有新的 Done()
通道的父进程的副本。当返回的 cancel 函数被调用或父上下文的 Done()
通道被关闭时,返回上下文的 Done()
通道将被关闭,以哪个先发生为准。
取消此上下文将释放与其关联的资源,因此在此上下文中运行的操作完成后,代码应立即调用cancel。
WithDeadline() 函数原型
func WithDeadline(parent Context, d time.Time) (Context, CancelFunc) {
if parent == nil {
panic("cannot create context from nil parent")
}
if cur, ok := parent.Deadline(); ok && cur.Before(d) {
// The current deadline is already sooner than the new one.
return WithCancel(parent)
}
c := &timerCtx{
cancelCtx: newCancelCtx(parent),
deadline: d,
}
propagateCancel(parent, c)
dur := time.Until(d)
if dur <= 0 {
c.cancel(true, DeadlineExceeded) // deadline has already passed
return c, func() { c.cancel(false, Canceled) }
}
c.mu.Lock()
defer c.mu.Unlock()
if c.err == nil {
c.timer = time.AfterFunc(dur, func() {
c.cancel(true, DeadlineExceeded)
})
}
return c, func() { c.cancel(true, Canceled) }
}
WithDeadline 函数,返回父上下文的一个副本,其截止日期调整为不迟于d。如果父上下文的截止日期已经早于d, WithDeadline(parent, d)
在语义上等价于parent。当截止日期到期、调用返回的 cancel 函数或父上下文的 Done()
通道被关闭时,返回上下文的Done通道将被关闭,以先发生的情况为准。
取消此上下文将释放与其关联的资源,因此在此上下文中运行的操作完成后,代码应立即调用cancel。
WithTimeout() 函数原型
func WithTimeout(parent Context, timeout time.Duration) (context, CancelFunc)
WithTimeout函数,返回 WithDeadline(parent, time.Now().add(timeout))
取消这个上下文会释放与之相关的资源,所以只要在这个上下文中运行的操作完成,代码就应该调用cancel:
func slowOperationWithTimeout(ctx context.Context) (Result, error) {
ctx, cancel := context.WithTimeout(ctx, 100*time.Millisecond)
defer cancel() // 如果slowOperation在超时之前完成,则释放资源
return slowOperation(ctx)
}