chaoz的杂货铺

生命有息、学无止境、折腾不止

0%

2022-Golang-标准库学习

标准库学习

context

20220206104112

20220206104200

如何将context集成到api中?

20220219164640

context的作用,哪些场景使用过context?

20220219165028

Background与todo

context.Background 是上下文的默认值,所有其他的上下文都应该从它衍生出来;
context.TODO 应该仅在不确定应该使用哪种上下文时使用;

strings

底层结构

在底层,一个string值的内容会被存储到一块连续的内存空间中。同时,这块内存容纳的字节数量也会被记录下来,并用于表示该string值的长度。

与string值相比,strings.Builder类型的值有哪些优势?

strings.Builder类型的值(以下简称Builder值)的优势有下面的三种:
1.已存在的内容不可变,但可以拼接更多的内容;
2.减少了内存分配和内容拷贝的次数;
3.可将内容重置,可重用值。

string类型的值是不可变的。 如果我们想获得一个不一样的字符串,那么就只能基于原字符串进行裁剪、拼接等操作,从而生成一个新的字符串。

与string值相比,Builder值的优势

主要体现在字符串拼接方面。它可以在保证已存在的内容不变的前提下,拼接更多的内容,并且会在拼接的过程中,尽量减少内存分配和内容拷贝的次数。

strings.Builder类型在使用上有约束吗?

它在被真正使用之后就不能再被复制了,否则就会引发 panic。
什么叫真正使用之后?

源码里会避免逃逸,有什么好处?

避免逃逸分析这种做法只应该在Go语言内部使用。因为这是一把双刃剑。它可以避免增加GC的压力,但是如果使用不当,在运行时系统伸缩goroutine堆栈时就会出问题。

逃逸分析之所以称为分析,是因为它有一个分析的过程。这不是在程序运行时做的,而是在编译时做的(决定一个值是分配在当前goroutine的堆栈上还是分配在公共的堆上),所以不存在拷贝来拷贝去的问题。

为什么说strings.Reader类型的值可以高效地读取字符串?

主要体现在它对字符串的读取机制上,它封装了很多用于在string值上读取内容的最佳实践。
strings.Reader类型的值(以下简称Reader值)可以让我们很方便地读取一个字符串中的内容。在读取的过程中,Reader值会保存已读取的字节的计数(以下简称已读计数)。
已读计数也代表着下一次读取的起始索引位置。Reader值正是依靠这样一个计数,以及针对字符串值的切片表达式,从而实现快速读取。

strings.Builder和 strings.Reader都分别实现了哪些接口?这样做有什么好处吗?

还有什么函数

https://studygolang.com/articles/5769
CountIndexRuneMapReplaceSplitNTrim,等等。
Count 计算字符串 sep 在 s 中的非重叠个数
Contains 判断字符串 s 中是否包含子串 substr
ContainsAny 判断字符串 s 中是否包含 chars 中的任何一个字符
ContainsRune 判断字符串 s 中是否包含字符 r
Index 返回子串 sep 在字符串 s 中第一次出现的位置
LastIndex 返回子串 sep 在字符串 s 中最后一次出现的位置
IndexRune 返回字符 r 在字符串 s 中第一次出现的位置
IndexAny 返回字符串 chars 中的任何一个字符在字符串 s 中第一次出现的位置
LastIndexAny 返回字符串 chars 中的任何一个字符在字符串 s 中最后一次出现的位置
SplitN 以 sep 为分隔符,将 s 切分成多个子串,结果中不包含 sep 本身
SplitAfterN 以 sep 为分隔符,将 s 切分成多个子串,结果中包含 sep 本身
Split 以 sep 为分隔符,将 s 切分成多个子切片,结果中不包含 sep 本身
SplitAfter 以 sep 为分隔符,将 s 切分成多个子切片,结果中包含 sep 本身
Fields 以连续的空白字符为分隔符,将 s 切分成多个子串,结果中不包含空白字符本身
FieldsFunc 以一个或多个满足 f(rune) 的字符为分隔符
Join 将 a 中的子串连接成一个单独的字符串,子串之间用 sep 分隔
HasPrefix 判断字符串 s 是否以 prefix 开头
HasSuffix 判断字符串 s 是否以 prefix 结尾
Map 将 s 中满足 mapping(rune) 的字符替换为 mapping(rune) 的返回值。
Repeat 将字符串 s 重复 count 遍,连接成一个新的字符串。
ToUpper 将 s 中的所有字符修改为其大写格式
ToLower 将 s 中的所有字符修改为其小写格式
ToTitle 将 s 中的所有字符修改为其 Title 格式
ToUpperSpecial 将 s 中的所有字符修改为其大写格式。
ToLowerSpecial 将 s 中的所有字符修改为其小写格式。
ToTitleSpecial 将 s 中的所有字符修改为其 Title 格式。
Title 将 s 中的所有单词的首字母修改为其 Title 格式
TrimLeftFunc 将删除 s 头部连续的满足 f(rune) 的字符
TrimRightFunc 将删除 s 尾部连续的满足 f(rune) 的字符
TrimFunc 将删除 s 首尾连续的满足 f(rune) 的字符
返回 s 中第一个满足 f(rune) 的字符的字节位置。
返回 s 中最后一个满足 f(rune) 的字符的字节位置。
Trim 将删除 s 首尾连续的包含在 cutset 中的字符
TrimLeft 将删除 s 头部连续的包含在 cutset 中的字符
TrimRight 将删除 s 尾部连续的包含在 cutset 中的字符
TrimSpace 将删除 s 首尾连续的的空白字符
TrimPrefix 删除 s 头部的 prefix 字符串
TrimSuffix 删除 s 尾部的 suffix 字符串
Replace 返回 s 的副本,并将副本中的 old 字符串替换为 new 字符串
EqualFold 判断 s 和 t 是否相等。忽略大小写,同时它还会对特殊字符进行转换
NewReplacer 通过“替换列表”创建一个 Replacer 对象。按照“替换列表”中的顺序进行替换,只替换非重叠部分。然后Replace。
WriteString 对 s 进行“查找和替换”,然后将结果写入 w 中

