假期小记

这里记录了假期时看到的一些代码片段以及知识点,觉得不错就记录了下来。

map

Go 实现 map 排序

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
package main
import (
"sort"
"fmt"
)
func map_sort(ages map[string]int) {
var names []string
for name := range ages {
names = append(names, name)
}
sort.Strings(names)
for _, name := range names {
fmt.Printf("%s\t%d\n", name, ages[name])
}
}

要判断两个 map 是否包含相同的 key 和 value,我们必须通过一个循环实现

1
2
3
4
5
6
7
8
9
10
11
func equal(x, y map[string]int) bool {
if len(x) != len(y) {
return false
}
for k, xv := range x {
if yv, ok := y[k]; !ok || yv != xv {
return false
}
}
return true
}

Reader 接口

Reader 接口的定义如下:

1
2
3
type Reader interface {
Read(p []byte) (n int, err error)
}

官方文档中关于该接口方法的说明:

1
2
3
4
5
// Read 将 len(p) 个字节读取到 p 中。它返回读取的字节数 n(0 <= n <= len(p)) 以及任何遇到的错误。即使 Read 返回的 n < len(p),它也会在调用过程中使用 p 的全部作为暂存空间。若一些数据可用但不到 len(p) 个字节,Read 会照例返回可用的数据,而不是等待更多数据。
//当 Read 在成功读取 n > 0 个字节后遇到一个错误或EOF(end-of-file),它就会返回读取的字节数。它会从相同的调用中返回(非nil的)错误或从随后的调用中返回错误(同时 n == 0)。 一般情况的一个例子就是 Reader 在输入流结束时会返回一个非零的字节数,同时返回的err不是EOF就是nil。无论如何,下一个 Read 都应当返回 0, EOF。
//调用者应当总在考虑到错误 err 前处理 n > 0 的字节。这样做可以在读取一些字节,以及允许的 EOF 行为后正确地处理I/O错误。

也就是说,当 Read 方法返回错误时,不代表没有读取到任何数据。调用者应该处理返回的任何数据,之后才处理可能的错误。

Writer 接口

Writer 接口的定义如下:

1
2
3
type Writer interface {
Write(p []byte) (n int, err error)
}

官方文档中关于该接口方法的说明:

1
//Write 将 len(p) 个字节从 p 中写入到基本数据流中。它返回从 p 中被写入的字节数 n(0 <= n <= len(p))以及任何遇到的引起写入提前停止的错误。若 Write 返回的 n < len(p),它就必须返回一个非nil的错误。

同样的,所有实现了 Write 方法的类型都实现了 io.Writer 接口。

实现了 io.Reade r接口或 io.Writer 接口的类型

我们可以知道,os.File 同时实现了这两个接口。我们还看到 os.Stdin/Stdout 这样的代码,它们似乎分别实现了 io.Reader/io.Writer 接口。没错,实际上在 os 包中有这样的代码:

1
2
3
4
5
var (
Stdin = NewFile(uintptr(syscall.Stdin), "/dev/stdin")
Stdout = NewFile(uintptr(syscall.Stdout), "/dev/stdout")
Stderr = NewFile(uintptr(syscall.Stderr), "/dev/stderr")
)

也就是说,Stdin/Stdout/Stderr 只是三个特殊的文件(即都是 os.File 的实例),自然也实现了 io.Reader 和io.Writer。

目前,Go 文档中还没法直接列出实现了某个接口的所有类型。不过,我们可以通过查看标准库文档,列出实现了io.Reader 或 io.Writer 接口的类型(导出的类型):

1
2
3
4
5
6
7
8
9
10
- os.File 同时实现了io.Reader和io.Writer
- strings.Reader 实现了io.Reader
- bufio.Reader/Writer 分别实现了io.Reader和io.Writer
- bytes.Buffer 同时实现了io.Reader和io.Writer
- bytes.Reader 实现了io.Reader
- compress/gzip.Reader/Writer 分别实现了io.Reader和io.Writer
- crypto/cipher.StreamReader/StreamWriter 分别实现了io.Reader和io.Writer
- crypto/tls.Conn 同时实现了io.Reader和io.Writer
- encoding/csv.Reader/Writer 分别实现了io.Reader和io.Writer
- mime/multipart.Part 实现了io.Reader

除此之外,io 包本身也有这两个接口的实现类型。如:

实现了Reader 的类型:LimitedReader、PipeReader、SectionReader

实现了 Writer 的类型:PipeWriter

以上类型中,常用的类型有:os.File、strings.Reader、bufio.Reader/Writer、bytes.Buffer、bytes.Reader。

切片

切片可以看作是对数组的一种包装形式,它所包装的数组称为该切片的底层数组。换句话说,切片是针对其底层数组中某个连续片段的描述符。

一个切片值总会持有一个对某个数组值的引用,事实上,一个切片值一旦被初始化,就会与一个包含了其中元素值的数组值相关联,即底层数组。多个切片值可能会共同用同一个底层数组。例如,如果把一个切片值复制成多个,或者针对其中的某个连续片段再切片成新的切片值,那么这些切片值所引用的都会是同一个底层数组。对切片值中的元素值的修改,实质上就是对底层数组上的对应元素的修改。从这个角度看,切片类似于指向底层数组的指针。反过来讲,对作为底层数组中元素值的改变,也会体现到引用该底层数组且包含该元素值的所有切片值上。