├── .gitignore ├── errors └── errors.md ├── index └── suffixarray.md ├── path ├── path.md └── filepath.md ├── LICENSE ├── flag └── flag.md ├── io ├── ioutil.md └── io.md ├── strconv └── strconv.md ├── container ├── list.md ├── ring.md └── heap.md ├── README.md ├── sort.md ├── sync ├── atomic.md └── sync.md ├── log └── log.md ├── context └── context.md ├── strings └── strings.md ├── bufio └── bufio.md └── net └── http └── http.md /.gitignore: -------------------------------------------------------------------------------- 1 | 2 | .DS_Store 3 | -------------------------------------------------------------------------------- /errors/errors.md: -------------------------------------------------------------------------------- 1 | # 概述 2 | 在go中没有异常捕获机制,而是通过一个单独的函数返回值来表示错误信息。 3 | 4 | # Error 5 | 错误类型的接口定义如下: 6 | 7 | ``` 8 | type error interface { 9 | Error() string 10 | } 11 | ``` 12 | 这个接口只有一个方法`Error`,这个方法返回一个字符串,描述错误的详情。在使用中,通常会通过直接实现`Error`方法来自定义错误类型,同时也可以传递不同的参数对错误状态进行更详细的说明,例如: 13 | 14 | ``` 15 | type AError struct { 16 | MoreInfo string 17 | } 18 | 19 | func (err *AError) Error() string { 20 | return fmt.Sprintf("Basic Error Info: %s", err.MoreInfo) 21 | } 22 | 23 | ``` 24 | 此外,当使用`fmt.Print`函数打印时,会自动的调用`Error`方法。 25 | # errors 26 | 但是对于大多数的自定义错误,只需要简单的错误描述,上面的声明方法过于繁琐。所以在errors包中提供了一种更简单的方法对错误进行自定义。 27 | 28 | `errors.New`接收一个字符串,并返回一个错误对象,该错误对象的`Error`方法返回该字符串,函数声明如下: 29 | 30 | ``` 31 | func New(text string) error 32 | ``` 33 | 有了这个函数,就可以像这样自定义错误对象了: 34 | 35 | ``` 36 | var ( 37 | AError = errors.New("AError") 38 | BError = errors.New("BError") 39 | CError = errors.New("CError") 40 | ) 41 | ``` 42 | # 更多内容 43 | - [[1] Error and Go](https://blog.golang.org/error-handling-and-go) -------------------------------------------------------------------------------- /index/suffixarray.md: -------------------------------------------------------------------------------- 1 | # 概述 2 | suffixarray模块提供了基于前缀数组的子串检索功能,能够在byte数组中检索指定子串,并获得其索引下标。 3 | 4 | # 创建前缀数组 5 | 可用通过New方法创建一个前缀数组,方法声明如下: 6 | 7 | ``` 8 | func New(data []byte) *Index 9 | ``` 10 | 此外可以通过其Bytes方法,获取原始byte数组,方法声明如下: 11 | 12 | ``` 13 | func (x *Index) Bytes() []byte 14 | ``` 15 | 16 | # 数据检索 17 | Index对象上提供了两种检索方法,FindAllIndex和Lookup。 18 | 19 | 其中FindAllIndex接收一个正则表达式,并返回长度不超过n的匹配索引列表,n<0时返回全部结果,方法声明如下: 20 | 21 | ``` 22 | func (x *Index) FindAllIndex(r *regexp.Regexp, n int) (result [][]int) 23 | ``` 24 | 25 | 而Lookup方法接收一个byte列表,返回长度不超过n的匹配索引列表,n<0时返回全部结果,方法声明如下: 26 | 27 | ``` 28 | func (x *Index) Lookup(s []byte, n int) (result []int) 29 | ``` 30 | 31 | 下面是一个简单的使用实例 32 | 33 | ``` 34 | package main 35 | 36 | import ( 37 | "index/suffixarray" 38 | "fmt" 39 | "sort" 40 | ) 41 | 42 | func main() { 43 | 44 | source := []byte("hello world, hello china") 45 | index := suffixarray.New(source) 46 | 47 | offsets := index.Lookup([]byte("hello"), -1) 48 | 49 | sort.Ints(offsets) 50 | 51 | fmt.Printf("%v", offsets) 52 | 53 | } 54 | ``` 55 | 56 | -------------------------------------------------------------------------------- /path/path.md: -------------------------------------------------------------------------------- 1 | # 概述 2 | path包提供了许多辅助函数来处理UNIX系统文件路径, 3 | # 辅助函数 4 | 一个unix文件路径有如下格式`/`分别对应于目录路径和基础路径,当这个路径表示一个文件时,BaseName就对应于文件名。 5 | 6 | 其中Base函数获取一个路径的BaseName,Dir函数获取一个路径的DirName;具体函数声明如下: 7 | 8 | ``` 9 | func Base(path string) string 10 | 11 | func Dir(path string) string 12 | ``` 13 | 在UNIX文件系统中,一个完整文件名由文件名和文件后缀组成,比如.go,.c;Ext函数可以用于获取路径中的后缀名。 14 | 15 | ``` 16 | func Ext(path string) string 17 | ``` 18 | 对于一个目录和文件还有绝对路径和相对路径的概念,绝对路径就是从根目录开始的完整路径,比如`/a/b/c`;相对路径就是相对于当前目录的路径,比如`a/b/c`,`../a/b/c`。使用IsAbs可以判断一个路径是否是绝对路径;具体函数声明如下: 19 | 20 | ``` 21 | func IsAbs(path string) bool 22 | ``` 23 | 最后,path包还提供了两个函数用于组合和拆分一个文件路径。Split函数将路径拆分为目录名和文件名;Join函数以`/`为分隔符将多个字符串进行连接;函数声明如下: 24 | 25 | ``` 26 | func Split(path string) (dir, file string) 27 | 28 | func Join(elem ...string) string 29 | ``` 30 | 31 | # 实例 32 | 33 | ``` 34 | package main 35 | 36 | import ( 37 | "path" 38 | "fmt" 39 | ) 40 | 41 | func main() { 42 | p := "foo/bar.tar" 43 | 44 | sDir, sBase := path.Split(p) 45 | 46 | fmt.Println(sDir) 47 | fmt.Println(sBase) 48 | fmt.Println(path.Ext(p)) 49 | } 50 | ``` 51 | 52 | 53 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 preyta 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 | -------------------------------------------------------------------------------- /path/filepath.md: -------------------------------------------------------------------------------- 1 | # 概述 2 | `filepath`包的功能和`path`包类似,但是对于不同操作系统提供了更好的支持。filepath包能够自动的根据不同的操作系统文件路径进行转换,所以如果你有跨平台的需求,你需要使用`filepath`。 3 | # 与`path`包相同的函数 4 | `filepath`包中的函数和path包很类似,其中对应函数的功能相同,只是一个可以跨平台,一个不能,所以这里不详细展开,可以从 [path](https://github.com/preytaren/go-doc-zh/blob/master/path/path.md) 中获取这些函数的详细说明。主要函数如下: 5 | 6 | - func Base(path string) string 7 | - func Dir(path string) string 8 | - func Ext(path string) string 9 | - func Join(elem ...string) string 10 | - func Split(path string) (dir, file string) 11 | 12 | # 其他函数 13 | 剩下的还有两个函数值得一说,一个是`Abs`函数,可以将一个文件路径转换为绝对路径。函数声明如下: 14 | 15 | ``` 16 | func Abs(path string) (string, error) 17 | ``` 18 | 19 | 另一个是`Walk`函数,和`filepath`包中的其他函数不同,它并不对文件路径字符串进行操作,而可以访问更多文件信息。它通过遍历的方式对目录中的每个子路径进行访问,函数接收两个参数,一个是路径名,另一个是遍历函数`WalkFunc`,函数声明如下: 20 | 21 | ``` 22 | func Walk(root string, walkFn WalkFunc) error 23 | 24 | type WalkFunc func(path string, info os.FileInfo, err error) error 25 | ``` 26 | WalkFunc接收三个参数,分别是当前子路径,路径的`FileInfo`对象,以及一个可能的访问错误信息。下面是一个简单的示例,打印了当前目录的所有文件。 27 | 28 | ``` 29 | package main 30 | 31 | import ( 32 | "path/filepath" 33 | "os" 34 | "fmt" 35 | ) 36 | 37 | func main() { 38 | 39 | filepath.Walk(".", func(path string, info os.FileInfo, err error) error { 40 | if err != nil { 41 | return err 42 | } 43 | fmt.Println(info.Name()) 44 | return nil 45 | }) 46 | } 47 | ``` 48 | 49 | -------------------------------------------------------------------------------- /flag/flag.md: -------------------------------------------------------------------------------- 1 | # 概述 2 | 在运行命令行程序时,通常通过命令行参数对程序运行进行配置。在go程序中使用flag包,可以快速构建命令行程序,对于程序使用者只需要声明所需命令行参数。 3 | # 使用示例 4 | 创建命令行程序可以分为两步: 5 | 6 | - 声明命令行参数 7 | - 运行`flag.Parse`,对参数进行解析 8 | 9 | 然后就可以读取命令行参数了。例如如下程序可以创建一个命令行程序`demo --foo hello --bar world` 10 | 11 | ``` 12 | package main 13 | 14 | import ( 15 | "fmt" 16 | "flag" 17 | ) 18 | 19 | func main() { 20 | foo := flag.String("foo", false, "Foo") 21 | bar := flag.String("bar", "", "Bar") 22 | 23 | flag.Parse() 24 | fmt.Print(*foo, *bar) 25 | } 26 | ``` 27 | # VarXXX 和 XXX 28 | flag包中对多中提供了两类参数声明函数,下面以String类型为例,两个函数声明如下: 29 | 30 | ``` 31 | func String(name string, value string, usage string) *string 32 | 33 | func StringVar(p *string, name string, value string, usage string) 34 | ``` 35 | String函数接收三个参数,分别是命令行参数名,即`--`;参数默认值;参数描述,也就是help命令显示的帮助描述;并返回对应参数值的指针。StringVar与之不同在于将返回值改为函数参数。 36 | 37 | 除了对string类型的支持外,flag包还提供了多个类似的函数用于解析不同类型的参数,函数名进行对应的替换即可,包括: 38 | 39 | - Int,Int64,Uint 40 | - Bool 41 | - Float 42 | - Duration 43 | 44 | # 其他 45 | 以上几乎就是使用flag包的全部了,flag包中的其他函数可以直接对其底层实现进行操作,普通命令行程序中不会使用。 46 | 47 | 在实际使用中,建议将参数声明部分放到var代码段中,对上面的代码进行修改后如下所示。 48 | 49 | ``` 50 | package main 51 | 52 | import ( 53 | "fmt" 54 | "flag" 55 | ) 56 | 57 | var ( 58 | foo = flag.Bool("foo", false, "Foo") 59 | bar = flag.String("bar", "", "Bar") 60 | ) 61 | 62 | func main() { 63 | flag.Parse() 64 | fmt.Print(*foo, *bar) 65 | } 66 | ``` 67 | -------------------------------------------------------------------------------- /io/ioutil.md: -------------------------------------------------------------------------------- 1 | # 概述 2 | 前面的io包提供了对输入输出设备最基本的抽象,而ioutil在io包的基础上提供了一系列的函数来应对具体的场景。 3 | # 数据读取 4 | ioutil一共提供了三个数据读取的函数,分别是: 5 | - ReadAll,从一个io.Reader读取所有数据,并返回一个字节数组 6 | - ReadllDir,从一个目录读取数据,并得到这个目录里的文件对象列表 7 | - ReadFile,读取指定文件的内容,并返回一个字节数组 8 | 9 | 其函数声明如下: 10 | 11 | ``` 12 | func ReadAll(r io.Reader) ([]byte, error) 13 | 14 | func ReadDir(dirname string) ([]os.FileInfo, error) 15 | 16 | func ReadFile(filename string) ([]byte, error) 17 | ``` 18 | 19 | 20 | 可以看到上面的三个函数分别对应于三个特定的场景,下面以ReadFile为例对其使用进行说明 21 | 22 | ``` 23 | package main 24 | 25 | import ( 26 | "fmt" 27 | "io/ioutil" 28 | ) 29 | 30 | func main() { 31 | content, err := ioutil.ReadFile("demo.txt") 32 | 33 | if err != nil { 34 | fmt.Fatal(err) 35 | } 36 | 37 | fmt.Print(content) 38 | } 39 | ``` 40 | 41 | # 临时文件 42 | ioutil也支持创建临时目录和文件,分别通过TempDir和TempFile函数实现,文件和目录不会自动销毁,需要使用者自行对创建的临时文件进行处理,可以使用`os.Remove()`删除文件。 43 | 44 | 使用TempDir可以创建一个临时的目录,函数接收父目录名和目录前缀名作为参数,创建一个临时目录并返回它的名字,具体函数声明如下: 45 | 46 | 47 | ``` 48 | func TempDir(dir, prefix string) (name string, err error) 49 | ``` 50 | 51 | 使用TempFile可以创建一个临时的文件,同样可以指定路径和文件名前缀,函数返回这个文件对象,可以直接对文件进行读写。具体函数声明如下: 52 | 53 | 54 | ``` 55 | func TempFile(dir, prefix string) (f *os.File, err error) 56 | ``` 57 | 58 | # 文件写入 59 | 和文件读取类似,WriteFile可以对文件进行写入,函数接收三个参数,分别是要写入的文件名,写入的数据,以及一个文件信息标识位。具体的标识位可以见文档 [FileMode](https://golang.org/pkg/os/#FileMode)。WriteFile函数声明如下: 60 | ``` 61 | func WriteFile(filename string, data []byte, perm os.FileMode) error 62 | ``` -------------------------------------------------------------------------------- /strconv/strconv.md: -------------------------------------------------------------------------------- 1 | # 概述 2 | strconv包中包含了一系列辅助函数,用于字符串类型变量和其他类型变量之间的转换。 3 | # Atoi & Itoa 4 | 其中最常用的就是字符串和整型变量的相互转换。Atoi(string to int),Itoa(int to string)分别是字符串转整型和整型转字符串(注意:这个两个函数中的整型变量都是十进制整数)。函数声明如下: 5 | 6 | ``` 7 | func Atoi(s string) (int, error) 8 | // 当字符串格式错误时会返回strcov.NumError 9 | 10 | func Itoa(i int) string 11 | ``` 12 | # ParseX 13 | 当需要将字符串转换为其他类型变量时,就需要使用到strconv中的ParseX系列函数,一共有四个函数`ParseBool,ParseInt,ParseFloat,ParseUint`,下面是详细的函数说明: 14 | 15 | - `func ParseBool(str string) (bool, error)` 16 | 17 | 函数接收一个字符串作为参数,返回转换后的布尔值,如输入格式错误,返回NumError(它接受真值:`1, t, T, TRUE, true`, True;假值:`0, f, F, FALSE, false, False`) 18 | 19 | - `func ParseInt(s string, base int, bitSize int) (i int64, err error)` 20 | 21 | 函数接收三个参数,第一个是需要转换的字符串;第二个是转换后整型变量的底数(0,2-36),一般取值是0,2,8,16;第三个是整型变量的大小(0-64),一般来说,0、8、16、32 和 64 分别代表 int、int8、int16、int32 和 int64,如果实际数据超出了bitSize,会产生数据溢出和截断。函数返回值和ParseBool类似,返回转换后的整型变量,如输入格式错误,返回NumError。 22 | 23 | 实际上Atoi等价于`ParseInt(s, 10, 0)` 24 | 25 | - `func ParseUint(s string, base int, bitSize int) (n uint64, err error)` 26 | 27 | ParseUint和ParseInt类似,针对无符号整型。 28 | 29 | - `func ParseFloat(s string, bitSize int) (float64, error)` 30 | 31 | 函数接收两个参数,第一个是输入字符串,第二个是转换后浮点数的二进制长度,两个典型值是32和64;无论bitSize取值如何,函数返回值类型都是float64。 32 | 33 | 34 | # FormatX 35 | 与ParseX相对于,可以使用FormatX系列函数可以将其他类型变量转换为字符串类型。与上面相对应也有四个函数: 36 | 37 | - `func FormatBool(b bool) string` 38 | - `func FormatInt(i int64, base int) string` 39 | - `func FormatUint(i uint64, base int) string` 40 | 41 | 前面三个函数可以和ParseX函数相对应,这里不再具体阐述,与ParseX函数不同,从其他类型转换为字符串总会成功而不会返回错误。 42 | 43 | - `func FormatFloat(f float64, fmt byte, prec, bitSize int) string` 44 | 45 | FormatFloat函数接收四个参数,第一个参数是输入浮点数,第二个是浮点数的显示格式(可以是`b, e, E, f, g, G`),第三个是浮点数精度(对于不同的fmt参数,具有不同的含义),最后一个表示浮点数的大小。对于FormatFloat参数的详细介绍见 [GoDoc FormatFloat](https://golang.org/pkg/strconv/#FormatFloat) 46 | -------------------------------------------------------------------------------- /container/list.md: -------------------------------------------------------------------------------- 1 | # 概述 2 | container/list包实现了基本的双向链表功能,包括元素的插入、删除、移动功能 3 | # 链表元素 4 | 链表中元素定义如下: 5 | 6 | ``` 7 | type Element struct { 8 | Value interface{} 9 | } 10 | 11 | func (e *Element) Next() *Element 12 | func (e *Element) Prev() *Element 13 | ``` 14 | 通过Value属性来获取元素的值,此外Element还有两个方法Next和Prev分别获取当前元素的前一个元素和后一个元素。 15 | # 成员函数 16 | ## 初始化 17 | 可以通过调用New函数或者Init方法来初始化一个空的list,此外Init也可以重置一个list。函数声明如下: 18 | 19 | ``` 20 | func New() *List 21 | func (l *List) Init() *List 22 | ``` 23 | 24 | ## 遍历 25 | 对于链表来说,遍历是最常用的操作,遍历操作一共三步: 26 | 27 | - 第一步,获取一个遍历起始点;使用Front或Back获取一个链表的头和尾,其函数声明如下: 28 | 29 | ``` 30 | func (l *List) Front() *List 31 | func (l *List) Back() *List 32 | ``` 33 | - 第二步,从当前元素转到下一个元素;使用Element上的Prev和Next方法向前或向后移动一个元素。 34 | - 第三步,遍历结束条件;遍历结束条件需要人为判断,一般比较当前元素是否为结束元素。 35 | ## 插入 36 | container/list中提供了两种插入方法,InsertAfter和InsertBefore,分别用于在一个元素前或后插入元素,方法声明如下: 37 | 38 | ``` 39 | func (l *List) InsertAfter(v interface{}, mark *Element) *Element 40 | func (l *List) InsertBefore(v interface{}, mark *Element) *Element 41 | ``` 42 | ## 添加 43 | PushBack和PushFront用于在一个链表的头和尾添加元素(此外还有一次性添加一个list的PushBackList和PushFrontList),方法声明如下: 44 | 45 | ``` 46 | func (l *List) PushBack(v interface{}) *Element 47 | func (l *List) PushFront(v interface{}) *Element 48 | ``` 49 | ## 删除 50 | 可以通过Remove方法,删除链表上指定元素,方法声明如下: 51 | 52 | ``` 53 | func (l *List) Remove(e *Element) interface{} 54 | ``` 55 | # 使用实例 56 | 实际上,将前面的内容整合起来,就可以实现一个简单的遍历链表的功能。下面是一个简单的遍历实现 57 | ``` 58 | package main 59 | 60 | 61 | import ( 62 | "fmt" 63 | "container/list" 64 | ) 65 | 66 | 67 | func main() { 68 | link := list.New() 69 | 70 | for i := 0; i <= 10; i++ { 71 | link.PushBack(i) 72 | }// 73 | 74 | for p := link.Front(); p != link.Back(); p = p.Next() { 75 | fmt.Println("Number", p.Value) 76 | } 77 | 78 | } 79 | ``` 80 | # 参考文献 81 | - [container/list](https://golang.org/pkg/container/list/#List.MoveAfter) 82 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # GoStdDoc 2 | Go标准库学习笔记,对Golang标准库各模块进行整理(持续更新中) 3 | # 目录 4 | * 数据结构相关 5 | * [堆 (container/heap)](https://github.com/preytaren/go-doc-zh/blob/master/container/heap.md) 6 | * [双向链表 (container/list)](https://github.com/preytaren/go-doc-zh/blob/master/container/list.md) 7 | * [环形链表 (container/ring)](https://github.com/preytaren/go-doc-zh/blob/master/container/ring.md) 8 | * 算法相关 9 | * [排序 (sort)](https://github.com/preytaren/go-doc-zh/blob/master/sort.md) 10 | * 并发与同步 11 | * [锁,信号量 (sync)](https://github.com/preytaren/go-doc-zh/blob/master/sync/sync.md) 12 | * [原子操作 (sync/atomic)](https://github.com/preytaren/go-doc-zh/blob/master/sync/atomic.md) 13 | * [上下文 (context)](https://github.com/preytaren/go-doc-zh/blob/master/context/context.md) 14 | * 日志 15 | * [日志(log)](https://github.com/preytaren/go-doc-zh/blob/master/log/log.md) 16 | * IO 17 | * [IO](https://github.com/preytaren/go-doc-zh/blob/master/io/io.md) 18 | * [IO工具(ioutil)](https://github.com/preytaren/go-doc-zh/blob/master/io/ioutil.md) 19 | * [带缓冲IO(ioutil)](https://github.com/preytaren/go-doc-zh/blob/master/bufio/bufio.md) 20 | * 网络 21 | * [HTTP(net/http)](https://github.com/preytaren/go-doc-zh/blob/master/net/http/http.md) 22 | * 字符串 23 | * [字符串(strings)](https://github.com/preytaren/go-doc-zh/blob/master/strings/strings.md) 24 | * [字符串转换(strconv)](https://github.com/preytaren/go-doc-zh/blob/master/strconv/strconv.md) 25 | * [字符串搜索(index/suffixarray)](https://github.com/preytaren/go-doc-zh/blob/master/index/suffixarray.md) 26 | * 操作系统相关 27 | * [文件路径(path)](https://github.com/preytaren/go-doc-zh/blob/master/path/path.md) 28 | * [跨平台文件路径(filepath)](https://github.com/preytaren/go-doc-zh/blob/master/path/filepath.md) 29 | * 命令行 30 | * [命令行(flag)](https://github.com/preytaren/go-doc-zh/blob/master/flag/flag.md) 31 | * 错误处理 32 | * [错误处理(errors)](https://github.com/preytaren/go-doc-zh/blob/master/errors/errors.md) 33 | -------------------------------------------------------------------------------- /sort.md: -------------------------------------------------------------------------------- 1 | # 概述 2 | sort包实现了对列表的排序以及在有序列表上的二分查找等操作 3 | ## 通用排序函数 4 | ### 接口实现 5 | 要使用sort包的各个函数,需要实现sort.Interface,定义如下: 6 | 7 | ``` 8 | type Interface interface { 9 | Len() int // 返回当前元素个数 10 | Less(i, j int) bool. // 判断第i个元素是小于第j个元素 11 | Swap(i, j int) // 交换两个元素 12 | } 13 | ``` 14 | ### Sort 15 | sort包最核心的函数,Sort,用于对一个列表上的元素进行排序,Sort函数会在原有列表上进行排序,函数声明如下: 16 | 17 | ``` 18 | func Sort(data Interface) 19 | ``` 20 | ### Stable 21 | 相较于Sort函数,Stable函数也用于对一个列表进行排序,但是它额外提供保证排序算法是稳定的,也就是排序前后值相同的两个元素相对位置不发生变化,函数声明和Sort类似。 22 | 23 | ``` 24 | func Stable(data Interface) 25 | ``` 26 | ### Slice 27 | Slice函数用于对一个Slice进行排序,这是实际使用中更为常用的一个函数,函数接收两个参数。第一个是需要排序的Slice;第二个是Slice元素比较函数,它类似于前面sort.Interface里的Less方法。函数声明如下: 28 | 29 | ``` 30 | func Slice(slice interface{}, less func(i, j int) bool) 31 | ``` 32 | 33 | ### Reverse 34 | Reverse函数用于翻转一个列表并返回翻转后的列表,函数声明如下: 35 | 36 | ``` 37 | func Reverse(data Interface) Interface 38 | ``` 39 | 40 | 41 | ### IsSorted 42 | IsSorted函数用于判断一个列表是否有序,函数声明如下: 43 | 44 | ``` 45 | func IsSorted(data Interface) bool 46 | ``` 47 | 48 | ### Search 49 | Search函数可以在一个有序列表上进行二分查找操作,它接收两个参数,第一个为从第一个元素开始搜索的元素个数;第二个参数是一个函数,通过接收一个函数f作为参数,找到使得f(x)==true的元素,函数声明如下: 50 | 51 | ``` 52 | func Search(n int, f func(int) bool) int 53 | ``` 54 | 55 | ## 特定类型方法 56 | 57 | 除了上面的通用函数之外,sort包还对几个常用基础类型(int,float,string和slice)的排序提供了支持,对于每个类型,分别实现了上一节的各个函数。具体函数定义见 [Package sort](https://golang.org/pkg/sort/) 58 | 59 | # 使用示例 60 | 下面以Slice排序为例进行说明,示例中声明了一个类型Person,根据Person.Age字段对数据进行排序。 61 | 62 | ``` 63 | package main 64 | 65 | import ( 66 | "sort" 67 | "fmt" 68 | ) 69 | 70 | type Person struct { 71 | Name string 72 | Age int 73 | } 74 | 75 | 76 | func main() { 77 | 78 | data := []Person{ 79 | {"Alice", 20}, 80 | {"Bob", 15}, 81 | {"Jane", 30}, 82 | } 83 | 84 | sort.Slice(data, func(i, j int) bool { 85 | return data[i].Age < data[j].Age 86 | }) 87 | 88 | for _, each := range data { 89 | fmt.Println("Name:", each.Name, "Age:", each.Age) 90 | } 91 | } 92 | ``` -------------------------------------------------------------------------------- /sync/atomic.md: -------------------------------------------------------------------------------- 1 | # 概述 2 | 为了保证并发安全,除了使用临界区之外,还可以使用原子操作。顾名思义这类操作满足原子性,其执行过程不能被中断,这也就保证了同一时刻一个线程的执行不会被其他线程中断,也保证了多线程下数据操作的一致性。 3 | 4 | 在atomic包中对几种基础类型提供了原子操作,包括int32,int64,uint32,uint64,uintptr,unsafe.Pointer。对于每一种类型,提供了五类原子操作分别是 5 | 6 | - Add, 增加和减少 7 | - CompareAndSwap, 比较并交换 8 | - Swap, 交换 9 | - Load , 读取 10 | - Store, 存储 11 | 12 | 具体函数名由原子操作名和类型关键字组成,例如对于int32的Add操作,函数名为AddInt32,其他函数名以此类推,后文中仅以int32类型系列函数为例进行说明,其他类型函数功能类似。 13 | 14 | # AddInt32 15 | AddInt32可以实现对元素的原子增加或减少,其函数定义如下,函数接收两个参数,分别是需要修改的变量的地址和修改的差值,函数会直接在传递的地址上进行修改操作,此外函数会返回修改之后的新值: 16 | 17 | ``` 18 | func AddInt32(addr *int32, delta int32) (new int32) 19 | ``` 20 | 21 | 传递一个正整数增加值,负整数减少值,函数的通常使用方法如下。需要注意的是当你处理unint32和unint64时,由于delta参数类型被限定,不能直接传输负数,所以需要利用二进制补码机制,其中N为需要减少的正整数值。 22 | 23 | ``` 24 | package main 25 | 26 | import ( 27 | "sync/atomic" 28 | "fmt" 29 | ) 30 | 31 | func main() { 32 | var a int32 33 | a += 10 34 | atomic.AddInt32(&a, 10) 35 | fmt.Println(a == 20) // true 36 | 37 | var b uint32 38 | b += 20 39 | atomic.AddUint32(&b, ^uint32(10-1)) 40 | // 等价于 b -= 10 41 | // atomic.Adduint32(&b, ^uint32(N-1)) 42 | fmt.Println(b == 10) // true 43 | } 44 | 45 | ``` 46 | 47 | 48 | # CompareAndSwapInt32 49 | CompareAndSwapInt32函数接收三个参数,一个是需要交换(赋值)的变量地址,然后是一个待比较的值(旧值),最后是需要交换的值(新值),函数返回一个布尔值表示交换结果。函数声明如下: 50 | 51 | ``` 52 | func CompareAndSwapInt32(addr *int32, old, new int32) (swapped bool) 53 | ``` 54 | 55 | 比较并交换(Compare and Swap,CAS)是一个常用的原子操作,首先判断当前变量的值和旧值是否相等(也就是变量值是否被其他线程所修改),如果相等则用新值替换掉原来的值,否则就不进行替换操作。 56 | # SwapInt32 57 | 类似于CAS,Swap也是将一个新值赋给某变量,区别在于CAS会比较当前变量与旧值,来决定赋值与否;Swap会直接执行赋值操作,并将原值作为返回值返回,其函数声明如下: 58 | 59 | ``` 60 | func SwapInt32(addr *int32, new int32) (old int32) 61 | ``` 62 | 63 | # LoadInt32 & StoreInt32 64 | Load和Store操作对应与变量的原子性读写,许多变量的读写无法在一个时钟周期内完成,而此时执行可能会被调度到其他线程,无法保证并发安全。函数的类型声明如下: 65 | 66 | ``` 67 | func LoadInt32(addr *int32) (val int32) 68 | 69 | func StoreInt32(addr *int32, val int32) 70 | ``` 71 | 72 | Load函数参数为需要读取的变量地址,返回值为读取的值;Store函数参数为需要存储的变量地址,以及需要写入的值,不同于CAS,Store操作不关心变量的原始值是否被修改,只是简单的执行写入,所以Store函数总能成功返回。 -------------------------------------------------------------------------------- /container/ring.md: -------------------------------------------------------------------------------- 1 | # 概述 2 | Ring是一种循环链表结构,没有头尾,从任意一个节点出发都可以遍历整个链。其定义如下,Value表示当前节点的值: 3 | 4 | ``` 5 | type Ring struct { 6 | Value interface{} 7 | } 8 | ``` 9 | # 类型方法 10 | ## New 11 | Ring.New用于创建一个新的Ring,接收一个整形参数,用于初始化Ring的长度,其方法定义如下: 12 | 13 | ``` 14 | func New(n int) *Ring 15 | ``` 16 | 17 | ## Next & Prev 18 | 作为一个链表,最重要的操作进行遍历,可以通过Next和Prev方法获取当前节点的上一个节点和下一个节点,方法定义如下: 19 | 20 | ``` 21 | func (r *Ring) Next() *Ring 22 | func (r *Ring) Prev() *Ring 23 | ``` 24 | 通过这两个方法可以对一个ring进行遍历,首先保存当前节点,然后依次访问下一个节点,直到回到起始节点,代码实现如下: 25 | 26 | ``` 27 | p := ring.Next() 28 | // do something with first element 29 | for p != ring { 30 | // do something with current element 31 | 32 | p = p.Next() 33 | } 34 | ``` 35 | ## Link & Unlink 36 | Link将两个ring连接到一起,而Unlink将一个ring拆分为两个,移除n个元素并组成一个新的ring,这两个操作组合起来可以对多个链表进行管理,方法声明如下: 37 | 38 | ``` 39 | func (r *Ring) Link(s *Ring) *Ring 40 | func (r *Ring) Unlink(n int) *Ring 41 | ``` 42 | ## Do 43 | 前面通过Next方法对ring进行了遍历,由于这类操作的广泛存在,所以Ring包中还提供了一个额外的方法Do,方法接收一个函数作为参数,方法声明如下: 44 | 45 | 46 | ``` 47 | func (r *Ring) Do(f func(interface{})) 48 | ``` 49 | 在调用Ring.Do时,会依次将每个节点的Value当做参数调用这个函数,实际上这是策略方法的应用,通过传递不同的函数,可以在同一个ring上实现多种不同的操作。下面展示一个简单的遍历打印程序。 50 | ``` 51 | package main 52 | 53 | import ( 54 | "container/ring" 55 | "fmt" 56 | ) 57 | 58 | func main() { 59 | r := ring.New(10) 60 | 61 | for i := 0; i < 10; i++ { 62 | r.Value = i 63 | r = r.Next() 64 | } 65 | 66 | sum := SumInt{} 67 | r.Do(func(i interface{}) { 68 | fmt.Println(i) 69 | }) 70 | } 71 | ``` 72 | 73 | 除了简单的无状态程序外,也可以通过结构体保存状态,例如下面是一个对ring上值求和的程序。 74 | 75 | ``` 76 | package main 77 | 78 | import ( 79 | "container/ring" 80 | "fmt" 81 | ) 82 | 83 | type SumInt struct { 84 | Value int 85 | } 86 | 87 | func (s *SumInt) add(i interface{}) { 88 | s.Value += i.(int) 89 | } 90 | 91 | func main() { 92 | r := ring.New(10) 93 | 94 | for i := 0; i < 10; i++ { 95 | r.Value = i 96 | r = r.Next() 97 | } 98 | 99 | sum := SumInt{} 100 | r.Do(sum.add) 101 | fmt.Println(sum.Value) 102 | } 103 | ``` 104 | 105 | -------------------------------------------------------------------------------- /log/log.md: -------------------------------------------------------------------------------- 1 | # 概述 2 | log 模块用于在程序中输出日志,它的使用十分简单,类似于fmt中的Print,一个最简单的示例如下: 3 | 4 | ``` 5 | package main 6 | 7 | import "log" 8 | 9 | func main() { 10 | log.Print("Hello World") 11 | } 12 | ``` 13 | 上面的程序会在命令行打印一条日志: 14 | 15 | ``` 16 | >>> 2018/05/16 16:48:06 Hello World 17 | ``` 18 | 19 | # Logger 20 | Logger是写入日志的基本组件,log模块中存在一个标准Logger,可以直接通过log进行访问,所以在上一节的例子中可以直接使用log.Print进行日志进行输出。但是在实际使用中,不同类型的日志可能拥有需求,仅标准Logger不能满足日志记录的需求,通过创建不同的Logger可以将不同类型的日志分类输出。使用logger前需要首先通过New函数创建一个Logger对象,函数声明如下: 21 | 22 | ``` 23 | func New(out io.Writer, prefix string, flag int) *Logger 24 | ``` 25 | 函数接收三个参数分别是日志输出的IO对象,日志前缀和日志包含的通用信息标识位,通过对它们进行设置可以对Logger进行定制。其中IO对象通常是标准输出os.Stdout,os.Stderr,或者绑定到文件的IO。日志前缀和信息标识位可以对日志的格式进行设置。 26 | 27 | 一条日志由三个部分组成,其结构如下: 28 | 29 | ``` 30 | {日志前缀} {标识1} {标识2} ... {标识n} {日志内容} 31 | ``` 32 | 33 | - 日志前缀,通过prefix参数设置,可以是任意字符串 34 | - 标识,通过flags参数设置,当某个标识被设置,会在日志中进行显示,log模块中定义了如下标识,多个标识通过按位或进行组合: 35 | - Ldate 显示当前日期(当前时区) 36 | - Ltime 显示当前时间(当前时区) 37 | - Lmicroseconds 显示当前时间(微秒) 38 | - Llongfile 包含路径的完整文件名 39 | - Lshortfile 不包含路径的文件名 40 | - LUTC Ldata和Ltime使用UTC时间 41 | - LstdFlags 标准Logger的标识,等价于 Ldate | Ltime 42 | 43 | ``` 44 | package main 45 | 46 | import ( 47 | "os" 48 | "log" 49 | ) 50 | 51 | func main() { 52 | prefix := "[THIS IS THE LOG]" 53 | logger := log.New(os.Stdout, prefix, log.LstdFlags | log.Lshortfile) 54 | logger.Print("Hello World") 55 | } 56 | ``` 57 | 上面的程序将会输出如下内容,可以看到日志由上述三个部分组成。 58 | 59 | ``` 60 | [THIS IS THE LOG]2018/05/16 17:12:19 log.go:11: Hello World 61 | ``` 62 | 63 | # 更多的输出方式 64 | log模块中日志输出分为三类,Print,Fatal,Panic。Print是普通输出;Fatal是在执行完Print之后,执行 os.Exit(1);Panic是在执行完Print之后调用panic()方法。 65 | 66 | 除了基础的Print之外,还有Printf和Println方法对输出进格式化,对于Fatal和Panic也是类似,具体的函数声明 [Log Index](https://www.godoc.org/log#pkg-index) 67 | ## 日志分级 68 | Go的log模块没有对日志进行分级的功能,对于这部分需求可以在log的基础上进行实现,下面是一个简单的INFO方法实现。 69 | 70 | ``` 71 | package main 72 | 73 | import ( 74 | "os" 75 | "log" 76 | ) 77 | 78 | func main() { 79 | var ( 80 | logger = log.New(os.Stdout, "INFO: ", log.Lshortfile) 81 | infof = func(info string) { 82 | logger.Print(info) 83 | } 84 | ) 85 | infof("Hello world") 86 | } 87 | ``` 88 | 89 | 90 | -------------------------------------------------------------------------------- /context/context.md: -------------------------------------------------------------------------------- 1 | # 概述 2 | context也是并发环境的一个常用标准库,它用于在并发环境下在协程之间安全的传递某些上下文信息。 3 | 4 | 一个经典的应用场景是服务器模型,当服务器处理接收到的请求时,通常需要并发的运行多个子任务,例如访问服务器,请求授权等。而这些任务都会以子协程的方式运行,也就是说一个请求绑定了多个协程,这些协程需要共享或传递某些请求相关的数据;此外当请求被撤销时,也需要有一种机制保证每个子协程能够安全的退出。而context包就给提供了上面说到的这些功能。 5 | # Context 6 | Context是一个上下文对象,其声明如下: 7 | 8 | ``` 9 | type Context interface { 10 | Deadline() (deadline time.Time, ok bool) 11 | // 获取deadline 12 | Done() <-chan struct{} 13 | // 14 | Err() error 15 | Value(key interface{}) interface{} 16 | } 17 | ``` 18 | ## 创建Context 19 | 在context中有两种基础的Context,分别通过Backgroud和TODO函数创建,下面是具体的函数声明: 20 | 21 | ``` 22 | func Background() Context 23 | 24 | func TODO() Context 25 | ``` 26 | 通常情况下,使用Backgroud函数即可,调用函数可以得到一个Context,但是这个Context不能够直接使用,只是作为一个基础的根Context使用,所有的Context都需要从这个Context上衍生。 27 | # 衍生 Context 28 | 要创建一个可使用的Context,你需要使用下面的三个函数,在根Context衍生出新的Context。当然,由于Context是以树状结构存在的,你也可以通过调用这些函数在任何一个Context上创建子Context。 29 | 30 | ## WithCancel 31 | WithCancel会返回一个可以取消的Context,函数声明如下: 32 | 33 | ``` 34 | func WithCancel(parent Context) (ctx Context, cancel CancelFunc) 35 | ``` 36 | 函数接收一个Contex作为参数,返回两个值,第一个是新创建的Context,结构上来看,这个Context是输入Context的子节点;第二个参数是cancel函数,用于向这个Context发送cancel信号。由于Context存在继承关系,当父节点调用cancel子节点的cancel也会被调用。 37 | ### CancelFunc & Done 38 | 这里介绍一下CancelFunc,Done这一对函数,类似于signal,wait;CancelFunc函数会向Context发送cancel信号;而Done方法返回一个通道,若当前Context被cancel,那么这个通道会被关闭;也就是说,通过CancelFunc和Done的协作,可以对子协程传递cancel信号,一个常用的代码段如下: 39 | 40 | ``` 41 | func Stream(ctx context.Context, out chan<- Value) error { 42 | for { 43 | v, err := DoSomething(ctx) 44 | if err != nil { 45 | return err 46 | } 47 | select { 48 | case <-ctx.Done(): 49 | return ctx.Err() 50 | case out <- v: 51 | } 52 | } 53 | } 54 | ``` 55 | 子协程不停地运行并检查当前任务是否被取消,若被取消则结束当前任务并返回。 56 | ## WithDeadLine & WithTimeout 57 | 和WithCancel类似,WithDeadLine和WithTimeout额外接收一个参数分别是消亡时间和超时时间。也就是说对于这两类Context,即使不主动取消,当发生超时时,该Context也会接收到cancel信号。函数声明如下: 58 | 59 | ``` 60 | func WithDeadline(parent Context, d time.Time) (Context, CancelFunc) 61 | 62 | func WithTimeout(parent Context, timeout time.Duration) (Context, CancelFunc) 63 | ``` 64 | 同样的,即使设置了很大的值,但是子Context的deadline和timeout也不会超过父Context的值。 65 | 66 | ## WithValue 67 | 这类Context用于在同一个上下文中传递数据,这个Context是不可取消的,其函数声明如下: 68 | 69 | ``` 70 | func WithValue(parent Context, key, val interface{}) Context 71 | ``` 72 | 除了Context参数外,还接收key和val参数用于保存数据,数据以键值对的方式存储;然后可以通过Context.Value(key)来获取对应的值。 73 | # 一些建议 74 | - 子协程不能cancel父协程的Context 75 | - Context需要显式的传递,而不是作为某个类型的一个字段 -------------------------------------------------------------------------------- /io/io.md: -------------------------------------------------------------------------------- 1 | # 概述 2 | IO是操作系统的基础概念,是对输入输出设备的抽象。Go语言的io库对这些功能进行了抽象,通过统一的接口对输入输出设备进行操作。 3 | 4 | # Reader 5 | Reader对象是对输入设备的抽象,一个Reader可以绑定到一个输入对象,并在这个输入设备上读取数据,其声明如下: 6 | 7 | ``` 8 | type Reader interface { 9 | Read(p []byte) (n int, err error) 10 | } 11 | ``` 12 | 除了基础的Reader类之外,io包中还有LimitReader,MultiReader和TeeReader。其中LimitReader只读取指定长度的数据;MultiReader用于聚合多个Reader,并依次进行读取;TeeReader将一个输入绑定到一个输出。具体声明如下: 13 | 14 | ``` 15 | func LimitReader(r Reader, n int64) Reader 16 | 17 | func MultiReader(readers ...Reader) Reader 18 | 19 | func TeeReader(r Reader, w Writer) Reader 20 | ``` 21 | 这些衍生Reader都以包装的方式进行使用,也就是传入一个Reader,在这个Reader上增加额外功能,然后返回这个新Reader。下面是一个简单的使用实例。 22 | 23 | ``` 24 | package main 25 | 26 | import ( 27 | "io" 28 | "strings" 29 | "os" 30 | ) 31 | 32 | func main() { 33 | r := strings.NewReader("some io.Reader stream to be read\n") 34 | lr := io.LimitReader(r, 4) 35 | io.Copy(os.Stdout, lr) 36 | } 37 | ``` 38 | ## ReadAtLeast & ReadFull 39 | 这两个函数用于从Reader里面读取数据到指定缓冲区,ReadAtLeast会读取至少n个字节的数据,ReadFull会读取直到数据填满整个缓冲区。其函数声明如下: 40 | 41 | ``` 42 | func ReadAtLeast(r Reader, buf []byte, min int) (n int, err error) 43 | 44 | func ReadFull(r Reader, buf []byte) (n int, err error) 45 | ``` 46 | 47 | 48 | # Writer 49 | Writer对象是对输出设备的抽象,一个Writer可以绑定到一个输出对象,并在这个输出设备上写入数据,其声明如下: 50 | 51 | ``` 52 | type Writer interface { 53 | Write(p []byte) (n int, err error) 54 | } 55 | ``` 56 | 和Reader类似,Writer也有MultiWriter,可以同步输出到多个Writer,声明如下: 57 | 58 | ``` 59 | func MultiWriter(writers ...Writer) Writer 60 | ``` 61 | ## WriteString 62 | WriteString函数用于向某个Writer写入一个字符串,其声明如下: 63 | 64 | ``` 65 | func WriteString(w Writer, s string) (n int, err error) 66 | ``` 67 | 68 | # ReadWriter 69 | 整合了Reader和Writer,可以同时进行读取和写入操作,声明如下: 70 | 71 | ``` 72 | type ReadWriter interface { 73 | Reader 74 | Writer 75 | } 76 | ``` 77 | 78 | # Copy 79 | io的一个常用操作就是数据的复制,io包中提供了多个复制函数,直接将数据从Writer复制到Reader。 80 | ## Copy 81 | Copy是最基础的复制函数,读取Writer中的数据,直到EOF,并写入Reader,函数声明如下: 82 | 83 | ``` 84 | func Copy(dst Writer, src Reader) (written int64, err error) 85 | ``` 86 | ## CopyBuffer 87 | CopyBuffer函数在Copy的基础上可以指定数据缓冲区。每次调用Copy函数时,都会生成一块临时的缓冲区,会带来一定的分配开销;CopyBuffer可以多次复用同一块缓冲区,其函数声明如下: 88 | 89 | ``` 90 | func CopyBuffer(dst Writer, src Reader, buf []byte) (written int64, err error) 91 | ``` 92 | 93 | ## CopyN 94 | CopyN在Copy的基础上,可以额外指定拷贝制定字节的数据,其函数声明如下: 95 | 96 | ``` 97 | func CopyN(dst Writer, src Reader, n int64) (written int64, err error) 98 | ``` 99 | # 更多内容 100 | io库中还有许多本文未涉及的内容,包括PipeReader,PipeWriter,ByteReader,ByteWriter等针对具体类型的实例和一些辅助函数。详见 [golang/io](https://golang.org/pkg/io/) -------------------------------------------------------------------------------- /container/heap.md: -------------------------------------------------------------------------------- 1 | # 概述 2 | container/heap包对通用堆进行了定义并实现了标准堆操作函数,以此为基础可以很容易对各类堆和优先队列进行实现。 3 | # 类型接口 4 | heap包中最核心的就是heap.Interface接口,堆的基础存储是一个树形结构,可以用数组或是链表实现。通过heap的函数,可以建立堆并在堆上进行操作;要使用heap包的函数,你的类需要实现heap.Interface接口,定义如下: 5 | 6 | ``` 7 | // heap.Interface 8 | type Interface interface { 9 | sort.Interface 10 | Push(x interface{}) // 在Len()位置插入一个元素 11 | Pop() interface{} // 删除并返回Len()-1位置的元素 12 | } 13 | 14 | // sort.Interface 15 | type Interface interface { 16 | Len() // 获取当前对象的长度 17 | Swap(i, j interface{}) // 交换i,j位置两个元素的位置 18 | Less(i, j interface{}) // 比较i位置元素的值是否小于j位置元素 19 | } 20 | ``` 21 | 在实现了这些接口之后,就可以被heap包提供的各个函数进行操作,从而实现一个堆。 22 | # 成员函数 23 | heap包中提供了几个最基本的堆操作函数,包括Init,Fix,Push,Pop和Remove。这些函数都通过调用前面实现接口里的方法,对堆进行操作。 24 | 25 | ## Init 26 | Init函数用于堆初始化,接受一个实现了heap.Interface的对象,并初始化为一个堆,所有的堆在使用之前都需要进行初始化,Init函数定义为: 27 | 28 | ``` 29 | func Init(h Interface) 30 | ``` 31 | 32 | 33 | ## Fix 34 | Fix函数用于单次对堆进行调整,接收一个堆对象以及一个位置参数i,其函数定义如下: 35 | 36 | ``` 37 | func Fix(h Interface, i int) 38 | ``` 39 | 40 | 如果你还记得如何维护一个堆,那么应该可以很容易理解这个函数的作用。实际上,每次在堆上插入一个元素后,堆结构会被破坏,需要通过Fix函数将这个元素交换到合适的位置,以保证堆的正确性。 41 | 42 | ## Push&Pop 43 | Push和Pop是一对标准堆操作,Push向堆添加一个新元素,Pop弹出并返回堆顶元素,而在push和pop操作不会破坏堆的结构;具体函数定义如下: 44 | 45 | ``` 46 | func Pop(h Interface) interface{} 47 | func Pop(h Interface) interface{} 48 | ``` 49 | 50 | ## Remove 51 | Remove函数用于删除堆上特定位置的元素,这个位置是指元素在堆上的排序,其函数定义如下: 52 | 53 | ``` 54 | func Remove(h Interface, i int) interface{} 55 | ``` 56 | 57 | # 使用实例 58 | 下面是一个简单的例子对上面的内容进行回顾,代码实现了一个小顶堆,堆中元素为长方形类,按照面积大小进行排序,使用slice作为基础存储。首先是类定义和接口实现,需要实现前面说到的五个接口。 59 | 60 | ``` 61 | type Rectangle struct { 62 | height int 63 | width int 64 | } 65 | 66 | func (rec *Rectangle) Area() { 67 | return rec.height * rec.width 68 | 69 | type RecHeap []Rectangle 70 | 71 | func (h RecHeap) Len() { 72 | return len(h) 73 | } 74 | 75 | func (h RecHeap) Swap(i, j interface{}) { 76 | h[i], h[j] = h[j], h[i] 77 | } 78 | 79 | func (h RecHeap) Less(i, j interface{}) { 80 | return h[i].Area() < h[j].Area() 81 | } 82 | 83 | func (h *RecHeap) Push(h interface{}) { 84 | *h = append(*h, h.(Rectangle) 85 | } 86 | 87 | func (h *RecHeap) Pop(h interface{}) { 88 | n := len(*h) 89 | x := *h[n-1] 90 | *h = *h[:n-1] 91 | return x 92 | } 93 | ``` 94 | 完成了接口定义之后就可以通过heap包提供的函数进行堆的操作了,首先使用Init进行初始化,然后通过Push进行元素的插入,Pop进行元素的删除,需要注意的一点是,heap包并没有提供Top这样一个函数获取当前堆顶元素,你可以通过获取slice[0]来获取。 95 | ``` 96 | import ( 97 | "fmt" 98 | "container/heap" 99 | ) 100 | 101 | func main() { 102 | hp := &[]RecHeap{} 103 | for i := 2; i <= 5; i++ { 104 | *hp = append(*hp, Rectangle{i, i}) 105 | // {2, 2}, {3, 3}, {4, 4}, {5, 5} 106 | } 107 | heap.Init(hp) // 初始化 108 | heap.Push(hp, Rectangle{1, 1}) 109 | fmt.Printf("minimum: %d\n", (*hp)[0]) // Rectangle{1, 1} 110 | res := heap.Pop(hp) 111 | fmt.Printf("minimum: %d\n", (*hp)[0]) // Rectangle{2, 2} 112 | } 113 | ``` 114 | 115 | 116 | 你也可以通过修改Less方法将其变为一个大顶堆。 117 | 118 | # 参考文献 119 | - [container/heap](https://golang.org/pkg/container/heap/#Interface) 120 | -------------------------------------------------------------------------------- /strings/strings.md: -------------------------------------------------------------------------------- 1 | # 概述 2 | 字符串是一个十分常用的基础类型,strings包提供了很多函数对string类型变量的操作。这些函数的调用方式大多类似,通过传入一个字符串为参数,在字符串上进行相应的处理。这些函数主要可以分为下面几类: 3 | 4 | - 字符串搜索和匹配 5 | - 字符串拆分 6 | - 字符串修改 7 | - 其他独立的函数 8 | 9 | # 字符串搜索与匹配 10 | strings.Contains可以检测字符串是否包含某个子串;strings.ContainsRune可以检测字符串是否包含某个字符;strings.ContainsAny可以检测字符串是否包含字符集中的某个字符。详细函数声明如下: 11 | 12 | ``` 13 | func Contains(s, substr string) bool 14 | 15 | func ContainsRune(s string, r rune) bool 16 | 17 | func ContainsAny(s, chars string) bool 18 | ``` 19 | 除了简单的判断字符串包含,使用strings.Index可以在字符串中搜索某个子串,并得到对应子串起始索引下标,若不存在对应子串则返回-1。函数声明如下: 20 | 21 | ``` 22 | func Index(s, substr string) int 23 | ``` 24 | 除了对子串进行搜索之外,也可以对某个字节,字符,字符集合进行搜索。具体声明如下: 25 | 26 | ``` 27 | func IndexByte(s string, c byte) int // 字节搜索 28 | 29 | func IndexRune(s string, r rune) int。 // 字符搜索 30 | 31 | func IndexAny(s, chars string) int。 // 字符集合搜索,匹配chars中的任何一个字符 32 | ``` 33 | 上述的所有搜索操作都返回第一个匹配的索引;除此之外,strings包也提供了一系列函数获取对应元素的最后一个匹配项的索引下标。对应于每个Index函数,都有一个LastIndex函数。例如Index返回第一个匹配的子串的起始索引,LastIndex返回最后一个匹配子串的起始索引。详细函数声明如下: 34 | 35 | ``` 36 | func LastIndex(s, substr string) int 37 | 38 | func LastIndexByte(s string, c byte) int 39 | 40 | func IndexAny(s, chars string) int。 41 | ``` 42 | # 字符串拆分 43 | 字符串拆分是字符串的常见操作。strings支持两类拆分操作:Split和SplitAfter。两者的区别在于Split拆分后的结果中不包含分隔符;而SplitAfter包含分隔符。例如`strings.Split("a,b,c", ",")`结果为`["a", "b", "c"]`;`strings.SplitAfter("a,b,c", ",")`结果为`["a,", "b,", "c"]`。函数声明如下: 44 | 45 | ``` 46 | func Split(s, sep string) []string 47 | 48 | func SplitAfter(s, sep string) []string 49 | ``` 50 | 51 | 前面的Split和SplitAfter都只能指定一个分隔符,那么如果希望指定一类分隔符应该怎么做呢。在strings模块中,提供了FieldsFunc函数,通过传递一个函数来确定一个字符是否为分隔符。下面来看一个例子,所有非数字和非字母的字符都被认为是分隔符而被跳过。 52 | 53 | ``` 54 | package main 55 | 56 | import ( 57 | "fmt" 58 | "strings" 59 | "unicode" 60 | ) 61 | 62 | func main() { 63 | str := " hello&$ world" 64 | 65 | f := func(c rune) bool { 66 | return !unicode.IsLetter(c) && !unicode.IsNumber(c) 67 | } 68 | fmt.Printf("%q", strings.FieldsFunc(str, f)) 69 | } 70 | ``` 71 | 72 | 函数声明如下: 73 | 74 | ``` 75 | func FieldsFunc(s string, f func(rune) bool) []string 76 | ``` 77 | 实际使用中,也可以使用Fields函数,对字符串中空格进行删除,相当于FieldsFunc(s, unicode.IsSpace) 78 | 79 | ``` 80 | func Fields(s string) []string 81 | ``` 82 | # 字符串修改 83 | 84 | Trim系列函数可以删除字符串首尾的连续多余字符,包括: 85 | 86 | - Trim,删除字符串首尾的多余字符 87 | - TrimLeft,删除字符串首的多余字符 88 | - TrimRight,删除字符串尾部的多余字符 89 | - TrimSpace,删除字符串首尾的空格 90 | 91 | 函数声明如下: 92 | 93 | ``` 94 | func Trim(s string, cutset string) string 95 | 96 | func TrimLeft(s string, cutset string) string 97 | 98 | func TrimRight(s string, cutset string) string 99 | 100 | func TrimSpace(s string) string 101 | ``` 102 | 103 | 104 | 除此之外,还可以通过传递函数的方式对删除字符进行更精确的选择,这里不再展开,具体见[TrimFunc](https://golang.org/pkg/strings/#TrimFunc)。 105 | 106 | 除了对字符进行删除之外,strings包也可以字符串进行格式化,通过一系列函数提供了支持,其中最为常用的是`ToLower`和`ToUpper`分别用于将字符串转化为小写和大写字母,函数声明如下: 107 | 108 | ``` 109 | func ToLower(s string) string 110 | 111 | func ToUpper(s string) string 112 | ``` 113 | 114 | # 其他函数 115 | 除了上述几类函数之外,strings包还提供了下面几个实用函数: 116 | 117 | - Join,将多个字符串组装成一个字符串,子串间通过分隔符连接(split的逆操作); 118 | - Compare,通过字典序比较两个字符串的大小等价于>,<,==运算; 119 | - Count,统计字符串中指定子串的数量, 120 | - Replace,替换字符串中的对应子串 121 | 122 | 函数声明如下: 123 | 124 | ``` 125 | func Join(a []string, sep string) string 126 | 127 | func Compare(a, b string) int 128 | 129 | func Count(s, substr string) int 130 | ``` 131 | 132 | -------------------------------------------------------------------------------- /sync/sync.md: -------------------------------------------------------------------------------- 1 | # 概述 2 | sync包对并发和同步机制进行了实现,但显然并发编程这样一个话题过于庞大,无法在一篇博客里面详细展开,所以本文的重点放在sync包的使用。 3 | 4 | 不过这里首先对并发的背景进行简单的介绍,在单线程的程序中,同一个时刻只存在一个线程对数据进行访问,访问永远是线性的,不需要额外的机制保障;但是当同时存在多个线程可能同时访问一个数据时,由于线程调度的特性,会带来难以预料的结果。试想如下代码会输出什么结果,正常情况会是3,但实际上可能的结果是2或者3,这是由于函数或者代码的运行没有原子性,比如在线程1执行Add1操作时被暂停了(已经读取data,还未写回),然后开始运行线程2,并读取数据,然后两个线程依次执行到结束,由于两个线程读取的值都是data=1,执行Add1后都得到2,而非3。 5 | 6 | ``` 7 | import ( 8 | "fmt" 9 | "time" 10 | ) 11 | 12 | func Add1(data *int) { 13 | tmp = *data 14 | *data = tmp + 1 15 | } 16 | 17 | func main(){ 18 | data = 1 19 | go Add1(&data) 20 | go Add1(&data) 21 | time.Sleep(10) 22 | fmt.Print(data) 23 | 24 | } 25 | ``` 26 | 并发编程的本质就是在乱序执行的代码中创建小块的临界区,在临界区中程序线性执行,保证代码的执行结果符合预期。 27 | 28 | # 互斥锁 29 | sync.Mutex是互斥锁的实现,这是同步中最经典的模型,Mutex有两个方法Lock和Unlock,分别用于锁定和解锁一个锁,每个互斥锁只能被Lock一次,在一个已锁定的锁上执行Lock操作,会阻塞直到这个锁被Unlock。Mutex的声明如下: 30 | 31 | ``` 32 | type Mutex struct { 33 | // contains filtered or unexported fields 34 | } 35 | 36 | func (m *Mutex) Lock() 37 | 38 | func (m *Mutex) Unlock() 39 | ``` 40 | 对于前面那个程序,可以加入锁保证并发安全,同时推荐使用defer保证解锁 41 | 42 | ``` 43 | import ( 44 | "fmt" 45 | "time" 46 | "sync" 47 | ) 48 | 49 | func Add1(data *int, mu &sync.Mutex) { 50 | mu.Lock() 51 | defer mu.Unlock() 52 | tmp = *data 53 | *data = tmp + 1 54 | } 55 | 56 | func main(){ 57 | mu := sync.Mutex() 58 | data = 1 59 | go Add1(&data, &mu) 60 | go Add1(&data, &mu) 61 | time.Sleep(10) 62 | fmt.Print(data) 63 | 64 | } 65 | ``` 66 | ## 读写锁 67 | 实际上,单纯对数据的并发读取是不会带来数据不一致的,而这种情况下使用互斥锁会带来额外的等待开销,所以除了基础的互斥锁之外,sync包还提供了RWMutex,与Mutex不同在于,Mutex只能同时被一个线程锁定,而RWMutex可以多次读锁定,也就是可以进行并发读取,具体见[sync.RWMutex](https://golang.org/pkg/sync/#RWMutex) 68 | 69 | # 信号量 70 | sync.Cond是信号量的实现,这也是一个经典的同步模型,从功能的角度来看,你只能等待一个信号量或者向信号量发送一个信号,经典的应用场景就是生产者消费者模型,当生产者结束一个生产,则发起一个信号通知消费者;而消费者只需要等待这个信号。 71 | ## NewCond 72 | Cond对象通过NewCond初始化,并且绑定到一个Locker。 73 | 74 | ``` 75 | type Cond struct { 76 | L Locker 77 | } 78 | 79 | func NewCond(l Locker) *Cond 80 | ``` 81 | ## Signal&Broadcast 82 | Signal和Broadcast用于唤醒一个信号量,区别在于Signal只会随机的唤醒一个线程,而Broadcast会唤醒所有在等待的线程。 83 | 84 | ``` 85 | func (c *Cond) Signal() 86 | 87 | func (c *Cond) Broadcast() 88 | ``` 89 | ## Wait 90 | 阻塞等待,当Signal或Broadcast被调用时唤醒,声明如下: 91 | 92 | ``` 93 | func (c *Cond) Wait() 94 | ``` 95 | 96 | # WaitGroup 97 | WaitGroup的功能和信号量类似,不过信号量只等待单个信号的到来,WaitGroup等待一组任务的结束。 98 | ## Add 99 | 在一个WaitGroup上增加某个值,方法声明如下: 100 | 101 | ``` 102 | func (wg *WaitGroup) Add(delta int) 103 | ``` 104 | 105 | ## Done 106 | 每次调用Done方法,会使WaitGroup的值减少 1 107 | 108 | ``` 109 | func (wg *WaitGroup) Done() 110 | ``` 111 | 112 | ## Wait 113 | 阻塞等待,当WaitGroup的值为 0 时唤醒 114 | 115 | ``` 116 | func (wg *WaitGroup) Wait() 117 | ``` 118 | 119 | ## 示例 120 | 下面用一个简单的例子对WaitGroup的使用进行说明,创建了一个WaitGroup并将其值增加3,然后并发的执行handle函数,最后调用Wait等待执行结束再退出主线程。 121 | ``` 122 | package main 123 | 124 | import ( 125 | "sync" 126 | "fmt" 127 | ) 128 | 129 | func handle(wg *sync.WaitGroup) { 130 | defer wg.Done() 131 | fmt.Println("Done Once") 132 | } 133 | 134 | func main() { 135 | wg := sync.WaitGroup{} 136 | wg.Add(3) 137 | for i := 0; i < 3; i++ { 138 | go handle(&wg) 139 | } 140 | 141 | wg.Wait() 142 | } 143 | ``` 144 | 145 | # Once 146 | sync.Once保证某个函数有且仅有一次执行,只有一个方法Do,接受一个无参函数作为参数,当你调用Do时会执行这个函数,其方法声明如下: 147 | 148 | ``` 149 | func (o *Once) Do(f func()) 150 | ``` 151 | 下面的代码是对Once的简单使用,由于Once的使用,只会打印出一次“Do Once”,而不是10次 152 | 153 | ``` 154 | for i := 0; i < 10; i++ { 155 | sync.Once.Do(func () { fmt.Println("Do Once") } 156 | } 157 | ``` 158 | -------------------------------------------------------------------------------- /bufio/bufio.md: -------------------------------------------------------------------------------- 1 | # 概述 2 | bufio模块通过对io模块的封装,提供了数据缓冲功能,能够一定程度减少大块数据读写带来的开销。 3 | 4 | 实际上在bufio各个组件内部都维护了一个缓冲区,数据读写操作都直接通过缓存区进行。当发起一次读写操作时,会首先尝试从缓冲区获取数据;只有当缓冲区没有数据时,才会从数据源获取数据更新缓冲。 5 | # Reader 6 | 可以通过`NewReader`函数创建bufio.Reader对象,函数接收一个io.Reader作为参数;也就是说,bufio.Reader不能直接使用,需要绑定到某个io.Reader上。函数声明如下: 7 | 8 | ``` 9 | func NewReader(rd io.Reader) *Reader 10 | 11 | func NewReaderSize(rd io.Reader, size int) *Reader // 可以配置缓冲区的大小 12 | ``` 13 | 相较于io.Reader,bufio.Reader提供了很多实用的方法,能够更有效的对数据进行读取。首先是几个基础方法,它们能够对Reader进行细粒度的操作: 14 | 15 | - Read,读取n个byte数据 16 | - Discard,丢弃接下来n个byte数据 17 | - Peek,获取当前缓冲区内接下来的n个byte,但是不移动指针 18 | - Reset,清空整个缓冲区 19 | 20 | 具体的方法声明如下: 21 | 22 | ``` 23 | func (b *Reader) Read(p []byte) (n int, err error) 24 | 25 | func (b *Reader) Discard(n int) (discarded int, err error) 26 | 27 | func (b *Reader) Peek(n int) ([]byte, error) 28 | 29 | func (b *Reader) Reset(r io.Reader) 30 | ``` 31 | 除了上面的基础操作之外,bufio.Reader还提供了多个更高抽象层次的方法对数据进行简单的结构化读取。主要包括如下几个方法: 32 | 33 | - ReadByte,读取一个byte 34 | - ReadRune,读取一个utf-8字符 35 | - ReadLine,读取一行数据,由'\n'分隔 36 | - ReadBytes,读取一个byte列表 37 | - ReadString,读取一个字符串 38 | 39 | 其中前三个函数都没有参数,会从缓冲区读取一个满足需求的数据。后面两个函数接收一个参数delim,用于做数据拆分,持续读取数据直到当前字节的值等于delim,然后返回这些数据;实际上这两个函数功能相同,只是在函数返回值的类型上有所区别。具体的方法声明如下: 40 | 41 | ``` 42 | func (b *Reader) ReadByte() (byte, error) 43 | 44 | func (b *Reader) ReadRune() (r rune, size int, err error) 45 | 46 | func (b *Reader) ReadLine() (line []byte, isPrefix bool, err error) 47 | 48 | func (b *Reader) ReadBytes(delim byte) ([]byte, error) 49 | 50 | func (b *Reader) ReadString(delim byte) (string, error) 51 | ``` 52 | 53 | 下面是一个简单的示例,使用ReadString方法获取用‘ ’分隔的字符串。 54 | 55 | ``` 56 | package main 57 | 58 | import ( 59 | "bufio" 60 | "fmt" 61 | "strings" 62 | ) 63 | 64 | func main() { 65 | r := strings.NewReader("hello world !") 66 | reader := bufio.NewReader(r) 67 | 68 | for { 69 | str, err := reader.ReadString(byte(' ')) 70 | fmt.Println(str) 71 | if err != nil { 72 | return 73 | } 74 | } 75 | } 76 | ``` 77 | # Scanner 78 | 实际使用中,更推荐使用Scanner对数据进行读取,而非直接使用Reader类。Scanner可以通过splitFunc将输入数据拆分为多个token,然后依次进行读取。 79 | 80 | 和Reader类似,Scanner需要绑定到某个io.Reader上,通过NewScannner进行创建,函数声明如下: 81 | 82 | ``` 83 | func NewScanner(r io.Reader) *Scanner 84 | ``` 85 | 86 | 在使用之前还需要设置splitFunc(默认为ScanLines),splitFunc用于将输入数据拆分为多个token。bufio模块提供了几个默认splitFunc,能够满足大部分场景的需求,包括: 87 | 88 | - ScanBytes,按照byte进行拆分 89 | - ScanLines,按照行("\n")进行拆分 90 | - ScanRunes,按照utf-8字符进行拆分 91 | - ScanWords,按照单词(" ")进行拆分 92 | 93 | 通过Scanner的Split方法,可以为Scanner指定splitFunc。使用方法如下: 94 | 95 | ``` 96 | scanner := bufio.NewScanner(os.StdIn) 97 | 98 | scanner.split(bufio.ScanWords) 99 | ``` 100 | 除此了默认的splitFunc之外,也可以定义自己的splitFunc,函数需要满足如下声明: 101 | 102 | ``` 103 | type SplitFunc func(data []byte, atEOF bool) (advance int, token []byte, err error) 104 | ``` 105 | 函数接收两个参数,第一个参数是输入数据,第二个参数是一个标识位,用于标识当前数据是否为结束。函数返回三个参数,第一个是本次split操作的指针偏移;第二个是当前读取到的token;第三个是返回的错误信息。 106 | 107 | 在完成了Scanner初始化之后,通过Scan方法可以在输入中向前读取一个token,读取成功返回True;使用Text和Bytes方法获取这个token,Text返回一个字符串,Bytes返回字节数组。方法声明如下: 108 | 109 | ``` 110 | func (s *Scanner) Scan() bool 111 | 112 | func (s *Scanner) Text() string 113 | 114 | func (s *Scanner) Text() []byte 115 | ``` 116 | 117 | 下面的示例使用Scanner对上面的示例进行了重现,可以看到和Reader相比,Scanner的使用更加便捷。 118 | 119 | ``` 120 | package main 121 | 122 | import ( 123 | "bufio" 124 | "strings" 125 | "fmt" 126 | ) 127 | 128 | func main() { 129 | 130 | scanner := bufio.NewScanner(strings.NewReader("hello world !")) 131 | 132 | scanner.Split(bufio.ScanWords) 133 | 134 | for scanner.Scan() { 135 | fmt.Println(scanner.Text()) 136 | } 137 | 138 | } 139 | 140 | ``` 141 | 142 | # Writer 143 | 和Reader类似,Writer也对应的提供了多组方法。基础方法包括如下几个: 144 | 145 | ``` 146 | func (b *Writer) Write(p []byte) (nn int, err error) // 写入n byte数据 147 | 148 | func (b *Writer) Reset(w io.Writer) // 重置当前缓冲区 149 | 150 | func (b *Writer) Flush() error // 清空当前缓冲区,将数据写入输出 151 | ``` 152 | 153 | 此外,Writer也提供了多个方法方便我们进行数据写入操作: 154 | 155 | ``` 156 | func (b *Writer) WriteByte(c byte) error // 写入一个字节 157 | 158 | func (b *Writer) WriteRune(r rune) (size int, err error) // 写入一个字符 159 | 160 | func (b *Writer) WriteString(s string) (int, error) // 写入一个字符串 161 | ``` -------------------------------------------------------------------------------- /net/http/http.md: -------------------------------------------------------------------------------- 1 | # 概述 2 | net/http可以用来处理HTTP协议,包括HTTP服务器和HTTP客户端,http包主要由五个部分组成: 3 | - Request,HTTP请求对象 4 | - Response,HTTP响应对象 5 | - Client,HTTP客户端 6 | - Server,HTTP服务端 7 | 8 | # 最简单的使用 9 | http包提供了对应于每个HTTP动词的函数来发送HTTP请求,当你不需要对请求进行详细的定制时可以直接使用它们。 10 | 11 | ``` 12 | resp, err := http.Get("http://example.com/") // GET 13 | 14 | resp, err := http.Post("http://example.com/") // POST 15 | 16 | resp, err := http.PostForm("http://example.com/", url.Values{"foo": "bar"}) // 提交表单 17 | ``` 18 | 19 | # HTTP请求和响应 20 | HTTP作为一个通信协议,通过报文传递信息;报文分为请求报文和响应报文,在http包中,分别用Reqeust和Response对象进行了抽象。 21 | ## Request 22 | 可以通过NewRequest创建一个Request对象,方法声明如下,需要传入HTTP方法,URL以及报文体进行初始化: 23 | 24 | ``` 25 | func NewRequest(method, url string, body io.Reader) (*Request, error) 26 | ``` 27 | Request对象主要用于数据的存储,结构如下: 28 | 29 | ``` 30 | type Request struct { 31 | 32 | Method string // HTTP方法 33 | URL *url.URL // URL 34 | 35 | Proto string // "HTTP/1.0" 36 | ProtoMajor int // 1 37 | ProtoMinor int // 0 38 | 39 | Header Header // 报文头 40 | Body io.ReadCloser // 报文体 41 | GetBody func() (io.ReadCloser, error) 42 | ContentLength int64 // 报文长度 43 | TransferEncoding []string // 传输编码 44 | Close bool // 关闭连接 45 | Host string // 主机名 46 | 47 | Form url.Values // 48 | PostForm url.Values // POST表单信息 49 | MultipartForm *multipart.Form // multipart, 50 | 51 | Trailer Header 52 | RemoteAddr string 53 | RequestURI string 54 | TLS *tls.ConnectionState 55 | Cancel <-chan struct{} 56 | Response *Response 57 | } 58 | ``` 59 | 可以看到Request对象可以对请求报文的各个方面进行设置,除了上述属性之外,Request也提供了一些方法对这些属性进行访问和修改,这里不具体展开,详细文档可见[Request](https://golang.org/pkg/net/http/#Request)。 60 | ## Response 61 | 和Request对象类似,Response也是一个数据对象,拥有多个字段来描述HTTP响应,需要注意的是Reponse对象拥有了当前Request对象的引用,对象的具体声明如下: 62 | 63 | ``` 64 | type Response struct { 65 | Status string // HTTP 状态 "200 OK" 66 | StatusCode int // 状态码 200 67 | Proto string // 版本号 "HTTP/1.0" 68 | ProtoMajor int // 主版本号 69 | ProtoMinor int // 次版本号 70 | 71 | Header Header // 响应报文头 72 | Body io.ReadCloser // 响应报文体 73 | ContentLength int64 // 报文长度 74 | TransferEncoding []string // 报文编码 75 | Close bool 76 | Trailer Header 77 | Request *Request // 请求对象 78 | TLS *tls.ConnectionState 79 | } 80 | ``` 81 | 82 | 83 | # Client 84 | 实际上第一节中的GET,POST等函数就是通过绑定到默认Client实现的。我们也可以创建自己的client对象,要通过Client发出HTTP请求,你需要首先初始化一个Client对象,然后发出请求,例如下面的程序可以访问Google: 85 | 86 | ``` 87 | package main 88 | 89 | import "net/http" 90 | 91 | func main() { 92 | client := http.Client() 93 | res, err := client.Get("http://www.google.com") 94 | } 95 | ``` 96 | 对于常用HTTP动词,Client对象都有对应的函数进行处理: 97 | 98 | ``` 99 | func (c *Client) Get(url string) (resp *Response, err error) 100 | 101 | func (c *Client) Head(url string) (resp *Response, err error) 102 | 103 | func (c *Client) Post(url string, contentType string, body io.Reader) (resp *Response, err error) 104 | 105 | func (c *Client) PostForm(url string, data url.Values) (resp *Response, err error) 106 | ``` 107 | 但是在很多情况下,需要支持对报文头,Cookies等的定制,上面提供的方法就不能满足需求了。所以Client对象还提供了一个Do方法,通过传入一个Request对象达到请求的定制化,具体方法声明如下: 108 | 109 | ``` 110 | func (c *Client) Get(url string) (resp *Response, err error) 111 | ``` 112 | 下面一个简单的配置实例: 113 | 114 | ``` 115 | package main 116 | 117 | import ( 118 | "net/http" 119 | "fmt" 120 | "io/ioutil" 121 | ) 122 | 123 | func main() { 124 | req, err := http.NewRequest(http.MethodGet, "http://www.baidu.com", nil) 125 | if err != nil { 126 | fmt.Println(err.Error()) 127 | return 128 | } 129 | req.Header.Set("Cookie", "name=foo") 130 | req.Header.Set("Content-Type", "application/x-www-form-urlencoded") 131 | 132 | client := http.Client{} 133 | res, err := client.Do(req) 134 | 135 | // defer res.Body.Close() 136 | 137 | if err != nil { 138 | fmt.Println(err.Error()) 139 | return 140 | } 141 | body, err := ioutil.ReadAll(res.Body) 142 | 143 | if err != nil { 144 | fmt.Println(err.Error()) 145 | return 146 | } 147 | 148 | fmt.Println(string(body)) 149 | } 150 | ``` 151 | 152 | # Server 153 | http包除了可以发送HTTP请求之外,也可以创建HTTP服务器,对外提供访问。可以通过ListenandServe方法创建一个HTTP服务。 154 | 155 | ``` 156 | package main 157 | 158 | import ( 159 | "net/http" 160 | "io/ioutil" 161 | "log" 162 | ) 163 | 164 | func EchoServer(w http.ResponseWriter, req *http.Request) { 165 | body, err := ioutil.ReadAll(req.Body) 166 | 167 | io.WriteString(w, body) 168 | } 169 | 170 | func main() { 171 | handleFunc := http.HandleFunc("/echo/", EchoServer) 172 | log.Fatal(http.ListenAndServe(":8080", nil)) 173 | } 174 | ``` 175 | 在Server模块中,有两个概念,一个是URL,一个是Handler;前者是访问的URL,后者是对应的处理函数。Server需要完成从URL到Handler映射,在http包中的默认实现是DefaultServerMux,每个Handler需要通过HandleFunc进行注册。 176 | 177 | 除了上面的默认方式之外,和Client一样可以通过创建Server实例,对服务进行定制,整体的流程差别不大,这里就不再展开。 178 | 179 | # HTTP方法和状态码 180 | 除了上面的内容以外,http包还定义了一系列常量用于表示HTTP动词和返回状态码,详见[constants](https://golang.org/pkg/net/http/#pkg-constants) --------------------------------------------------------------------------------