bytes

strings包主要面向的是 Unicode 字符和经过 UTF-8 编码的字符串,而bytes包面对的则主要是字节和字节切片。

bytes.Buffer

bytes.Buffer类型的用途主要是作为字节序列的缓冲区。

bytes.Buffer类型的值记录的已读计数,在其中起到了怎样的作用?

1.读取内容时,相应方法会依据已读计数找到未读部分,并在读取后更新计数。
2.写入内容时,如需扩容,相应方法会根据已读计数实现扩容策略。
3.截断内容时,相应方法截掉的是已读计数代表索引之后的未读部分。
4.读回退时,相应方法需要用已读计数记录回退点。
5.重置内容时,相应方法会把已读计数置为0。
6.导出内容时,相应方法只会导出已读计数代表的索引之后的未读部分。
7.获取长度时,相应方法会依据已读计数和内容容器的长度,计算未读部分的长度并返回。

bytes.Buffer的扩容策略是怎样的?

bytes.Buffer中的哪些方法可能会造成内容的泄露?

这里所说的内容泄露是指,使用Buffer值的一方通过某种非标准的(或者说不正式的)方式,得到了本不该得到的内容。

对比strings.Builder和bytes.Buffer的String方法,并判断哪一个更高效?原因是什么?

bytes.Buffer 值的 String() 方法在转换时采用了指针 (string)(unsafe.Pointer(&b.buf)),更节省时间和内存

string与bytes思考题

string 类型的值是常量,不可更改

尝试使用索引遍历字符串,来更新字符串中的个别字符,是不允许的。

string 类型的值是只读的二进制 byte slice,如果真要修改字符串中的字符,将 string 转为 []byte 修改后,再转为 string 即可:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// 修改字符串的错误示例
func main() {
x := "text"
x[0] = "T" // error: cannot assign to x[0]
fmt.Println(x)
}


// 修改示例
func main() {
x := "text"
xBytes := []byte(x)
xBytes[0] = 'T' // 注意此时的 T 是 rune 类型
x = string(xBytes)
fmt.Println(x) // Text
}

注意: 上边的示例并不是更新字符串的正确姿势,因为一个 UTF8 编码的字符可能会占多个字节,比如汉字就需要 3~4个字节来存储,此时更新其中的一个字节是错误的。

更新字串的正确姿势:将 string 转为 rune slice(此时 1 个 rune 可能占多个 byte),直接更新 rune 中的字符

1
2
3
4
5
6
7
func main() {
x := "text"
xRunes := []rune(x)
xRunes[0] = '我'
x = string(xRunes)
fmt.Println(x) // 我ext
}

string 与 byte slice 之间的转换

当进行 string 和 byte slice 相互转换时,参与转换的是拷贝的原始值。这种转换的过程,与其他编程语的强制类型转换操作不同,也和新 slice 与旧 slice 共享底层数组不同。

Go 在 string 与 byte slice 相互转换上优化了两点,避免了额外的内存分配:

在 map[string] 中查找 key 时,使用了对应的 []byte,避免做 m[string(key)] 的内存分配
使用 for range 迭代 string 转换为 []byte 的迭代:for i,v := range []byte(str) {…}
18.string 与索引操作符
对字符串用索引访问返回的不是字符,而是一个 byte 值。

这种处理方式和其他语言一样,比如 PHP 中:

