ioutil
ioutil包封装了一些I/O实用程序函数。
NopCloser
是将一个不带 Close
的 Reader
封装成 ReadCloser
。
func TestClose(t *testing.T) {
http.HandleFunc("", func(w http.ResponseWriter, r *http.Request) {
b, _ := ioutil.ReadAll(r.Body)
buf := bytes.NewBuffer(b)
r.Body = io.NopCloser(buf)
})
}
ReadAll
从r读取数据直到EOF或遇到error,返回读取的数据和可能的错误。成功的调用返回的err为nil而非EOF。因为本函数定义为读取r直到EOF,它不会将读取返回的EOF视为应报告的错误。
func TestReadAll(t *testing.T) {
r := strings.NewReader("xiaojun is a dog")
b, _ := ioutil.ReadAll(r)
fmt.Println(string(b))
}
ReadDir
读取dirname目录内的所有文件信息,注意此序列有序。
func TestReadDir(t *testing.T) {
os.Mkdir("a", 0666)
os.Create("a/a.txt")
os.MkdirAll("a/b", 0666)
os.Create("a/b.txt")
dirs, _ := ioutil.ReadDir("a")
for _, fi := range dirs {
fmt.Printf("%#v\n", fi)
}
}
ReadFile
从filename指定的文件中读取数据并返回文件的内容。对err的判断和ReadAll一样。
func TestReadFile(t *testing.T) {
f, _ := os.OpenFile("c.txt", os.O_CREATE|os.O_RDWR|os.O_TRUNC, 0666)
f.Write([]byte("xiaojun is a dog"))
b, _ := ioutil.ReadFile("c.txt")
fmt.Println(*(*string)(unsafe.Pointer(&b)))
}
WriteFile
函数向filename指定的文件中写入数据。如果文件不存在将按给出的perm权限创建文件,否则在写入数据之前清空文件。
func TestWriteFile(t *testing.T) {
ioutil.WriteFile("d.txt", []byte("betry is a dog"), 0666)
}
TempDir
在dir目录里创建一个新的、使用prefix作为前缀的临时文件夹,并返回文件夹的路径。如果dir是空字符串,TempDir使用默认用于临时文件的目录(参见os.TempDir函数)。 不同程序同时调用该函数会创建不同的临时目录,调用本函数的程序有责任在不需要临时文件夹时摧毁它。
func TestTempDir(t *testing.T) {
context := []byte("xiaojun is a dog")
d, _ := ioutil.TempDir("", "dog")
fmt.Printf("%s\n", d)
tfn := filepath.Join(d, "tmpfile.txt")
fmt.Printf("%s\n", tfn)
ioutil.WriteFile(tfn, context, 0666)
defer os.RemoveAll(d)
}
TempFile
在dir目录下创建一个新的、使用prefix为前缀的临时文件,以读写模式打开该文件并返回os.File指针。如果dir是空字符串,TempFile使用默认用于临时文件的目录(参见os.TempDir函数)。责任与TempDir相同。
func TestTempFile(t *testing.T) {
f, _ := ioutil.TempFile("", "dogxiaojun")
fmt.Printf("%s\n", f.Name())
f.WriteString("xiaojun is a dog")
defer os.Remove(f.Name())
defer f.Close()
}
bufio
bufio包实现了有缓冲的I/O。它包装一个io.Reader或io.Writer接口对象,创建另一个也实现了该接口,且同时还提供了缓冲和一些文本I/O的帮助函数的对象。
bufio就提供了缓冲区(分配一块内存),读和写都先在缓冲区中,最后再读写文件,来降低访问本地磁盘的次数,从而提高效率。
Reader
bufio.Reader
是bufio中对io.Reader 的封装。
bufio.Read(p []byte)
相当于读取大小len的内容,思路如下:
- 当缓存区有内容的时,将缓存区内容全部填入p并清空缓存区
- 当缓存区没有内容的时候且len>len(buf),即要读取的内容比缓存区还要大,直接去文件读取即可
- 当缓存区没有内容的时候且len<len(buf),即要读取的内容比缓存区小,缓存区从文件读取内容充满缓存区,并将p填满(此时缓存区有剩余内容)
- 以后再次读取时缓存区有内容,将缓存区内容全部填入p并清空缓存区(此时和情况1一样)
- reader内部通过维护一个r, w 即读入和写入的位置索引来判断是否缓存区内容被全部读出。
// Read读取数据写入p。本方法返回写入p的字节数。本方法一次调用最多会调用下层Reader接口一次Read方法,因此返回值n可能小于len。读取到达结尾时,返回值n将为0而err将为io.EOF。
func TestRead(t *testing.T) {
s1 := strings.NewReader("xiaojun is a dog\ndog")
reader := bufio.NewReader(s1)
var b [4]byte
var ret []byte
for {
n, err := reader.Read(b[:])
if err == io.EOF {
break
}
ret = append(ret, b[:n]...)
}
fmt.Println(string(ret))
}
// Reset丢弃缓冲中的数据,清除任何错误,将b重设为将其输出写入w。
func TestReset(t *testing.T) {
s1 := strings.NewReader("xiaojun is a dog\ndog")
s2 := strings.NewReader("betry is a dog\ndog")
br := bufio.NewReader(s1)
str1, _ := br.ReadString('\n')
fmt.Println(str1)
br.Reset(s2)
str2, _ := br.ReadBytes('\n')
fmt.Println(string(str2))
}
// UnreadByte吐出最近一次读取操作读取的最后一个字节。(只能吐出最后一个,多次调用会出问题)
func TestUnReadByte(t *testing.T) {
s1 := strings.NewReader("xiaojun is a dog\ndog")
r := bufio.NewReader(s1)
b1, _ := r.ReadByte()
fmt.Println(b1)
r.UnreadByte()
b2, _ := r.ReadByte()
fmt.Println(b2)
}
// ReadLine尝试返回一行数据,不包括行尾标志的字节。如果行太长超过了缓冲,返回值isPrefix会被设为true,并返回行的前面一部分。该行剩下的部分将在之后的调用中返回。返回值isPrefix会在返回该行最后一个片段时才设为false。返回切片是缓冲的子切片,只在下一次读取操作之前有效。ReadLine要么返回一个非nil的line,要么返回一个非nil的err,两个返回值至少一个非nil。
// 返回的文本不包含行尾的标志字节(“\r\n”或"\n")。如果输入流结束时没有行尾标志字节,方法不会出错,也不会指出这一情况。在调用ReadLine之后调用UnreadByte会总是吐出最后一个读取的字节(很可能是该行的行尾标志字节),即使该字节不是ReadLine返回值的一部分。
func TestReadLine(t *testing.T) {
s := strings.NewReader("xiaojun\nis\r\na\r\nDOG")
br := bufio.NewReader(s)
w, isPrefix, _ := br.ReadLine()
fmt.Printf("%q %v\n", w, isPrefix)
w, isPrefix, _ = br.ReadLine()
fmt.Printf("%q %v\n", w, isPrefix)
w, isPrefix, _ = br.ReadLine()
fmt.Printf("%q %v\n", w, isPrefix)
w, isPrefix, _ = br.ReadLine()
fmt.Printf("%q %v\n", w, isPrefix)
}
// ReadSlice读取直到第一次遇到delim字节,返回缓冲里的包含已读取的数据和delim字节的切片。该返回值只在下一次读取操作之前合法。如果ReadSlice放在在读取到delim之前遇到了错误,它会返回在错误之前读取的数据在缓冲中的切片以及该错误(一般是io.EOF)。如果在读取delim之前缓冲就写满了,ReadSlice失败并返回ErrBufferFull。因为ReadSlice的返回值会被下一次I/O操作重写,调用者应尽量使用ReadBytes或ReadString替代本方法。当且仅当ReadBytes方法返回的切片不以delim结尾时,会返回一次非nil的错误。
func TestReadSlice(t *testing.T) {
s := strings.NewReader("xiaojun\nis\r\na\r\nDOG")
br := bufio.NewReader(s)
slice, _ := br.ReadSlice('\n')
fmt.Printf("%q \n", slice)
slice, _ = br.ReadSlice('\n')
fmt.Printf("%q \n", slice)
slice, _ = br.ReadSlice('\n')
fmt.Printf("%q \n", slice)
}
// ReadString读取直到第一次遇到delim字节,返回一个包含已读取的数据和delim字节的字符串。如果ReadString方法在读取到delim之前遇到了错误,它会返回在错误之前读取的数据以及该错误(一般是io.EOF)。当且仅当ReadString方法返回的切片不以delim结尾时,会返回一个非nil的错误。
func TestReadString(t *testing.T) {
s := strings.NewReader("XIAOJUN IS A DOG")
br := bufio.NewReader(s)
w, _ := br.ReadString(' ')
fmt.Printf("%q\n", w)
w, _ = br.ReadString(' ')
fmt.Printf("%q\n", w)
w, _ = br.ReadString(' ')
fmt.Printf("%q\n", w)
}
func TestWriteTo(t *testing.T) {
s := strings.NewReader("XIAOJUN IS A DOG")
br := bufio.NewReader(s)
b := bytes.NewBuffer(make([]byte, 0))
br.WriteTo(b)
fmt.Println(b.String())
}
Writer
bufio.Writer
是bufio中对io.Writer 的封装。
bufio.Write(p []byte)
的思路如下
- 判断buf中可用容量是否可以放下 p
- 如果能放下,直接把p拼接到buf后面,即把内容放到缓冲区
- 如果缓冲区的可用容量不足以放下,且此时缓冲区是空的,直接把p写入文件即可
- 如果缓冲区的可用容量不足以放下,且此时缓冲区有内容,则用p把缓冲区填满,把缓冲区所有内容写入文件,并清空缓冲区
- 判断p的剩余内容大小能否放到缓冲区,如果能放下(此时和步骤1情况一样)则把内容放到缓冲区
- 如果p的剩余内容依旧大于缓冲区,(注意此时缓冲区是空的,情况和步骤3一样)则把p的剩余内容直接写入文件
func TestWriter(t *testing.T) {
b := bytes.NewBuffer(make([]byte, 0))
b1 := bytes.NewBuffer(make([]byte, 0))
w := bufio.NewWriter(b)
w.WriteString("XIAOJUN IS A DOG")
// Reset丢弃缓冲中的数据,清除任何错误,将b重设为将其输出写入w。
w.Reset(b1)
w.WriteString("BETRY IS A DOG")
// 将缓冲中的数据写入下层的io.Writer接口。
w.Flush()
fmt.Println(b.String())
fmt.Println(b1.String())
}
func TestWriter1(t *testing.T) {
b := bytes.NewBuffer(make([]byte, 0))
w := bufio.NewWriter(b)
fmt.Printf("%d %d \n", w.Available(), w.Buffered())
w.WriteString("XIAOJUN IS A DOG")
fmt.Printf("%d %d %q\n", w.Available(), w.Buffered(), b)
w.Flush()
fmt.Printf("%d %d %q\n", w.Available(), w.Buffered(), b)
}
ReadWriter
ReadWriter类型保管了指向Reader和Writer类型的指针。
func TestReadWriter(t *testing.T) {
b1 := bytes.NewBufferString("XIAOJUN IS A DOG")
r := bufio.NewReader(b1)
b2 := bytes.NewBuffer(make([]byte, 0))
w := bufio.NewWriter(b2)
rw := bufio.NewReadWriter(r, w)
s, _ := rw.ReadString('\n')
fmt.Println(s)
rw.WriteString("BETRY IS A DOG")
rw.Flush()
fmt.Println(b2.String())
}
SplitFunc
SplitFunc类型代表用于对输出作词法分析的分割函数。
参数data是尚未处理的数据的一个开始部分的切片,参数atEOF表示是否Reader接口不能提供更多的数据。返回值是解析位置前进的字节数,将要返回给调用者的token切片,以及可能遇到的错误。如果数据不足以(保证)生成一个完整的token,例如需要一整行数据但data里没有换行符,SplitFunc可以返回(0, nil, nil)来告诉Scanner读取更多的数据写入切片然后用从同一位置起始、长度更长的切片再试一次(调用SplitFunc类型函数)。
如果返回值err非nil,扫描将终止并将该错误返回给Scanner的调用者。
除非atEOF为真,永远不会使用空切片data调用SplitFunc类型函数。然而,如果atEOF为真,data却可能是非空的、且包含着未处理的文本。
SplitFunc的作用很简单,从data中找出你感兴趣的数据,然后返回并告诉调用者,data中有多少数据你已经处理过了。
func TestSplitFunc(t *testing.T) {
f, _ := os.OpenFile("a.txt", os.O_CREATE|os.O_RDWR|os.O_TRUNC, 0666)
f.WriteString("XIAOJUN IS A DOG\n")
f.WriteString("BETRY IS A DOG\n")
f.WriteString("xiaojun little dog\n")
f.Seek(0, 0)
defer f.Close()
sc := bufio.NewScanner(f)
sc.Split(bufio.ScanWords)
for sc.Scan() {
fmt.Println(sc.Text())
}
}
Scanner
Scanner类型提供了方便的读取数据的接口,如从换行符分隔的文本里读取每一行。成功调用的Scan方法会逐步提供文件的token,跳过token之间的字节。token由SplitFunc类型的分割函数指定;默认的分割函数会将输入分割为多个行,并去掉行尾的换行标志。本包预定义的分割函数可以将文件分割为行、字节、unicode码值、空白分隔的word。调用者可以定制自己的分割函数。扫描会在抵达输入流结尾、遇到的第一个I/O错误、token过大不能保存进缓冲时,不可恢复的停止。当扫描停止后,当前读取位置可能会在最后一个获得的token后面。需要更多对错误管理的控制或token很大,或必须从reader连续扫描的程序,应使用buffio.Reader代替。
func TestScan(t *testing.T) {
b := bytes.NewBufferString("betry is a dog")
sc := bufio.NewScanner(b)
sc.Split(bufio.ScanWords)
//sc.Split(bufio.ScanBytes)
//sc.Split(bufio.ScanRunes)
for sc.Scan() {
fmt.Println(sc.Text())
}
}
builtin
builtin包提供了一些类型声明、变量和常量声明,还有一些便利函数,这个包不需要导入,这些变量和函数就可以直接使用。
append
func TestAppend(t *testing.T) {
a := []int{1, 2, 3}
// 直接在slice后面添加单个元素
a = append(a, 4)
fmt.Println(a)
b := []byte("xiaojun")
// 直接将另外一个slice添加到slice后面,但其本质还是将anotherSlice中的元素一个一个添加到slice中
b = append(b, []byte(" is a dog")...)
fmt.Printf("%s\n", b)
}
len
返回,数组、切片、字符串、通道的长度。
func TestLen(t *testing.T) {
a := [3]int{}
b := []byte("xiaojun dog")
c := "betry dog"
d := make(chan int, 2)
d <- 1
fmt.Println(len(a), len(b), len(c), len(d))
}
print、println
打印输出到控制台。
func TestPrint(t *testing.T) {
print("xiaojun ")
println("he is a dog")
}
panic
抛出一个panic异常。
func TestPanic(t *testing.T) {
defer func() {
if err := recover(); err != nil {
fmt.Println(err)
}
}()
panic("this is a panic")
}
new、make
- make只能用来分配及初始化类型为
slice
,map
,chan
的数据;new可以分配任意类型的数据; - new分配返回的是指针,即类型*T;make返回引用,即T;
- new分配的空间被清零,make分配后,会进行初始化。
func TestNew(t *testing.T) {
a := new(int)
b := new(bool)
c := new(float64)
fmt.Println(*a, *b, *c)
}
例如:make([]int, 10 , 100)
说明:分配一个有100个int的数组,然后创建一个长度为10,容量为100的slice结构,该slice引用包含前10个元素的数组。对应的,new([]int)返回一个指向新分配的,被置零的slice结构体的指针,即指向值为nil的slice的指针。
内建函数make(T, args)与new(T)的用途不一样。它只用来创建slice、map和channel,并且返回一个初始化的(而不是置零),类型为T的值(而不是*T)。之所以有所不同,是因为这三个类型的背后引用了使用前必须初始化的数据结构。例如,slice是一个三元描述符,包含一个指向数据(在数组中)的指针,长度,以及容量,在这些项被初始化之前,slice都是nil的。对于slice,map和channel,make初始化这些内部数据结构,并准备好可用的值。
func TestMake(t *testing.T) {
var p *[]int = new([]int)
var v []int = make([]int, 10)
fmt.Printf("p: %v\n", p)
fmt.Printf("v: %v\n", v)
var p1 *[]int = new([]int)
*p1 = make([]int, 5, 10)
v1 := make([]int, 10)
fmt.Printf("p1: %v\n", p1)
fmt.Printf("v1: %v\n", v1)
}
json
json包可以实现json的编码和解码,就是将json字符串转换为struct,或者将struct转换为json。
Marshal
将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 包支持的任意数据类型)
func TestMarshal(t *testing.T) {
p := struct {
Name string `json:"name"`
Age int `json:"age"`
Email string `json:"email"`
}{
"xiaojun",
38,
"btry@dog.com",
}
b, _ := json.Marshal(p)
fmt.Println(*(*string)(unsafe.Pointer(&b)))
}
Unmarshal
将json转码为struct结构体。这个函数会把传入的 data 作为一个JSON来进行解析,解析后的数据存储在参数 v 中。这个参数 v 也是任意类型的参数(但一定是一个类型的指针),原因是我们在是以此函数进行JSON 解析的时候,这个函数不知道这个传入参数的具体类型,所以它需要接收所有的类型。
func TestUnmarshal(t *testing.T) {
b := []byte("{\"Name\":\"xiaojun\",\"Age\":38,\"Email\":\"btry@dog.com\"}")
p := struct {
Name string
Age int
Email string
}{}
json.Unmarshal(b, &p)
fmt.Printf("%#v\n", p)
}
Decoder
从输入流读取并解析json,应用于io流Reader Writer可以扩展到http websocket等场景。
func TestDecoder(t *testing.T) {
buf := bytes.NewBufferString("{\"Name\":\"xiaojun\",\"Age\":38,\"Email\":\"btry@dog.com\"}")
reader := bytes.NewReader(buf.Bytes())
decoder := json.NewDecoder(reader)
var ret map[string]interface{}
_ = decoder.Decode(&ret)
fmt.Printf("%#v\n", ret)
}
Encoder
写json到输出流,应用于io流Reader Writer可以扩展到http websocket等场景。
func TestEncoder(t *testing.T) {
p := struct {
Name string `json:"name"`
Age int `json:"age"`
Email string `json:"email"`
}{
"xiaojun",
38,
"btry@dog.com",
}
f, _ := os.OpenFile("a.txt", os.O_CREATE|os.O_RDWR|os.O_TRUNC, 0666)
encoder := json.NewEncoder(f)
_ = encoder.Encode(p)
}
sort
sort包提供了排序切片和用户自定义数据集以及相关功能的函数。
sort包主要针对[]int
、[]float64
、[]string
、以及其他自定义切片的排序。
排序接口
type NewInts []uint
func (n NewInts) Len() int {
return len(n)
}
func (n NewInts) Less(i, j int) bool {
fmt.Printf("%d %d %t %v\n", i, j, n[i] < n[j], n)
return n[i] < n[j]
}
func (n NewInts) Swap(i, j int) {
n[i], n[j] = n[j], n[i]
}
func TestSort(t *testing.T) {
n := []uint{1, 4, 2}
sort.Sort(NewInts(n))
fmt.Println(n)
}
数据集合排序
Sort排序方法
对数据集合(包括自定义数据类型的集合)排序,需要实现sort.Interface
接口的三个方法,即:
type Interface interface {
// 获取数据集合元素个数
Len() int
// 如果i索引的数据小于j索引的数据,返回true,Swap(),即数据升序排序,false 不调用swap。
Less(i, j int) bool
// 交换i和j索引的两个元素的位置
Swap(i, j int)
}
实现了这三个方法后,即可调用该包的Sort()方法进行排序。Sort()方法唯一的参数就是待排序的数据集合。
IsSorted
sort包提供了IsSorted方法,可以判断数据集合是否已经排好顺序。IsSorted方法的内部实现依赖于我们自己实现的Len()和Less()方法。
type NewInts []uint
func (n NewInts) Len() int {
return len(n)
}
func (n NewInts) Less(i, j int) bool {
//fmt.Printf("%d %d %t %v\n", i, j, n[i] < n[j], n)
return n[i] < n[j]
}
func (n NewInts) Swap(i, j int) {
n[i], n[j] = n[j], n[i]
}
func TestReverseSort(t *testing.T) {
var n NewInts = NewInts([]uint{1, 4, 2})
var n1 sort.Interface = sort.Reverse(n)
// false
fmt.Println(sort.IsSorted(n))
// false
fmt.Println(sort.IsSorted(n1))
sort.Sort(n1)
// false
fmt.Println(sort.IsSorted(n))
// true
fmt.Println(sort.IsSorted(n1))
// [4 2 1]
fmt.Println(n)
}
Reverse
sort包提供了Reverse()方法,将数据按Less()定义的排序方式逆序排序,而不必修改Less()代码。
type NewInts []uint
func (n NewInts) Len() int {
return len(n)
}
func (n NewInts) Less(i, j int) bool {
fmt.Printf("%d %d %t %v\n", i, j, n[i] < n[j], n)
return n[i] < n[j]
}
func (n NewInts) Swap(i, j int) {
n[i], n[j] = n[j], n[i]
}
func TestReverseSort(t *testing.T) {
var n NewInts = NewInts([]uint{1, 4, 2})
var n1 sort.Interface = sort.Reverse(n)
sort.Sort(n1)
fmt.Println(n)
}
Search
sort包提供Search方法查询位置。Search()方法会使用“二分查找”算法。
通过sort.Search
无法直接判断某个元素是否在切片中,还需要补一个 data[index]和target是否相等的判断。
type NewInts []uint
func (n NewInts) Len() int {
return len(n)
}
func (n NewInts) Less(i, j int) bool {
//fmt.Printf("%d %d %t %v\n", i, j, n[i] < n[j], n)
return n[i] < n[j]
}
func (n NewInts) Swap(i, j int) {
n[i], n[j] = n[j], n[i]
}
func TestSortSearch(t *testing.T) {
var n NewInts = NewInts([]uint{1, 6, 3})
target := uint(6)
sort.Sort(n)
ret := sort.Search(len(n), func(i int) bool {
return n[i] >= target
})
if ret != len(n) {
fmt.Println(n[ret] == target)
} else {
fmt.Println(false)
}
}
内部数据类型
[]int
sort包定义了一个IntSlice类型,并且实现了sort.Interface接口。提供的sort.Ints()方法使用了该IntSlice类型。
func TestSortInts(t *testing.T) {
n := []int{34, 5, 2, 56, 87}
fmt.Printf("排序前:%v\n", n)
// 正序排序
sort.Ints(n)
fmt.Printf("排序后:%v\n", n)
// 倒序排序
sort.Sort(sort.Reverse(sort.IntSlice(n)))
fmt.Printf("逆序后:%v\n", n)
// 正序排序,二分查找元素
sort.Ints(n)
searchTarget := -1
s := sort.SearchInts(n, searchTarget)
fmt.Printf("查找坐标:%d\n", s)
if s == len(n) || n[s] != searchTarget {
fmt.Println("查找失败")
} else {
fmt.Println("查找成功")
}
}
[]float64
func TestSortFloat64(t *testing.T) {
n := []float64{34.001, 5, 2.999, 56, 87.444}
fmt.Printf("排序前:%v\n", n)
// 正序排序
sort.Float64s(n)
fmt.Printf("排序后:%v\n", n)
// 倒序排序
sort.Sort(sort.Reverse(sort.Float64Slice(n)))
fmt.Printf("逆序后:%v\n", n)
// 正序排序,二分查找元素
sort.Float64s(n)
searchTarget := 2.999
s := sort.SearchFloat64s(n, searchTarget)
fmt.Printf("查找坐标:%d\n", s)
if s == len(n) || n[s] != searchTarget {
fmt.Println("查找失败")
} else {
fmt.Println("查找成功")
}
}
[]string
两个string对象之间的大小比较是基于“字典序”的。
func TestSortStrings(t *testing.T) {
n := []string{"xiaojun", "dog", "cat", "Betry", "pig"}
fmt.Printf("排序前:%v\n", n)
// 正序
sort.Strings(n)
fmt.Printf("排序后:%v\n", n)
// 倒序
sort.Sort(sort.Reverse(sort.StringSlice(n)))
fmt.Printf("倒序后:%v\n", n)
// 正序,二分查询
sort.Strings(n)
target := "pig"
idx := sort.SearchStrings(n, target)
if idx < len(n) && n[idx] == target {
fmt.Println("查找成功")
} else {
fmt.Println("查找失败")
}
}
[] []int
type Array2D [][]int
func (arr Array2D) Len() int {
return len(arr)
}
func (arr Array2D) Less(i, j int) bool {
if arr[i][0] != arr[j][0] {
return arr[i][0] < arr[j][0]
} else {
return arr[i][1] < arr[j][1]
}
}
func (arr Array2D) Swap(i, j int) {
arr[i], arr[j] = arr[j], arr[i]
}
func Test2DArray(t *testing.T) {
ar := Array2D{
{1, 3},
{6, 5},
{4, 3},
{4, 2},
}
// [[1 3] [6 5] [4 3] [4 2]]
fmt.Println(ar)
sort.Sort(ar)
// [[1 3] [4 2] [4 3] [6 5]]
fmt.Println(ar)
}
[]map[string]float64
type ArrayMap []map[string]string
func (a ArrayMap) Len() int {
return len(a)
}
// 按照map里面dog对应的值排序
func (a ArrayMap) Less(i, j int) bool {
return a[i]["dog"] < a[j]["dog"]
}
func (a ArrayMap) Swap(i, j int) {
a[i], a[j] = a[j], a[i]
}
func TestArrayMap(t *testing.T) {
am := ArrayMap{
{"dog": "xiaojun", "flag": "true"},
{"dog": "betry", "flag": "true"},
{"dog": "wannian", "flag": "false"},
{"dog": "longjia", "flag": "true"},
}
// [map[dog:xiaojun flag:true] map[dog:betry flag:true] map[dog:wannian flag:false] map[dog:longjia flag:true]]
fmt.Println(am)
sort.Sort(am)
// [map[dog:betry flag:true] map[dog:longjia flag:true] map[dog:wannian flag:false] map[dog:xiaojun flag:true]]
fmt.Println(am)
}
[]struct
type User struct {
Name string
Age int
}
type UserSlice []User
func (us UserSlice) Len() int {
return len(us)
}
func (us UserSlice) Less(i, j int) bool {
return us[i].Age < us[j].Age
}
func (us UserSlice) Swap(i, j int) {
us[i], us[j] = us[j], us[i]
}
func TestSortStruct(t *testing.T) {
u := UserSlice{
{"肖俊", 38},
{Name: "贝塔", Age: 18},
{"betry", 44},
{Name: "longjia", Age: 15},
}
// [{肖俊 38} {贝塔 18} {betry 44} {longjia 15}]
fmt.Println(u)
sort.Sort(u)
// [{longjia 15} {贝塔 18} {肖俊 38} {betry 44}]
fmt.Println(u)
}
math
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)
常用函数
IsNaN
是否表示一个NaN(Not A Number)值,是数值返回一个false,不是数值则返回一个true。
func TestIsNan(t *testing.T) {
f := math.Mod(66, 0)
var f1 float64
// true
fmt.Println(math.IsNaN(f))
// false
fmt.Println(math.IsNaN(f1))
}
Ceil
返回一个不小于x的最小整数,向上取整。
func TestCeil(t *testing.T) {
fmt.Println(math.Ceil(1.1)) //2
}
Floor
返回一个不大于x的最小整数,向下取整。
func TestFloor(t *testing.T) {
fmt.Println(math.Floor(1.9)) //1
}
Trunc
返回x整数部分。
func TestTrunc(t *testing.T) {
fmt.Println(math.Trunc(2.9999)) //2
}
Abs
返回x的绝对值
func TestAbs(t *testing.T) {
fmt.Println(math.Abs(3)) //3
fmt.Println(math.Abs(-3.1)) //3.1
}
Max
返回x和y中最大值。
func TestMax(t *testing.T) {
fmt.Println(math.Max(1000, 200)) //1000
}
Min
返回x和y中最小值。
func TestMin(t *testing.T) {
fmt.Println(math.Min(1000, 200)) //200
}
Dim
函数返回x-y和0中的最大值。
func TestDim(t *testing.T) {
fmt.Println(math.Dim(1000, 2000)) //0
fmt.Println(math.Dim(1000, 200)) //800
}
Mod
取余运算,可以理解为 x-Trunc(x/y)*y,结果的正负号和x相同。
func TestMod(t *testing.T) {
fmt.Println(math.Mod(123, 0)) //NaN
fmt.Println(math.Mod(123, 10)) //3
}
Sqrt
返回x的二次方根,平方根。
func TestSqrt(t *testing.T) {
fmt.Println(math.Sqrt(144)) //12
}
Cbrt
返回x的三次方根,立方根。
func TestCbrt(t *testing.T) {
fmt.Println(math.Cbrt(1728)) //12
}
Hypot
返回Sqrt(p * p + q * q),注意要避免不必要的溢出或下溢。
func TestHypot(t *testing.T) {
fmt.Println(math.Hypot(12, 12)) //16.970562748477143
}
Pow
求幂,x的y次方。
func TestPow(t *testing.T) {
fmt.Println(math.Pow(2, 3)) //8
}
Sin
求正弦。
func TestSin(t *testing.T) {
fmt.Println(math.Sin(12)) //-0.5365729180004349
}
Cos
求余弦。
func TestCos(t *testing.T) {
fmt.Println(math.Cos(12)) //0.8438539587324921
}
Tan
求正切。
func TestTan(t *testing.T) {
fmt.Println(math.Tan(12)) //-0.6358599286615807
}
Log
求自然对数。
func TestLog(t *testing.T) {
fmt.Println(math.Log(2)) //0.6931471805599453
}
Log2
求2为底的对数。
func TestLog2(t *testing.T) {
fmt.Println(math.Log2(128)) //7
}
Log10
求10为底的对数。
func TestLog10(t *testing.T) {
fmt.Println(math.Log10(10000)) //4
}
Signbit
如果x是一个负数或者负零,返回true。
func TestSignbit(t *testing.T) {
fmt.Println(math.Signbit(10000)) //false
fmt.Println(math.Signbit(-200)) //true
}
随机数math/rand
math/rand包是go提供用来产生各种各样随机数的包,注意: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。 |
func TestRand(t *testing.T) {
// 1.20版本后无需传种子
rand.Seed(time.Now().Unix())
fmt.Println(rand.Int())
fmt.Println(rand.Int31())
fmt.Println(rand.Intn(10))
}
自定义rand
var myRand *rand.Rand
func init() {
// 使用当前的纳秒生成一个随机源
s := rand.NewSource(time.Now().UnixNano())
// 生成一个rand
myRand = rand.New(s)
}
func TestSource(t *testing.T) {
fmt.Println(myRand.Intn(100))
}
flag
Go语言内置的flag包实现了命令行参数的解析,flag包使得开发命令行工具更为简单。
参数类型
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
func main() {
var d *time.Duration = flag.Duration("duration", time.Second, "时间间隔")
var name *string = flag.String("name", "xiaojun", "姓名")
var age *uint = flag.Uint("age", 38, "年龄")
var married *bool = flag.Bool("married", false, "不是单身狗")
flag.Parse()
fmt.Println(*name, *age, *married, *d)
}
flag.TypeVar
func main() {
var name string
var age uint
var married bool
var d time.Duration
flag.DurationVar(&d, "duration", time.Second, "时间间隔")
flag.StringVar(&name, "name", "xiaojun", "姓名")
flag.UintVar(&age, "age", 38, "年龄")
flag.BoolVar(&married, "married", false, "不是单身狗")
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() 返回使用的命令行参数个数
func main() {
var name string
var age uint
var married bool
var d time.Duration
flag.StringVar(&name, "n", "beta", "姓名")
flag.UintVar(&age, "a", 18, "年龄")
flag.BoolVar(&married, "m", true, "婚否")
flag.DurationVar(&d, "d", time.Second, "时间间隔")
flag.Parse()
// 肖俊 38 false 3h45m10s
fmt.Println(name, age, married, d)
// [aaa 222 false]
fmt.Println(flag.Args())
// 3
fmt.Println(flag.NArg())
// 4
fmt.Println(flag.NFlag())
}
> go run .\flagtest.go -n 肖俊 --a 38 -m=false --d=3h45m10s aaa 222 false
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时间片,重新等待安排任务。
func TestGosched(t *testing.T) {
runtime.GOMAXPROCS(1)
go func(s string) {
for i := 0; i < 2; i++ {
fmt.Println(s)
runtime.Gosched()
}
}("world")
for i := 0; i < 2; i++ {
fmt.Println("hello")
runtime.Gosched()
}
time.Sleep(time.Second)
}
runtime.Goexit
退出当前协程。
func TestGoexit(t *testing.T) {
wg.Add(1)
go func() {
defer wg.Done()
defer fmt.Println("A defer")
func() {
defer fmt.Println("B defer")
runtime.Goexit()
defer fmt.Println("C defer")
fmt.Println("B")
}()
fmt.Println("A")
}()
wg.Wait()
}
runtime.GOMAXPROCS
Go运行时调度器使用runtime.GOMAXPROCS
参数来确定需要使用多少个os线程来同时执行go代码,默认值是机器上的CPU核心数量,我们可以通过将任务分配到不同的CPU逻辑核心上,从而实现并行的效果。
var wg sync.WaitGroup
func a() {
defer wg.Done()
for i := 0; i < 10; i++ {
fmt.Println("a:", i)
}
}
func b() {
defer wg.Done()
for i := 0; i < 10; i++ {
fmt.Println("b:", i)
}
}
func TestGOMAXPROCS(t *testing.T) {
wg.Add(2)
go a()
go b()
wg.Wait()
}
runtime.NumCPU
返回当前系统的 CPU 核数量。
func TestGONumCPU(t *testing.T) {
fmt.Println(runtime.NumCPU())
}
runtime.GOOS
目标操作系统。
func TestGOOS(t *testing.T) {
fmt.Println(runtime.GOOS)
}
context
context,它可以对 API 和进程之间传递截止日期、取消信号和其他请求范围的值。
使用上下文的程序应遵循以下规则:
- 保持包之间的接口一致
- 不要在结构类型中存储上下文
- 上下文应该是第一个参数,通常命名为ctx
- 上下文值仅用于传输进程和API的请求范围数据,而不用于向函数传递可选参数
Context 实际上只定义了接口,凡是实现该接口的类都可称为是一种 context,官方包实现了几个常用的context,分别可用于不同的场景。
context类型
空 context
context包中定义了一个空的context,名为emptyCtx,用于 context 的根节点,空的 context 只是简单的实现了 context,本身不包含任何值,仅用于其他 context 的父节点。
context.emptyCtx
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
}
Background()
Background 函数返回一个非nil的空Context。它永远不会被取消,没有价值,也没有期限。它通常由主函数、初始化和测试使用,并作为传入请求的顶级上下文。
var background = new(emptyCtx)
func Background() Context{
return background
}
TODO()
TODO 函数返回一个非nil的空Context。代码应该使用上下文。当不清楚要使用哪个Context或者它还不可用时(因为周围的函数还没有扩展到接受Context参数)。
var todo = new(emptyCtx)
func TODO() Context {
return todo
}
WithValue()
func WithValue(parent Context, key, val any) Context
WithValue 函数,返回父对象的副本,其中与键关联的值为val。
上下文值只用于传递进程和api的请求范围内的数据,而不是传递可选参数给函数。
提供的键必须具有可比性,不应该是string类型或任何其他内置类型,以避免使用上下文的包之间的冲突。使用WithValue的用户应该定义自己的键类型。在给接口{}
赋值时,为了避免分配,上下文键通常有具体的类型struct{}
。另外,导出的上下文关键变量的静态类型应该是指针或接口。
func main() {
type favContextKey string
f := func(ctx context.Context, key favContextKey) {
if v := ctx.Value(key); v != nil {
fmt.Println("found value:", v)
return
}
fmt.Println("not found key:", key)
}
key1 := favContextKey("key1")
ctx := context.WithValue(context.Background(), key1, "beta")
f(ctx, key1)
f(ctx, favContextKey("key2"))
}
WithCancel()
WithCancel 函数,返回带有新的 Done()
通道的父进程的副本。当返回的 cancel 函数被调用或父上下文的 Done()
通道被关闭时,返回上下文的 Done()
通道将被关闭,以哪个先发生为准。
取消此上下文将释放与其关联的资源,因此在此上下文中运行的操作完成后,代码应立即调用cancel。
func handleRequest(ctx context.Context) {
go writeRedis(ctx)
go writeDatabase(ctx)
for {
select {
case <-ctx.Done():
fmt.Println("handleRequest Done")
return
default:
fmt.Println("handleRequest running")
time.Sleep(2 * time.Second)
}
}
}
func writeRedis(ctx context.Context) {
for {
select {
case <-ctx.Done():
fmt.Println("redis done")
return
default:
fmt.Println("redis running")
time.Sleep(2 * time.Second)
}
}
}
func writeDatabase(ctx context.Context) {
for {
select {
case <-ctx.Done():
fmt.Println("database done")
return
default:
fmt.Println("database running")
time.Sleep(2 * time.Second)
}
}
}
func main() {
ctx, cancelFunc := context.WithCancel(context.Background())
go handleRequest(ctx)
time.Sleep(5 * time.Second)
fmt.Println("its time to stop all goroutines")
cancelFunc()
var c chan os.Signal = make(chan os.Signal, 1)
signal.Notify(c, os.Interrupt, os.Kill)
<-c
}
WithDeadline()
WithDeadline 函数,返回父上下文的一个副本,其截止日期调整为不迟于d。如果父上下文的截止日期已经早于d, WithDeadline(parent, d)
在语义上等价于parent。当截止日期到期、调用返回的 cancel 函数或父上下文的 Done()
通道被关闭时,返回上下文的Done通道将被关闭,以先发生的情况为准。
取消此上下文将释放与其关联的资源,因此在此上下文中运行的操作完成后,代码应立即调用cancel。
const d = 2 * time.Millisecond
func main() {
dTime := time.Now().Add(d)
ctx, cancelFunc := context.WithDeadline(context.Background(), dTime)
defer cancelFunc()
select {
case <-time.After(1 * time.Second):
fmt.Println("截止日期之后")
case <-ctx.Done():
fmt.Println("截止日期之内")
}
}
WithTimeout
WithTimeout函数,返回 WithDeadline(parent, time.Now().add(timeout))
取消这个上下文会释放与之相关的资源,所以只要在这个上下文中运行的操作完成,代码就应该调用cancel:
const d = 2 * time.Millisecond
func main() {
ctx, cancelFunc := context.WithTimeout(context.Background(), d)
defer cancelFunc()
select {
case <-time.After(1 * time.Second):
fmt.Println("截止日期之后")
case <-ctx.Done():
fmt.Println("截止日期之内")
}
}