1
2
3
4
5
6
7
8
9
10
11
12
13
> php -r '$name="中文"; var_dump($name);'    # "中文" 占用 6 个字节
string(6) "中文"

> php -r '$name="中文"; var_dump($name[0]);' # 把第一个字节当做 Unicode 字符读取,显示 U+FFFD
string(1) "�"

> php -r '$name="中文"; var_dump($name[0].$name[1].$name[2]);'
string(3) "中"
func main() {
x := "ascii"
fmt.Println(x[0]) // 97
fmt.Printf("%T\n", x[0])// uint8
}

如果需要使用 for range 迭代访问字符串中的字符(unicode code point / rune),标准库中有 “unicode/utf8” 包来做 UTF8 的相关解码编码。另外 utf8string 也有像 func (s *String) At(i int) rune 等很方便的库函数。

字符串并不都是 UTF8 文本

string 的值不必是 UTF8 文本,可以包含任意的值。只有字符串是文字字面值时才是 UTF8 文本,字串可以通过转义来包含其他数据。

判断字符串是否是 UTF8 文本,可使用 “unicode/utf8” 包中的 ValidString() 函数:

1
2
3
4
5
6
7
8
9
10
func main() {
str1 := "ABC"
fmt.Println(utf8.ValidString(str1)) // true

str2 := "A\xfeC"
fmt.Println(utf8.ValidString(str2)) // false

str3 := "A\\xfeC"
fmt.Println(utf8.ValidString(str3)) // true // 把转义字符转义成字面值
}

字符串的长度

在 Python 中:

1
2
data = u'♥'  
print(len(data)) # 1

然而在 Go 中:

1
2
3
4
func main() {
char := "♥"
fmt.Println(len(char)) // 3
}

Go 的内建函数 len() 返回的是字符串的 byte 数量,而不是像 Python 中那样是计算 Unicode 字符数。

如果要得到字符串的字符数,可使用 “unicode/utf8” 包中的 RuneCountInString(str string) (n int)

1
2
3
4
func main() {
char := "♥"
fmt.Println(utf8.RuneCountInString(char)) // 1
}

注意: RuneCountInString 并不总是返回我们看到的字符数,因为有的字符会占用 2 个 rune:

1
2
3
4
5
6
func main() {
char := "é"
fmt.Println(len(char)) // 3
fmt.Println(utf8.RuneCountInString(char)) // 2
fmt.Println("cafe\u0301") // café // 法文的 cafe,实际上是两个 rune 的组合
}

不导出的 struct 字段无法被 encode

以小写字母开头的字段成员是无法被外部直接访问的,所以 struct 在进行 json、xml、gob 等格式的 encode 操作时,这些私有字段会被忽略,导出时得到零值:

1
2
3
4
5
6
7
8
9
10
11
func main() {
in := MyData{1, "two"}
fmt.Printf("%#v\n", in) // main.MyData{One:1, two:"two"}

encoded, _ := json.Marshal(in)
fmt.Println(string(encoded)) // {"One":1} // 私有字段 two 被忽略了

var out MyData
json.Unmarshal(encoded, &out)
fmt.Printf("%#v\n", out) // main.MyData{One:1, two:""}
}

sort

对map中的key进行排序怎么做?

io包

在io包中,io.Reader的扩展接口和实现类型都有哪些?它们分别都有什么功用?

  1. io.ReadWriter:此接口既是io.Reader的扩展接口,也是io.Writer的扩展接口。换句话说,该接口定义了一组行为,包含且仅包含了基本的字节序列读取方法Read,和字节序列写入方法Write。
  2. io.ReadCloser:此接口除了包含基本的字节序列读取方法之外,还拥有一个基本的关闭方法Close。后者一般用于关闭数据读写的通路。这个接口其实是io.Reader接口和io.Closer接口的组合。
  3. io.ReadWriteCloser:很明显,此接口是io.Reader、io.Writer和io.Closer这三个接口的组合。io.ReadSeeker:此接口的特点是拥有一个用于寻找读写位置的基本方法Seek。更具体地说,该方法可以根据给定的偏移量基于数据的起始位置、末尾位置,或者当前读写位置去寻找新的读写位置。这个新的读写位置用于表明下一次读或写时的起始索引。Seek是io.Seeker接口唯一拥有的方法。
  4. io.ReadWriteSeeker:显然,此接口是另一个三合一的扩展接口,它是io.Reader、io.Writer和io.Seeker的组合。

io包中的io.Reader接口的实现类型

  1. *io.LimitedReader:此类型的基本类型会包装io.Reader类型的值,并提供一个额外的受限读取的功能。所谓的受限读取指的是,此类型的读取方法Read返回的总数据量会受到限制,无论该方法被调用多少次。这个限制由该类型的字段N指明,单位是字节。

  2. *io.SectionReader:此类型的基本类型可以包装io.ReaderAt类型的值,并且会限制它的Read方法,只能够读取原始数据中的某一个部分(或者说某一段)。这个数据段的起始位置和末尾位置,需要在它被初始化的时候就指明,并且之后无法变更。该类型值的行为与切片有些类似,它只会对外暴露在其窗口之中的那些数据。

  3. *io.teeReader:此类型是一个包级私有的数据类型,也是io.TeeReader函数结果值的实际类型。这个函数接受两个参数r和w,类型分别是io.Reader和io.Writer。其结果值的Read方法会把r中的数据经过作为方法参数的字节切片p写入到w。可以说,这个值就是r和w之间的数据桥梁,而那个参数p就是这座桥上的数据搬运者。

  4. *io.multiReader:此类型也是一个包级私有的数据类型。类似的,io包中有一个名为MultiReader的函数,它可以接受若干个io.Reader类型的参数值,并返回一个实际类型为io.multiReader的结果值。当这个结果值的Read方法被调用时,它会顺序地从前面那些io.Reader类型的参数值中读取数据。因此,我们也可以称之为多对象读取器。

  5. io.pipe: 此类型为一个包级私有的数据类型,它比上述类型都要复杂得多。它不但实现了io.Reader接口,而且还实现了io.Writer接口。实际上,io.PipeReader类型和io.PipeWriter类型拥有的所有指针方法都是以它为基础的。这些方法都只是代理了io.pipe类型值所拥有的某一个方法而已。又因为io.Pipe函数会返回这两个类型的指针值并分别把它们作为其生成的同步内存管道的两端,所以可以说,io.pipe类型就是io包提供的同步内存管道的核心实现。

  6. *io.PipeReader:此类型可以被视为io.pipe类型的代理类型。它代理了后者的一部分功能,并基于后者实现了io.ReadCloser接口。同时,它还定义了同步内存管道的读取端。

io包中的接口都有哪些?它们之间都有着怎样的关系?

io包中的核心接口只有 3 个,它们是:io.Reader、io.Writer和io.Closer。

20220217231655

io包中的同步内存管道的运作机制是什么?

buffered I/O

这个代码包中的程序实体实现的 I/O 操作都内置了缓冲区。
bufio包中的数据类型主要有:Reader;Scanner;Writer和ReadWriter。

bufio.Reader类型值中的缓冲区起着怎样的作用?

bufio.Reader类型的值(以下简称Reader值)内的缓冲区,其实就是一个数据存储中介,它介于底层读取器与读取方法及其调用方之间。所谓的底层读取器,就是在初始化此类值的时候传入的io.Reader类型的参数值。
Reader值的读取方法一般都会先从其所属值的缓冲区中读取数据。同时,在必要的时候,它们还会预先从底层读取器那里读出一部分数据,并暂存于缓冲区之中以备后用。
有这样一个缓冲区的好处是,可以在大多数的时候降低读取方法的执行时间。虽然,读取方法有时还要负责填充缓冲区,但从总体来看,读取方法的平均执行时间一般都会因此有大幅度的缩短。

缓冲区的压缩包括两个步骤

第一步,把缓冲区中在[已读计数, 已写计数)范围之内的所有元素值(或者说字节)都依次拷贝到缓冲区的头部。

第二步中,fill方法会把已写计数的新值设定为原已写计数与原已读计数的差。这个差所代表的索引,就是压缩后第一次写入字节时的开始索引。

bufio.Writer类型值中缓冲的数据什么时候会被写到它的底层写入器?

bufio.Writer类型都有哪些字段

bufio.Reader类型读取方法有哪些不同?

bufio.Reader类型的Peek方法、ReadSlice方法和ReadLine方法都有可能会造成内容泄露。为什么?

bufio.Scanner类型的主要功用是什么?它有哪些特点?

ioutils

ioutil.ReadFile 适合文件的读取
ioutil.ReadAll 适合数据流的读取

底层实现都有切片长度判断,扩容(多次读取情况下)。
20220207225209

io

flag

bufio

os

sleep底层实现原理

onc

注意点
四个字节的uint32

  • 因为对它的操作必须是”原子的”
  • atomic.Loaduint32

uint 可以进行加减么?

执行函数束后,尽量快速执行

  • Do方法的参数函数没有结東扶行,任何之后调用该方法的 goroutine就都会被阻
  • 只有在这个参数函数执行结束以后,些 goroutine才会遂一核唤

defer done->1无论函数执行成功大

  • done都会变为1记得回头检查
  • planb宣一下失敗了再初始化一个Once韭续执行
喜欢这篇文章?打赏一下作者吧!

欢迎关注我的其它发布渠道