小黄

黄小黄的幸福生活!


  • 首页

  • 标签

  • 分类

  • 归档

  • Java

go源码解读-bytes包常用函数

发表于 2020-05-08 | 分类于 go , 源码解读

bytes包常用函数

  • bytes.go中提供了很多字节切片的工具类

Compare

  • Compare方法比较两个字节切片的大小,-1,0, 1
  • nil和空切片是相等的
1
2
3
func Compare(a, b []byte) int {
return bytealg.Compare(a, b)
}

Contains

  • Contains方法返回b中是否存在子切片subslice
1
2
3
func Contains(b, subslice []byte) bool {
return Index(b, subslice) != -1
}
  • Contains函数主题为函数Index
  • Index函数返回sep在s中找到的第一个实例的索引值,比如s为{1,2,3,1,2,3}, sep为{2,3},则Index函数返回1,如果没有匹配到,则返回-1
  • Index方法中,根据sep的长度做了一些优化,为0直接返回0,意思是index为0
  • sep长度为1时,利用IndexByte函数找一个字节是否在切片中出现过
  • sep的长度和s的长度相等时,利用Equal方法判断两个切片是否相等
  • sep长度小于MaxLen时,这个值根据操作系统在32或者64,
    • s的长度小于64时,直接用bytealg.Index函数进行暴力查找即可
    • 是的长度大于64时,先找到匹配sep第一个字符的索引值,然后判断第二个字符是否相等,相等则直接用Equal方法判断两个截取的切片是否相等。如果第二个字符不相等,则失败次数+1,同时比较失败次数和Cutover函数的返回值,如果错误次数过多,此时会直接进行暴力查找
  • sep的长度大于MaxLen时,前半部分和之前阐述的类似,不通的在于对失败错误的容忍度。
    • Cutover函数允许每8个字节出错一次,实验证明其切换间隔为16个字节时效率更高,此时采用RabinKarp算法进行查找
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
func Index(s, sep []byte) int {
n := len(sep)
switch {
case n == 0:
return 0
case n == 1:
return IndexByte(s, sep[0])
case n == len(s):
if Equal(sep, s) {
return 0
}
return -1
case n > len(s):
return -1
case n <= bytealg.MaxLen:
// Use brute force when s and sep both are small
if len(s) <= bytealg.MaxBruteForce {
return bytealg.Index(s, sep)
}
c0 := sep[0]
c1 := sep[1]
i := 0
t := len(s) - n + 1
fails := 0
for i < t {
if s[i] != c0 {
// IndexByte is faster than bytealg.Index, so use it as long as
// we're not getting lots of false positives.
o := IndexByte(s[i:t], c0)
if o < 0 {
return -1
}
i += o
}
if s[i+1] == c1 && Equal(s[i:i+n], sep) {
return i
}
fails++
i++
// Switch to bytealg.Index when IndexByte produces too many false positives.
if fails > bytealg.Cutover(i) {
r := bytealg.Index(s[i:], sep)
if r >= 0 {
return r + i
}
return -1
}
}
return -1
}
c0 := sep[0]
c1 := sep[1]
i := 0
fails := 0
t := len(s) - n + 1
for i < t {
if s[i] != c0 {
o := IndexByte(s[i:t], c0)
if o < 0 {
break
}
i += o
}
if s[i+1] == c1 && Equal(s[i:i+n], sep) {
return i
}
i++
fails++
if fails >= 4+i>>4 && i < t {
j := indexRabinKarp(s[i:], sep)
if j < 0 {
return -1
}
return i + j
}
}
return -1
}
func IndexByte(b []byte, c byte) int {
return bytealg.IndexByte(b, c)
}
func Equal(a, b []byte) bool {
return bytealg.Equal(a, b)
}
func Cutover(n int) int {
// 1 error per 8 characters, plus a few slop to start.
return (n + 16) / 8
}
  • RabinKarp算法实现,假定sep为{a,b},s为{d,c,a,b}
    • RabinKarp算法首先选取一个基准值,primeRK=16777619,这个
    • 然后用函数hashStr对sep进行hash运算,返回的结果包括两个
      • 第一个hash,表示的是对子串的计算值,其值可以理解为primeRK进制下该字节数组的值,则hash=97*primeRK+b
      • 第二个pow,返回的是和sep长度相关的值,由于pow的值为uint32,则实际上是个取余之后的值,RK^(len(sep-1) % 2^32,其目的是为了在s上找sep滑动时,去除掉最高位的hash值
    • 在s的最左边开始计算和sep长度相等的hash值为d*RK+c,如果匹配则直接返回索引0
    • 如果不匹配,则在s上进行长度为len(sep)的滑动,此时hash为d*Rk*Rk + c * RK + a - d * pow
    • 而实际上pow = RK*RK的,则hash值为c*RK + a,此时不符合继续滑动
    • 滑动之后hash值为a*RK + b,此时返回索引2
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
func indexRabinKarp(s, sep []byte) int {
// Rabin-Karp search
hashsep, pow := hashStr(sep)
n := len(sep)
var h uint32
for i := 0; i < n; i++ {
h = h*primeRK + uint32(s[i])
}
if h == hashsep && Equal(s[:n], sep) {
return 0
}
for i := n; i < len(s); {
h *= primeRK
h += uint32(s[i])
h -= pow * uint32(s[i-n])
i++
if h == hashsep && Equal(s[i-n:i], sep) {
return i - n
}
}
return -1
}
// 基准值
const primeRK = 16777619
func hashStr(sep []byte) (uint32, uint32) {
hash := uint32(0)
for i := 0; i < len(sep); i++ {
hash = hash*primeRK + uint32(sep[i])
}
var pow, sq uint32 = 1, primeRK
for i := len(sep); i > 0; i >>= 1 {
if i&1 != 0 {
pow *= sq
}
sq *= sq
}
return hash, pow
}
  • ContainsAny方法只要chars中任意一个utf-8编码的字符存在于字节切片中,返回true
    • IndexAny实际上为ContainsAny的主要函数
    • 如果s字节数组大于8并且是ASCII编码的,则用函数makeASCIISet构建ASCII集合
      • makeASCIISet返回的数据asciiSet为一个32字节的数据,从前向后每一个bit位的索引代表对应的ASCII的值,实际上32字节的数据是分为8个uint32的数字来表示的,这样如字符a的位置为第四个元素的倒数第二个bit位
      • 从而对s进行迭代,看其中的byte是否存asciiSet中,asciiSet提供了contain方法来校验其对应的bit位上是否存在该字符
      • bitmap可以最大限度的节省内存空间,但是代码会复杂一些,实际工作中自己做取舍
    • 否则对切片s进行rune转码,逐个字符迭代比较并返回结果,属于暴力方法
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
func ContainsAny(b []byte, chars string) bool {
return IndexAny(b, chars) >= 0
}
func IndexAny(s []byte, chars string) int {
if chars == "" {
// Avoid scanning all of s.
return -1
}
if len(s) > 8 {
if as, isASCII := makeASCIISet(chars); isASCII {
for i, c := range s {
if as.contains(c) {
return i
}
}
return -1
}
}
var width int
for i := 0; i < len(s); i += width {
r := rune(s[i])
if r < utf8.RuneSelf {
width = 1
} else {
r, width = utf8.DecodeRune(s[i:])
}
for _, ch := range chars {
if r == ch {
return i
}
}
}
return -1
}
type asciiSet [8]uint32
func makeASCIISet(chars string) (as asciiSet, ok bool) {
for i := 0; i < len(chars); i++ {
c := chars[i]
if c >= utf8.RuneSelf {
return as, false
}
as[c>>5] |= 1 << uint(c&31)
}
return as, true
}
// contains reports whether c is inside the set.
func (as *asciiSet) contains(c byte) bool {
return (as[c>>5] & (1 << uint(c&31))) != 0
}
  • ContainsRune方法返回切片b中是否含有字符r,返回找到的匹配字节的索引值
  • 通过方法IndexRune来实现
    • 如果r是ASCII字符,调用函数IndexBytes来完成查找
    • 如果要查找的是utf8.RuneError,则返回第一个不合法的序列
    • 否则将rune转换成字节切片,调用Index方法完成查找
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
func ContainsRune(b []byte, r rune) bool {
return IndexRune(b, r) >= 0
}
func IndexRune(s []byte, r rune) int {
switch {
case 0 <= r && r < utf8.RuneSelf:
return IndexByte(s, byte(r))
case r == utf8.RuneError:
for i := 0; i < len(s); {
r1, n := utf8.DecodeRune(s[i:])
if r1 == utf8.RuneError {
return i
}
i += n
}
return -1
case !utf8.ValidRune(r):
return -1
default:
var b [utf8.UTFMax]byte
n := utf8.EncodeRune(b[:], r)
return Index(s, b[:n])
}
}

Count

  • Count函数返回字节数组中s存在sep的个数
    • sep长度为0,则返回s中字符数+1
    • sep长度为1,调用bytealg.Count函数返回结果
    • 否则,利用Index方法来进行个数查找
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
func Count(s, sep []byte) int {
if len(sep) == 0 {
return utf8.RuneCount(s) + 1
}
if len(sep) == 1 {
return bytealg.Count(s, sep[0])
}
n := 0
for {
i := Index(s, sep)
if i == -1 {
return n
}
n++
s = s[i+len(sep):]
}
}

Equal

  • Equal的实现是调用更底层的Equal方法
  • Equal实际上只是看两个字节切片的长度和元素值是否相等,不看是否来自同一个底层数组的引用
  • nil和空切片是相等的
1
2
3
func Equal(a, b []byte) bool {
return bytealg.Equal(a, b)
}

EqualFold

  • EqualFold方法将判断s,t在Unicode大小写折叠下是否相等
  • 如果是ASCII编码则简单比较大小写即可,否则需要进行unicode.SimpleFold进行转换并比较
  • 判断也是值是否相等
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
func EqualFold(s, t []byte) bool {
for len(s) != 0 && len(t) != 0 {
// Extract first rune from each.
var sr, tr rune
if s[0] < utf8.RuneSelf {
sr, s = rune(s[0]), s[1:]
} else {
r, size := utf8.DecodeRune(s)
sr, s = r, s[size:]
}
if t[0] < utf8.RuneSelf {
tr, t = rune(t[0]), t[1:]
} else {
r, size := utf8.DecodeRune(t)
tr, t = r, t[size:]
}
// If they match, keep going; if not, return false.
// Easy case.
if tr == sr {
continue
}
// Make sr < tr to simplify what follows.
if tr < sr {
tr, sr = sr, tr
}
// Fast check for ASCII.
if tr < utf8.RuneSelf {
// ASCII only, sr/tr must be upper/lower case
if 'A' <= sr && sr <= 'Z' && tr == sr+'a'-'A' {
continue
}
return false
}
// General case. SimpleFold(x) returns the next equivalent rune > x
// or wraps around to smaller values.
r := unicode.SimpleFold(sr)
for r != sr && r < tr {
r = unicode.SimpleFold(r)
}
if r == tr {
continue
}
return false
}
// One string is empty. Are both?
return len(s) == len(t)
}

Fields

  • Fields函数将字节切片s,以空白字符切割,并返回[][]byte类型结果,如[]byte("1 2 3 4")返回[]string{"1", "2", "3", "4"}
  • 用setBits来记录s中出现过的字节
  • 如果setBits>utf8.RuneSelf表示存在不是ASCII编码的数据,此时调用FieldsFunc方法来完成分割操作
    • FieldFunc传入的第二个参数为unicode.IsSpace,来判断一个字符是否为空白字符
    • span用于记录各个分割出的Fied的起点和终点,从而组织输出数据
  • 如果setBits<utf8.RuneSelf则表示都是ASCII编码,而ASCII编码的空白字符为[256]uint{'\t': 1, '\n': 1, '\v': 1, '\f': 1, '\r': 1, ' ': 1},可以快速进行分割
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
func Fields(s []byte) [][]byte {
n := 0
wasSpace := 1
// setBits is used to track which bits are set in the bytes of s.
setBits := uint8(0)
for i := 0; i < len(s); i++ {
r := s[i]
setBits |= r
isSpace := int(asciiSpace[r])
n += wasSpace & ^isSpace
wasSpace = isSpace
}
if setBits >= utf8.RuneSelf {
// Some runes in the input slice are not ASCII.
return FieldsFunc(s, unicode.IsSpace)
}
// ASCII fast path
a := make([][]byte, n)
na := 0
fieldStart := 0
i := 0
// Skip spaces in the front of the input.
for i < len(s) && asciiSpace[s[i]] != 0 {
i++
}
fieldStart = i
for i < len(s) {
if asciiSpace[s[i]] == 0 {
i++
continue
}
a[na] = s[fieldStart:i:i]
na++
i++
// Skip spaces in between fields.
for i < len(s) && asciiSpace[s[i]] != 0 {
i++
}
fieldStart = i
}
if fieldStart < len(s) { // Last field might end at EOF.
a[na] = s[fieldStart:len(s):len(s)]
}
return a
}
func FieldsFunc(s []byte, f func(rune) bool) [][]byte {
// A span is used to record a slice of s of the form s[start:end].
// The start index is inclusive and the end index is exclusive.
type span struct {
start int
end int
}
spans := make([]span, 0, 32)
// Find the field start and end indices.
wasField := false
fromIndex := 0
for i := 0; i < len(s); {
size := 1
r := rune(s[i])
if r >= utf8.RuneSelf {
r, size = utf8.DecodeRune(s[i:])
}
if f(r) {
if wasField {
spans = append(spans, span{start: fromIndex, end: i})
wasField = false
}
} else {
if !wasField {
fromIndex = i
wasField = true
}
}
i += size
}
// Last field might end at EOF.
if wasField {
spans = append(spans, span{fromIndex, len(s)})
}
// Create subslices from recorded field indices.
a := make([][]byte, len(spans))
for i, span := range spans {
a[i] = s[span.start:span.end:span.end]
}
return a
}

Has函数

  • HasPrefix,HasSuffix函数本质上是用Equals方法判断截取后的字节切片是否相等
1
2
3
4
5
6
7
8
9
// HasPrefix tests whether the byte slice s begins with prefix.
func HasPrefix(s, prefix []byte) bool {
return len(s) >= len(prefix) && Equal(s[0:len(prefix)], prefix)
}
// HasSuffix tests whether the byte slice s ends with suffix.
func HasSuffix(s, suffix []byte) bool {
return len(s) >= len(suffix) && Equal(s[len(s)-len(suffix):], suffix)
}

Join

  • Join函数,Fields的逆向操作,将二维字切片用sep切片拼接为一维字节切片
  • 对于二维切片数组不同的长度做了相应的拼接处理
  • 拼接过程中用内建函数copy来完成
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
func Join(s [][]byte, sep []byte) []byte {
if len(s) == 0 {
return []byte{}
}
if len(s) == 1 {
// Just return a copy.
return append([]byte(nil), s[0]...)
}
n := len(sep) * (len(s) - 1)
for _, v := range s {
n += len(v)
}
b := make([]byte, n)
bp := copy(b, s[0])
for _, v := range s[1:] {
bp += copy(b[bp:], sep)
bp += copy(b[bp:], v)
}
return b
}

Last类函数

  • LastIndex函数返回s中sep最后一次出现的索引值
  • LastIndexByte返回字节c在s中最后一次出现的索引值
  • LastIndexAny返回s中最后一个出现的chars中的任意一个字符的索引值
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
func LastIndex(s, sep []byte) int {
n := len(sep)
if n == 0 {
return len(s)
}
c := sep[0]
for i := len(s) - n; i >= 0; i-- {
if s[i] == c && (n == 1 || Equal(s[i:i+n], sep)) {
return i
}
}
return -1
}
func LastIndexByte(s []byte, c byte) int {
for i := len(s) - 1; i >= 0; i-- {
if s[i] == c {
return i
}
}
return -1
}
func LastIndexAny(s []byte, chars string) int {
if chars == "" {
// Avoid scanning all of s.
return -1
}
if len(s) > 8 {
if as, isASCII := makeASCIISet(chars); isASCII {
for i := len(s) - 1; i >= 0; i-- {
if as.contains(s[i]) {
return i
}
}
return -1
}
}
for i := len(s); i > 0; {
r, size := utf8.DecodeLastRune(s[:i])
i -= size
for _, c := range chars {
if r == c {
return i
}
}
}
return -1
}
  • LastIndexFunc返回满足函数f的最后一个字符的索引值
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
func LastIndexFunc(s []byte, f func(r rune) bool) int {
return lastIndexFunc(s, f, true)
}
func lastIndexFunc(s []byte, f func(r rune) bool, truth bool) int {
for i := len(s); i > 0; {
r, size := rune(s[i-1]), 1
if r >= utf8.RuneSelf {
r, size = utf8.DecodeLastRune(s[0:i])
}
i -= size
if f(r) == truth {
return i
}
}
return -1
}

Map

  • Map函数根据函数mapping完成s中字符的映射变换
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
func Map(mapping func(r rune) rune, s []byte) []byte {
maxbytes := len(s) // length of b
nbytes := 0 // number of bytes encoded in b
b := make([]byte, maxbytes)
for i := 0; i < len(s); {
wid := 1
r := rune(s[i])
if r >= utf8.RuneSelf {
r, wid = utf8.DecodeRune(s[i:])
}
r = mapping(r)
if r >= 0 {
rl := utf8.RuneLen(r)
if rl < 0 {
rl = len(string(utf8.RuneError))
}
if nbytes+rl > maxbytes {
// Grow the buffer.
maxbytes = maxbytes*2 + utf8.UTFMax
nb := make([]byte, maxbytes)
copy(nb, b[0:nbytes])
b = nb
}
nbytes += utf8.EncodeRune(b[nbytes:maxbytes], r)
}
i += wid
}
return b[0:nbytes]
}

Repeat

  • Repeat函数完成对s的多份复制,如{a,b}变为{a,b,a,b}
  • 程序中进行相乘的时候需要考虑溢出问题
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
func Repeat(b []byte, count int) []byte {
if count < 0 {
panic("bytes: negative Repeat count")
} else if count > 0 && len(b)*count/count != len(b) {
panic("bytes: Repeat count causes overflow")
}
nb := make([]byte, len(b)*count)
bp := copy(nb, b)
for bp < len(nb) {
copy(nb[bp:], nb[:bp])
bp *= 2
}
return nb
}

Replace

  • Replace函数完成字节切片替换,n为替换的个数,n<0则替换全部
  • Count函数用于计算old切片出现的次数
  • Index用于查找要替换的旧切片old
  • ReplaceAll将n设置为-1即可
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
func Replace(s, old, new []byte, n int) []byte {
m := 0
if n != 0 {
m = Count(s, old)
}
if m == 0 {
return append([]byte(nil), s...)
}
if n < 0 || m < n {
n = m
}
// Apply replacements to buffer.
t := make([]byte, len(s)+n*(len(new)-len(old)))
w := 0
start := 0
for i := 0; i < n; i++ {
j := start
if len(old) == 0 {
if i > 0 {
_, wid := utf8.DecodeRune(s[start:])
j += wid
}
} else {
j += Index(s[start:], old)
}
w += copy(t[w:], s[start:j])
w += copy(t[w:], new)
start = j + len(old)
}
w += copy(t[w:], s[start:])
return t[0:w]
}
func ReplaceAll(s, old, new []byte) []byte {
return Replace(s, old, new, -1)
}

Rune

  • Rune函数将字节数组转换成Rune切片,主要调用utf8编码的函数来实现
1
2
3
4
5
6
7
8
9
10
11
func Runes(s []byte) []rune {
t := make([]rune, utf8.RuneCount(s))
i := 0
for len(s) > 0 {
r, l := utf8.DecodeRune(s)
t[i] = r
i++
s = s[l:]
}
return t
}

Split函数

  • SplitN函数表示将s按照分隔符sep分割成n份,不带分隔符,最后一份不再进行分割
  • SplitAfterN表示将s按照分隔符sep分割成n份,最后一份不再进行分割,带分隔符
  • Split函数将s按照分隔符sep进行分割,不带分隔符
  • SplitAfter函数将s按照分隔符sep进行分割,带分隔符
1
2
3
4
5
6
7
8
9
10
11
func SplitN(s, sep []byte, n int) [][]byte { return genSplit(s, sep, 0, n) }
func SplitAfterN(s, sep []byte, n int) [][]byte {
return genSplit(s, sep, len(sep), n)
}
func Split(s, sep []byte) [][]byte { return genSplit(s, sep, 0, -1) }
func SplitAfter(s, sep []byte) [][]byte {
return genSplit(s, sep, len(sep), -1)
}
  • Split类方法实际上都是对genSplit方法的封装
  • sep为空的时候,用explode完成对字节切片的按照字符的分割
  • 索引定位的时候也是通过Index方法来实现的
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
func genSplit(s, sep []byte, sepSave, n int) [][]byte {
if n == 0 {
return nil
}
if len(sep) == 0 {
return explode(s, n)
}
if n < 0 {
n = Count(s, sep) + 1
}
a := make([][]byte, n)
n--
i := 0
for i < n {
m := Index(s, sep)
if m < 0 {
break
}
a[i] = s[: m+sepSave : m+sepSave]
s = s[m+len(sep):]
i++
}
a[i] = s
return a[:i+1]
}
func explode(s []byte, n int) [][]byte {
if n <= 0 {
n = len(s)
}
a := make([][]byte, n)
var size int
na := 0
for len(s) > 0 {
if na+1 >= n {
a[na] = s
na++
break
}
_, size = utf8.DecodeRune(s)
a[na] = s[0:size:size]
s = s[size:]
na++
}
return a[0:na]
}

Title

  • Title方法利用Map完成对切片s首字母的大写转换
  • isSeparator标识一个字符是否为边界字符
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
func Title(s []byte) []byte {
prev := ' '
return Map(
func(r rune) rune {
if isSeparator(prev) {
prev = r
return unicode.ToTitle(r)
}
prev = r
return r
},
s)
}
func isSeparator(r rune) bool {
// ASCII alphanumerics ad underscore are not separators
if r <= 0x7F {
switch {
case '0' <= r && r <= '9':
return false
case 'a' <= r && r <= 'z':
return false
case 'A' <= r && r <= 'Z':
return false
case r == '_':
return false
}
return true
}
// Letters and digits are not separators
if unicode.IsLetter(r) || unicode.IsDigit(r) {
return false
}
// Otherwise, all we can do for now is treat spaces as separators.
return unicode.IsSpace(r)
}

To类函数

  • To类函数实际上都是对Map的一层封装,具体对字符的操作可以参照unicode包的方法
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
func ToUpper(s []byte) []byte { return Map(unicode.ToUpper, s) }
func ToLower(s []byte) []byte { return Map(unicode.ToLower, s) }
func ToTitle(s []byte) []byte { return Map(unicode.ToTitle, s) }
func ToUpperSpecial(c unicode.SpecialCase, s []byte) []byte {
return Map(c.ToUpper, s)
}
func ToLowerSpecial(c unicode.SpecialCase, s []byte) []byte {
return Map(c.ToLower, s)
}
func ToTitleSpecial(c unicode.SpecialCase, s []byte) []byte {
return Map(c.ToTitle, s)
}

Trim类函数

  • Trim函数完成字节切片前后去除指定的字符数据
  • Trim函数的实现是通过TrimFunc函数来实现的
  • TrimFunc函数是通过组合TrimRightFunc和TrimLeftFunc来实现的
  • TrimRightFunc完成右侧的字符截取,通过lastIndexFunc函数完成
  • TrimLeftFunc完成左侧的字符截取,通过indexFunc函数完成
  • Trim函数的筛选函数f是makeCutsetFunc函数的返回值
  • makeCutsetFunc会分情况返回当前的字节是否属于该剔除的字符
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
func Trim(s []byte, cutset string) []byte {
return TrimFunc(s, makeCutsetFunc(cutset))
}
func TrimFunc(s []byte, f func(r rune) bool) []byte {
return TrimRightFunc(TrimLeftFunc(s, f), f)
}
func TrimRightFunc(s []byte, f func(r rune) bool) []byte {
i := lastIndexFunc(s, f, false)
if i >= 0 && s[i] >= utf8.RuneSelf {
_, wid := utf8.DecodeRune(s[i:])
i += wid
} else {
i++
}
return s[0:i]
}
func TrimLeftFunc(s []byte, f func(r rune) bool) []byte {
i := indexFunc(s, f, false)
if i == -1 {
return nil
}
return s[i:]
}
func makeCutsetFunc(cutset string) func(r rune) bool {
if len(cutset) == 1 && cutset[0] < utf8.RuneSelf {
return func(r rune) bool {
return r == rune(cutset[0])
}
}
if as, isASCII := makeASCIISet(cutset); isASCII {
return func(r rune) bool {
return r < utf8.RuneSelf && as.contains(byte(r))
}
}
return func(r rune) bool {
for _, c := range cutset {
if c == r {
return true
}
}
return false
}
}
  • TrimPrefix完成指定前缀的截取
  • TrimSuffix完成指定后缀的截取
  • TrimSpace完成对前后空白字符的截取
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
func TrimPrefix(s, prefix []byte) []byte {
if HasPrefix(s, prefix) {
return s[len(prefix):]
}
return s
}
func TrimSuffix(s, suffix []byte) []byte {
if HasSuffix(s, suffix) {
return s[:len(s)-len(suffix)]
}
return s
}
func TrimSpace(s []byte) []byte {
return TrimFunc(s, unicode.IsSpace)
}

go源码解读-bytes.Buffer

发表于 2020-05-07 | 分类于 go , 源码解读

bytes.Buffer

  • Buffer结构体
    • bytes.Buffer实际上就是长度可变的,可读可写的数组buffer
    • bytes.Buffer的零值为一个可用的buffer
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
type Buffer struct {
buf []byte // 字节切片,Buffer中的内容为切片[off: len(buf)]
off int // 读取索引 &buf[off], 写入从&buf[len(buf)]写入
lastRead readOp // 记录上一次对buffer的操作,从而实现撤销操作
}
// 最后一次操作buffer的行为
type readOp int8
const (
opRead readOp = -1 // Any other read operation.
opInvalid readOp = 0 // Non-read operation.
opReadRune1 readOp = 1 // Read rune of size 1.
opReadRune2 readOp = 2 // Read rune of size 2.
opReadRune3 readOp = 3 // Read rune of size 3.
opReadRune4 readOp = 4 // Read rune of size 4.
)

Buffer结构体的方法

  • Bytes方法返回Buffer未读取的字节数组
1
func (b *Buffer) Bytes() []byte { return b.buf[b.off:] }
  • String方法以字符串的格式返回未读取的buffer数据
1
2
3
4
5
6
7
func (b *Buffer) String() string {
if b == nil {
// Special case, useful in debugging.
return "<nil>"
}
return string(b.buf[b.off:])
}
  • Len方法返回未读取的字节数
  • Cap方法返回buffer数据分配的内存大小
1
2
3
func (b *Buffer) Len() int { return len(b.buf) - b.off }
func (b *Buffer) Cap() int { return cap(b.buf) }
  • Truncate截取b.buf[:b.off+n]为新的buf
  • Reset方法为重置操作,创建一个零值的Buffer
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
func (b *Buffer) Truncate(n int) {
if n == 0 {
b.Reset()
return
}
b.lastRead = opInvalid
if n < 0 || n > b.Len() {
panic("bytes.Buffer: truncation out of range")
}
b.buf = b.buf[:b.off+n]
}
func (b *Buffer) Reset() {
b.buf = b.buf[:0]
b.off = 0
b.lastRead = opInvalid
}
  • Grow方法对buffer的容量进行扩容,n指定扩容的字节数大小
  • n为负数会panic, buffer不能扩容也会报错
  • grow方法返回的m为写入数据的起点
  • tryGrowByReslice方法是在buf本身容量是足够的情况下的扩容操作,只需要重新获取切片接口
  • 扩容时是有最小扩容空间的,为64字节,n<64则会被赋值为64,避免小尺度多次扩容的情况
  • 扩容时,会优先考虑复用已经分配的切片空间,不能满足其需求则会重新扩容
  • makeSlice创建一个新的slice并分配空间,扩容尺寸为原来分配的内存空间的2倍+n个字节
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
func (b *Buffer) Grow(n int) {
if n < 0 {
panic("bytes.Buffer.Grow: negative count")
}
m := b.grow(n)
b.buf = b.buf[:m]
}
func (b *Buffer) grow(n int) int {
m := b.Len()
// If buffer is empty, reset to recover space.
if m == 0 && b.off != 0 {
b.Reset()
}
// Try to grow by means of a reslice.
if i, ok := b.tryGrowByReslice(n); ok {
return i
}
if b.buf == nil && n <= smallBufferSize {
b.buf = make([]byte, n, smallBufferSize)
return 0
}
c := cap(b.buf)
if n <= c/2-m {
// We can slide things down instead of allocating a new
// slice. We only need m+n <= c to slide, but
// we instead let capacity get twice as large so we
// don't spend all our time copying.
copy(b.buf, b.buf[b.off:])
} else if c > maxInt-c-n {
panic(ErrTooLarge)
} else {
// Not enough space anywhere, we need to allocate.
buf := makeSlice(2*c + n)
copy(buf, b.buf[b.off:])
b.buf = buf
}
// Restore b.off and len(b.buf).
b.off = 0
b.buf = b.buf[:m+n]
return m
}
func (b *Buffer) tryGrowByReslice(n int) (int, bool) {
if l := len(b.buf); n <= cap(b.buf)-l {
b.buf = b.buf[:l+n]
return l, true
}
return 0, false
}
// makeSlice allocates a slice of size n. If the allocation fails, it panics
// with ErrTooLarge.
func makeSlice(n int) []byte {
// If the make fails, give a known error.
defer func() {
if recover() != nil {
panic(ErrTooLarge)
}
}()
return make([]byte, n)
}
  • Write方法向buf中写入字节数组p
  • WriteString向buf中写入字符串,内建方法copy支持复制字符串到字节数组中
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
func (b *Buffer) Write(p []byte) (n int, err error) {
b.lastRead = opInvalid
m, ok := b.tryGrowByReslice(len(p))
if !ok {
m = b.grow(len(p))
}
return copy(b.buf[m:], p), nil
}
func (b *Buffer) WriteString(s string) (n int, err error) {
b.lastRead = opInvalid
m, ok := b.tryGrowByReslice(len(s))
if !ok {
m = b.grow(len(s))
}
return copy(b.buf[m:], s), nil
}
  • MinRead为ReadFrom最少读入的字节数
  • ReadFrom从实现了io.Reader接口的结构体中读取数据
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
const MinRead = 512
func (b *Buffer) ReadFrom(r io.Reader) (n int64, err error) {
b.lastRead = opInvalid
for {
i := b.grow(MinRead)
b.buf = b.buf[:i]
m, e := r.Read(b.buf[i:cap(b.buf)])
if m < 0 {
panic(errNegativeRead)
}
b.buf = b.buf[:i+m]
n += int64(m)
if e == io.EOF {
return n, nil // e is EOF, so return nil explicitly
}
if e != nil {
return n, e
}
}
}
  • WriteTo 将数据写入到,直到Buffer中数据为空
  • WriteByte方法将字节c写入到buffer中
  • WriteRune方法将字符r写入到buffer中
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
func (b *Buffer) WriteTo(w io.Writer) (n int64, err error) {
b.lastRead = opInvalid
if nBytes := b.Len(); nBytes > 0 {
m, e := w.Write(b.buf[b.off:])
if m > nBytes {
panic("bytes.Buffer.WriteTo: invalid Write count")
}
b.off += m
n = int64(m)
if e != nil {
return n, e
}
// all bytes should have been written, by definition of
// Write method in io.Writer
if m != nBytes {
return n, io.ErrShortWrite
}
}
// Buffer is now empty; reset.
b.Reset()
return n, nil
}
func (b *Buffer) WriteByte(c byte) error {
b.lastRead = opInvalid
m, ok := b.tryGrowByReslice(1)
if !ok {
m = b.grow(1)
}
b.buf[m] = c
return nil
}
  • Read方法从buffer中读取len(p)个字节到字节切片p中
  • Next返回从buffer中将要读取到的n个字节切片
  • ReadByte从buffer中读取一个字节
  • ReadRune从buffer中读取一个字符
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
func (b *Buffer) Read(p []byte) (n int, err error) {
b.lastRead = opInvalid
if b.empty() {
// Buffer is empty, reset to recover space.
b.Reset()
if len(p) == 0 {
return 0, nil
}
return 0, io.EOF
}
n = copy(p, b.buf[b.off:])
b.off += n
if n > 0 {
b.lastRead = opRead
}
return n, nil
}
func (b *Buffer) Next(n int) []byte {
b.lastRead = opInvalid
m := b.Len()
if n > m {
n = m
}
data := b.buf[b.off : b.off+n]
b.off += n
if n > 0 {
b.lastRead = opRead
}
return data
}
func (b *Buffer) ReadByte() (byte, error) {
if b.empty() {
// Buffer is empty, reset to recover space.
b.Reset()
return 0, io.EOF
}
c := b.buf[b.off]
b.off++
b.lastRead = opRead
return c, nil
}
func (b *Buffer) ReadRune() (r rune, size int, err error) {
if b.empty() {
// Buffer is empty, reset to recover space.
b.Reset()
return 0, 0, io.EOF
}
c := b.buf[b.off]
if c < utf8.RuneSelf {
b.off++
b.lastRead = opReadRune1
return rune(c), 1, nil
}
r, n := utf8.DecodeRune(b.buf[b.off:])
b.off += n
b.lastRead = readOp(n)
return r, n, nil
}
  • UnreadRune,撤销ReadRune方法的操作
  • UnreadByte,撤销ReadByte方法的操作
  • UnreadRune,撤销ReadRune方法的操作
  • UnreadRune,撤销ReadRune方法的操作
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
func (b *Buffer) UnreadRune() error {
if b.lastRead <= opInvalid {
return errors.New("bytes.Buffer: UnreadRune: previous operation was not a successful ReadRune")
}
if b.off >= int(b.lastRead) {
b.off -= int(b.lastRead)
}
b.lastRead = opInvalid
return nil
}
// bytes, UnreadByte returns an error.
func (b *Buffer) UnreadByte() error {
if b.lastRead == opInvalid {
return errors.New("bytes.Buffer: UnreadByte: previous operation was not a successful read")
}
b.lastRead = opInvalid
if b.off > 0 {
b.off--
}
return nil
}
  • ReadBytes从buffer中读取字节切片直到遇到delim字节,返回结果中包含delim字节
  • ReadString从buffer中读取字符串,直到遇到delim字节
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
func (b *Buffer) ReadBytes(delim byte) (line []byte, err error) {
slice, err := b.readSlice(delim)
// return a copy of slice. The buffer's backing array may
// be overwritten by later calls.
line = append(line, slice...)
return line, err
}
// readSlice is like ReadBytes but returns a reference to internal buffer data.
func (b *Buffer) readSlice(delim byte) (line []byte, err error) {
i := IndexByte(b.buf[b.off:], delim)
end := b.off + i + 1
if i < 0 {
end = len(b.buf)
err = io.EOF
}
line = b.buf[b.off:end]
b.off = end
b.lastRead = opRead
return line, err
}
func (b *Buffer) ReadString(delim byte) (line string, err error) {
slice, err := b.readSlice(delim)
return string(slice), err
}

初始化方法

1
2
3
4
5
func NewBuffer(buf []byte) *Buffer { return &Buffer{buf: buf} }
func NewBufferString(s string) *Buffer {
return &Buffer{buf: []byte(s)}
}

go源码解读-strings.Builder

发表于 2020-05-07 | 分类于 go , 源码解读

builder

  • Builder是效率最高,内存使用最少的创建一个字符串的方法
  • Builder结构体包含两个参数
    • addr 字符串的地址
    • buf 字节数组
1
2
3
4
5
6
7
// A Builder is used to efficiently build a string using Write methods.
// It minimizes memory copying. The zero value is ready to use.
// Do not copy a non-zero Builder.
type Builder struct {
addr *Builder // of receiver, to detect copies by value
buf []byte
}

常用方法

  • String方法返回Builder构建的数据
  • Len方法返回字节数组占据的字节数,1个汉字三个字节
  • Cap方法返回字节数组分配的内存空间大小
  • Reset方法将Builder重置为初始状态
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// String returns the accumulated string.
func (b *Builder) String() string {
return *(*string)(unsafe.Pointer(&b.buf))
}
// Len returns the number of accumulated bytes; b.Len() == len(b.String()).
func (b *Builder) Len() int { return len(b.buf) }
// Cap returns the capacity of the builder's underlying byte slice. It is the
// total space allocated for the string being built and includes any bytes
// already written.
func (b *Builder) Cap() int { return cap(b.buf) }
// Reset resets the Builder to be empty.
func (b *Builder) Reset() {
b.addr = nil
b.buf = nil
}

Grow方法

  • Grow方法来扩展buf数组的分配内存的大小
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
// grow copies the buffer to a new, larger buffer so that there are at least n
// bytes of capacity beyond len(b.buf).
func (b *Builder) grow(n int) {
buf := make([]byte, len(b.buf), 2*cap(b.buf)+n)
copy(buf, b.buf)
b.buf = buf
}
// Grow grows b's capacity, if necessary, to guarantee space for
// another n bytes. After Grow(n), at least n bytes can be written to b
// without another allocation. If n is negative, Grow panics.
func (b *Builder) Grow(n int) {
b.copyCheck()
if n < 0 {
panic("strings.Builder.Grow: negative count")
}
if cap(b.buf)-len(b.buf) < n {
b.grow(n)
}
}
func (b *Builder) copyCheck() {
if b.addr == nil {
// This hack works around a failing of Go's escape analysis
// that was causing b to escape and be heap allocated.
// See issue 23382.
// TODO: once issue 7921 is fixed, this should be reverted to
// just "b.addr = b".
b.addr = (*Builder)(noescape(unsafe.Pointer(b)))
} else if b.addr != b {
panic("strings: illegal use of non-zero Builder copied by value")
}
}
func noescape(p unsafe.Pointer) unsafe.Pointer {
x := uintptr(p)
return unsafe.Pointer(x ^ 0)
}

Write相关方法

  • Write方法将字节数组加添加到buf数组后面
  • WriteByte将字节c添加到buf数组后边
  • WriteRune将rune字符添加到buf数组后面
  • WriteString将字符串添加到buf数组后面
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
// Write appends the contents of p to b's buffer.
// Write always returns len(p), nil.
func (b *Builder) Write(p []byte) (int, error) {
b.copyCheck()
b.buf = append(b.buf, p...)
return len(p), nil
}
// WriteByte appends the byte c to b's buffer.
// The returned error is always nil.
func (b *Builder) WriteByte(c byte) error {
b.copyCheck()
b.buf = append(b.buf, c)
return nil
}
// WriteRune appends the UTF-8 encoding of Unicode code point r to b's buffer.
// It returns the length of r and a nil error.
func (b *Builder) WriteRune(r rune) (int, error) {
b.copyCheck()
if r < utf8.RuneSelf {
b.buf = append(b.buf, byte(r))
return 1, nil
}
l := len(b.buf)
if cap(b.buf)-l < utf8.UTFMax {
b.grow(utf8.UTFMax)
}
n := utf8.EncodeRune(b.buf[l:l+utf8.UTFMax], r)
b.buf = b.buf[:l+n]
return n, nil
}
// WriteString appends the contents of s to b's buffer.
// It returns the length of s and a nil error.
func (b *Builder) WriteString(s string) (int, error) {
b.copyCheck()
b.buf = append(b.buf, s...)
return len(s), nil
}

go源码解读-bytes.Reader

发表于 2020-05-07 | 分类于 go , 源码解读

bytes.Reader

  • bytes包中的结构体Reader实现的接口包括
    • io.Reader
    • io.ReaderAt
    • io.WriterTo
    • io.Seeker,
    • io.ByteScanner
    • io.RuneScanner

Reader结构体机器方法

  • bytes.Reader可以实现从字节数组中读取数据
  • bytes.Reader是只读的,并且支持查找
  • bytes.Reader的零值等价于从空slice中读取的值
1
2
3
4
5
type Reader struct {
s []byte // 字节数组
i int64 // 当前读取到的索引
prevRune int // 上一个读取的字符的索引
}

Reader结构体的方法

  • Len方法返回Reader结构体中还未读取的部分的字节数
  • Size方法返回Reader结构体中字节数组s的字节数,Size这个方法无论何时调用,返回结果都应该相同
  • Reset方法,初始化Reader为最初从字节b读取的状态,重置方法
1
2
3
4
5
6
7
8
9
10
func (r *Reader) Len() int {
if r.i >= int64(len(r.s)) {
return 0
}
return int(int64(len(r.s)) - r.i)
}
func (r *Reader) Size() int64 { return int64(len(r.s)) }
func (r *Reader) Reset(b []byte) { *r = Reader{b, 0, -1} }
  • 实现io.Reader接口的Read方法,将数据读取到字节数组byte中
1
2
3
4
5
6
7
8
9
func (r *Reader) Read(b []byte) (n int, err error) {
if r.i >= int64(len(r.s)) {
return 0, io.EOF
}
r.prevRune = -1
n = copy(b, r.s[r.i:])
r.i += int64(n)
return
}
  • 实现io.ReaderAt接口的ReadAt方法,从offset的位置开始读取
1
2
3
4
5
6
7
8
9
10
11
12
13
14
func (r *Reader) ReadAt(b []byte, off int64) (n int, err error) {
// cannot modify state - see io.ReaderAt
if off < 0 {
return 0, errors.New("bytes.Reader.ReadAt: negative offset")
}
if off >= int64(len(r.s)) {
return 0, io.EOF
}
n = copy(b, r.s[off:])
if n < len(b) {
err = io.EOF
}
return
}
  • 实现io.ByteReader的ReadByte方法,每次读取一个byte数据
1
2
3
4
5
6
7
8
9
10
// ReadByte implements the interface.
func (r *Reader) ReadByte() (byte, error) {
r.prevRune = -1
if r.i >= int64(len(r.s)) {
return 0, io.EOF
}
b := r.s[r.i]
r.i++
return b, nil
}
  • 实现io.ByteScanner接口的UnreadByte方法,将上一个读取过的byte设置为未读取,及r.i–
1
2
3
4
5
6
7
8
func (r *Reader) UnreadByte() error {
if r.i <= 0 {
return errors.New("bytes.Reader.UnreadByte: at beginning of slice")
}
r.prevRune = -1
r.i--
return nil
}
  • 实现io.RuneReader接口的ReadRune方法,从byte中读取一个字符
1
2
3
4
5
6
7
8
9
10
11
12
13
14
func (r *Reader) ReadRune() (ch rune, size int, err error) {
if r.i >= int64(len(r.s)) {
r.prevRune = -1
return 0, 0, io.EOF
}
r.prevRune = int(r.i)
if c := r.s[r.i]; c < utf8.RuneSelf {
r.i++
return rune(c), 1, nil
}
ch, size = utf8.DecodeRune(r.s[r.i:])
r.i += int64(size)
return
}
  • 实现io.RuneScanner接口的UnreadRune方法,将上一个字符,设置为未读,r.i = int64(r.prevRune)即可
1
2
3
4
5
6
7
8
9
10
11
func (r *Reader) UnreadRune() error {
if r.i <= 0 {
return errors.New("bytes.Reader.UnreadRune: at beginning of slice")
}
if r.prevRune < 0 {
return errors.New("bytes.Reader.UnreadRune: previous operation was not ReadRune")
}
r.i = int64(r.prevRune)
r.prevRune = -1
return nil
}
  • 实现io.Seeker的Seek方法
    • whence场景包括开头,结尾,和当前指向的index
    • 返回的是要查找的索引值
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
func (r *Reader) Seek(offset int64, whence int) (int64, error) {
r.prevRune = -1
var abs int64
switch whence {
case io.SeekStart:
abs = offset
case io.SeekCurrent:
abs = r.i + offset
case io.SeekEnd:
abs = int64(len(r.s)) + offset
default:
return 0, errors.New("bytes.Reader.Seek: invalid whence")
}
if abs < 0 {
return 0, errors.New("bytes.Reader.Seek: negative position")
}
r.i = abs
return abs, nil
}
  • 实现io.WriterTo接口的WriteTo方法,将i之后的字节切片写入w中
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
func (r *Reader) WriteTo(w io.Writer) (n int64, err error) {
r.prevRune = -1
if r.i >= int64(len(r.s)) {
return 0, nil
}
b := r.s[r.i:]
m, err := w.Write(b)
if m > len(b) {
panic("bytes.Reader.WriteTo: invalid Write count")
}
r.i += int64(m)
n = int64(m)
if m != len(b) && err == nil {
err = io.ErrShortWrite
}
return
}

初始化Reader方法

  • NewReader创建一个读取b的Reader对象
1
2
// NewReader returns a new Reader reading from b.
func NewReader(b []byte) *Reader { return &Reader{b, 0, -1} }

go语言实战 - 3、4、5

发表于 2020-04-24 | 分类于 go

第三章 打包和工具链

包 package

  • go语言的程序会被组织成若干组文件,每组文件都被称为一个包
  • 不能把多个包放在同一个目录中
  • 不能把同一个包拆分到多个不同目录中
  • 一个目录下的.go文件必须声明同一个包名
  • 包名命名的管理是使用包所在的目录的名字
  • 包的导入使用全路径,允许名字重复
  • 导入后的报名默认使用包的名字,但是是可以修改的
  • main包会被编译成二进制可执行文件
  • main包需要有个main函数作为主函数入口
  • go build 命令用于构建可执行程序

导入 import

  • import语句告诉编译器到磁盘的哪里去寻找导入的包
  • 编译器会使用Go环境变量设置的路径,通过引入的相对路径来查找磁盘上的包
  • 标准库中的包会在安装Go的位置找到
  • Go开发者创建的包会在Gopath的环境变量指定的目录中查找
  • go get命令将获取指定URL的包
  • 包支持命名导入 import nh net/http
  • 导入一个不在代码里使用的包时,会导致编译报错,避免代码变得臃肿
  • _空白标识符用于抛弃,比如导入的包,抛出的异常,返回的函数都可以被抛弃

初始函数 init

  • 每个包可以包含任意多个init函数,这些函数在程序执行开始的时候被调用
  • 所有被编译器发现的init函数都会在main函数之前执行
  • init函数主要用在设置包、初始化变量、或者其他先导设置工作,例如在数据库驱动启动的包中,初始化函数会将自身注册到sql包中,启动之后才可以完成数据库的调用
  • 空白标识符可以保证导入包的初始化函数正常执行,而不会报错

go 工具

  • go build 执行编译
  • go clean 删除编译之后产生的二进制文件
  • go run 先编译,后执行
  • go vet 帮助开发人员检测代码的常见错误
    • 类型匹配错误
    • 方法签名调用错误
    • 错误的结构标签
    • 没有指定字段名的结构字面量
  • go fmt 将代码布局修改成和go源码类似的风格
  • go doc tar可以在命令行打开tar包的相关文档
  • godoc -http=:6060可以在web浏览文档

良好的习惯

  • 包应该在代码库的根目录
  • 包可以非常小
  • 代码需要执行 go fmt
  • 给代码写文档

第四章 数组、切片和映射

数组

  • 数组是一个长度固定的数据类型,用于存储一段具有相同的类型的元素的连续块
  • 内存时连续分配的
  • 数组的类型信息可以提供每次访问一个元素时需要在内存中移动的距离
  • 随机读取速度非常快
  • 声明 var array [5]int,声明之后数组里存储的数据类型和长度都不能改变了
  • 声明之后,数组的值默认为零值
  • 使用数组字面量可以在定义的时候给数组元素赋值 array := [5]int{1,2,3,4,5}
  • 相同类型的数组可以赋值给另一个数组
  • 数组变量的类型时包含长度和每个元素的类型的,只有两部分都相同的数组,才是同类型的数组,才可以互相赋值
  • 指针数组赋值,两个数组会指向同一组的字符串
  • 多维数组 var array [2][2]int
  • 函数之间传递数组的开销比较大,值传递需要复制整个数组,最好只传入指向数组的指针

切片

  • 切片的概念类似于动态数组,可以根据需要自动增长和缩小
  • 切片是一个很小的对象,对底层数组进行了抽象,并提供相关的方法
  • 前片有3个字段的数据,指向底层数组的指针,切片长度,切片容量
  • 切片的创建方法
    • make创建
      • 只指定长度,长度和容量相等 slice := make([]string, 5)
      • 指定了长度和容量 slice := make([]string, 3, 5)
      • 不允许创建容量小于长度的切片
    • 使用切片字面量来创建
      • 长度和容量都是3 slice := []string{"r", "g", "b"}
      • 创建长度和容量是100的切片 slice := []string{99: ""}
    • nil 切片 var slice []int
  • 空切片,在底层数组中包含0个元素,也没有分配内核的存储空间
    • slice := []int{}
    • slice := make([]int, 0)
  • 切片索引的赋值和数组完全一致
  • 切片可以理解为底层数组的一个滑动窗口,类似于视图的概念
  • 底层数组容量是k的切片slice[i:j]其长度为j-i,容量为k-i
  • 切片是可以根据需要来增加切片的容量的,go语言内置的append函数会处理其细节
1
2
3
4
5
6
7
8
9
10
// The append built-in function appends elements to the end of a slice. If
// it has sufficient capacity, the destination is resliced to accommodate the
// new elements. If it does not, a new underlying array will be allocated.
// Append returns the updated slice. It is therefore necessary to store the
// result of append, often in the variable holding the slice itself:
// slice = append(slice, elem1, elem2)
// slice = append(slice, anotherSlice...)
// As a special case, it is legal to append a string to a byte slice, like this:
// slice = append([]byte("hello "), "world"...)
func append(slice []Type, elems ...Type) []Type
  • 切片添加多个数据用...完成
  • 切片迭代for index, val := range slice
  • 切片也支持多维
  • 函数内传递切片采用值传递,其本身尺寸很小

映射

  • 映射用于存储一组无序的键值对
  • 查询时间复杂度O(1)
  • 映射是可以迭代的,但是不保证顺序
  • 映射的散列表包含一组桶,在存储、删除或者查找键值对的时候,所有操作都需要选择一个桶
  • 散列函数生成的散列值的低位来选择桶
  • 创建和初始化
    • dict := make(map[string]int)
    • dict := map[string]string{"Red":"red"}
    • dict := map[int][]string{}

小结

  • 数组是构造切片和映射的基石
  • go语言里切片经常用于处理数据的集合,映射用来处理具有键值对结构的数据
  • 内置函数make可以创建切片和映射,并制定原始的长度和容量
  • 切片有容量限制,可以使用append来扩展容量
  • 映射的增长没有任何限制
  • 内置函数len可以用来获取切片或者映射的长度
  • 内置函数cap只能用于切片
  • 将切片或者映射传递给函数的成本很小,不会复制底层的数据结构

第五章 Go语言的类型系统

  • go语言是一种静态类型的语言,编译器编译时需要知道每个值的类型
  • 值的类型可以使得编译器知道分配多少内存给这个值,以及这个内存存储什么数据

用户定义的类型

  • 当用户声明一个新的类型时,这个声明就给编译器提供一个框架,告知必要的内存大小和表示信息
  • 自定义类型的方法
1
2
3
4
5
type user struct {
name string
age int
}
var bill user
  • 声明变量时,这个变量对应的值总是会被初始化,未指定初始化的值时,各个字段默认取零值
    • 数字类型零值为0
    • 字符串类型零值为””
    • bool类型零值为false

方法

  • 方法能给用户定义的类型添加新的行为
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
type user struct {
name string
email string
}
func (u user) notify () {
fmt.Printf("Sending User Email To %s<%s> \n", u.name, u.email)
}
func (u user) changeEmail(email string) {
u.email = email
}
func main () {
bill := user {
name: "Bill",
email: "bill@email.com",
}
bill.notify()
lisa := &user{"Lisa", "lisa@email.com"}
lisa.notify()
bill.changeEmail("bill@new.com")
bill.notify()
}
  • 实际上一个函数如果有接收者,这个函数就被称为方法

类型的本质

  • 内置类型

    • 对内置类型进行增加或者删除的时候,会创建一个新值,所以函数中内置类型的传递时值传递
  • 引用类型

    • 引用类型包括:切片,映射,通道,接口,函数类型
    • 创建的变量被称为标头值,函数中使用是指针传递,本质上共享底层数据结构
  • 结构类型

    • 当需要更改类型本身的值时,采用指针传递
    • 当不需要更改类型本身的值时,用值传递

接口

  • 多态是指代码可以根据类型的具体实现采用不同行为的能力
  • 如果一个类型实现了这个接口的所有方法,那么就是实现了这个接口
  • 接口是用来定义行为的类型

go语言实战 - 测试

发表于 2020-04-23 | 分类于 go

go语言实战 - 测试

第九章 测试和性能

单元测试

  • 单元测试是用来测试包或者程序的一部分代码或者一组代码的函数
  • 正向路径测试,保证代码不产生错误,正常运行的测试
  • 负向路径测试,保证代码产生错误,并且是预期的错误
  • go中的单元测试
    • 基础测试,只用一组参数和结果进行测试
    • 表组测试,多组参数和结果进行测试
    • mock数据来减少外部依赖

基础单元测试

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
const (
checkMark = "\u2713"
ballotX = "\u2717"
)
func TestDownload(t *testing.T) {
uri := "https://api.weixin.qq.com/cgi-bin/user/info"
statusCode := 200
t.Log("Given the need to test downloading content")
{
t.Logf("\tWhen checking \"%s\" for status code \"%d\"", uri, statusCode)
{
resp, err := http.Get(uri)
if err != nil {
t.Fatal("\t\tShould be able to make the Get call", ballotX, err)
}
t.Log("\t\tShould be able to make the Get call", checkMark)
defer resp.Body.Close()
if resp.StatusCode == statusCode {
t.Logf("\t\tShould reveive a \"%d\" status %v", statusCode, checkMark)
} else {
t.Logf("\t\tShould reveive a \"%d\" status %v %v", statusCode, ballotX, resp.StatusCode)
}
}
}
}
/*
=== RUN TestDownload
--- PASS: TestDownload (0.27s)
01_test.go:17: Given the need to test downloading content
01_test.go:19: When checking "https://api.weixin.qq.com/cgi-bin/user/info" for status code "200"
01_test.go:25: Should be able to make the Get call ✓
01_test.go:30: Should reveive a "200" status ✓
PASS
*/
  • go语言只认为以_test.go结尾的文件是测试文件
  • testing包提供了从测试框架到报告测试的输出和状态的各种测试功能的支持
  • 测试函数必须是公开的函数,并且以Test开头,函数签名必须接收一个指向testing.T类型的指针,并且不返回任何值

表组测试

  • 测试可以接受一组不通的输入并且产生不同的输出的代码,推荐使用表组测试的方法
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
func TestDownload2(t *testing.T) {
var urls = []struct{
url string
statusCode int
} {
{
url: "https://api.weixin.qq.com/cgi-bin/user/info",
statusCode: 200,
},
{
url: "https://api.weixin.qq.com/cgi-bin/user",
statusCode: http.StatusNotFound,
},
}
t.Log("Given the need to test downloading content")
{
for _, u := range urls {
uri := u.url
statusCode := u.statusCode
t.Logf("\tWhen checking \"%s\" for status code \"%d\"", uri, statusCode)
{
resp, err := http.Get(uri)
if err != nil {
t.Fatal("\t\tShould be able to make the Get call", ballotX, err)
}
t.Log("\t\tShould be able to make the Get call", checkMark)
defer resp.Body.Close()
if resp.StatusCode == statusCode {
t.Logf("\t\tShould reveive a \"%d\" status %v", statusCode, checkMark)
} else {
t.Logf("\t\tShould reveive a \"%d\" status %v %v", statusCode, ballotX, resp.StatusCode)
}
}
}
}
}

Mock调用

  • httptest包可以让开发人员模仿基于http的网络调用
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
var feed = `<?xml version="1.0" encoding="UTF-8"?>
<rss>
<channel>
<title>Going Go Programming</title>
<description>Golang : https://github.com/goinggo</description>
<link>http://www.goinggo.net/</link>
<item>
<pubDate>Sun, 15 Mar 2015 15:04:00 +0000</pubDate>
<title>Object Oriented Programming Mechanics</title>
<description>Go is an object oriented language.</description>
<link>http://www.goinggo.net/2015/03/object-oriented</link>
</item>
</channel>
</rss>
`
func mockServer() *httptest.Server {
f := func(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(200)
w.Header().Set("Content-Type", "application/xml")
_, _ = fmt.Fprintln(w, feed)
}
return httptest.NewServer(http.HandlerFunc(f))
}
func TestDownload3(t *testing.T) {
statusCode := 200
server := mockServer()
defer server.Close()
t.Log("Given the need to test downloading content")
{
t.Logf("\tWhen checking \"%s\" for status code \"%d\"", server.URL, statusCode)
{
resp, err := http.Get(server.URL)
if err != nil {
t.Fatal("\t\tShould be able to make the Get call", ballotX, err)
}
t.Log("\t\tShould be able to make the Get call", checkMark)
defer resp.Body.Close()
if resp.StatusCode == statusCode {
t.Logf("\t\tShould reveive a \"%d\" status %v", statusCode, checkMark)
} else {
t.Logf("\t\tShould reveive a \"%d\" status %v %v", statusCode, ballotX, resp.StatusCode)
}
}
}
}

示例

  • 开发人员可以创建自己的示例
  • 函数开头为Example
  • 示例代码的函数名字必须基于已经存在的公开的函数或者方法
  • 最好将示例的输出列在下方

基准测试

  • 基准测试是测试代码性能的方法
  • 基准测试必须以Benchmark开头,接受一个执行testing.B类型的指针作为唯一参数
  • 基准测试默认框架会在持续1s的时间内,反复调用需要测试的函数,测试框架每次调用测试函数时,都会增加b.N的值
  • 如果只想运行基准测试,而跳过单元测试,则用命令go test -v -run="none" -bench="BenchmarkSprintf"即可
  • -benchtime=3s可以变更基准测试的运行时间,默认为1s
  • b.ResetTimer用来重置计时器
  • -benchmem展示每次操作分配内存的次数
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
package main
import (
"fmt"
"strconv"
"testing"
)
// 找出最快的奖整数转换成字符串的方法
func BenchmarkSprintf(b *testing.B) {
number := 10
b.ResetTimer()
for i := 0; i < b.N; i++ {
fmt.Sprintf("%d", number)
}
}
func BenchmarkFormat(b *testing.B) {
number := int64(10)
b.ResetTimer()
for i := 0; i < b.N; i++ {
strconv.FormatInt(number, 10)
}
}
func BenchmarkItoa(b *testing.B) {
number := 10
b.ResetTimer()
for i := 0; i < b.N; i++ {
strconv.Itoa(number)
}
}
/*
goos: darwin
goarch: amd64
pkg: api/books/go-in-action/9-testing
BenchmarkSprintf-12 20000000 70.6 ns/op
BenchmarkFormat-12 1000000000 2.56 ns/op
BenchmarkItoa-12 1000000000 2.42 ns/op
PASS
*/

小结

  • 测试功能被内置到go语言中,go语言提供了必要的测试工具
  • go test工具用来运行测试
  • 测试文件总是以_test作为文件名的结尾
  • 表组测试是利用一个测试函数测试多组值的好办法
  • 表中的实例代码,既能用于测试,也能用于文档
  • 基准测试提供了探查代码性能的机制

go语言实战 - 标准库

发表于 2020-04-21 | 分类于 go

第八章 标准库

  • 标准库是一组核心包,用于扩展和增强语言的能力
    • 每次语言更新都会带有标准库
    • 标准库会严格遵守哦向后兼容的承诺
    • 标准库是go语言开发,构建,发布过程的一部分
    • 标准库由go的构建者们维护和评审
    • go语言发布新版本时,标准库都会被测试,并评估性能
  • 开发中尽量使用标准库

文档与源代码

  • 作为go发布包的一部分,标准库的源代码是经过预编译的,称为归档文件
  • 这些文件是go静态库文件,由go的构建工具创建,并在编译和链接最终程序时被使用

日志

log包简介

  • log包日志项包含前缀、日期时间戳、源文件、行数、日志消息等组成
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
package main
import "log"
func init() {
log.SetPrefix("TRACE: ")
log.SetFlags(log.Ldate | log.Lmicroseconds | log.Llongfile)
}
func main() {
log.Println("message")
log.Fatalln("fatal message")
log.Panicln("panic message")
}
/* 输出结果
TRACE: 2020/04/21 14:34:16.281971 /Users/dxchang/go/src/api/books/go-in-action/8-pkg/log.go:11: message
TRACE: 2020/04/21 14:34:16.282102 /Users/dxchang/go/src/api/books/go-in-action/8-pkg/log.go:13: fatal message
*/
  • init函数作为初始化的一部分,会在main函数执行前执行
  • SetPrefix()方法进行设置前缀,一般全部大写
  • SetFlags设定标志来控制可以写到每个日志项的输出信息,如下
  • 这样可以用一个字节来存储日志的标志位,log.Ldate | log.Ltime | log.Lmicroseconds | log.Llongfile和传入00001111是等价的
  • 日志中以ln结尾的函数会换行,以f结尾的函数可以自定义格式化输出
  • log包是多线程安全的,多个goroutine同时来调用一个日志记录器的函数,不会存在写冲突
1
2
3
4
5
6
7
8
9
const (
Ldate = 1 << iota // 本地日期: 2009/01/23
Ltime // 本地时间: 01:23:23
Lmicroseconds // 时间毫秒数: 01:23:23.123123.
Llongfile // 日志打印的文件全路径: /a/b/c/d.go:23
Lshortfile // 日志打印的文件短路径: d.go:23
LUTC // 使用UTC时区时间
LstdFlags = Ldate | Ltime // 默认日志,打印日期和时间,到秒
)
  • Fatal系列函数来先写消息,然后调用os.Exit方法退出
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// Fatal is equivalent to Print() followed by a call to os.Exit(1).
func Fatal(v ...interface{}) {
std.Output(2, fmt.Sprint(v...))
os.Exit(1)
}
// Fatalf is equivalent to Printf() followed by a call to os.Exit(1).
func Fatalf(format string, v ...interface{}) {
std.Output(2, fmt.Sprintf(format, v...))
os.Exit(1)
}
// Fatalln is equivalent to Println() followed by a call to os.Exit(1).
func Fatalln(v ...interface{}) {
std.Output(2, fmt.Sprintln(v...))
os.Exit(1)
}
  • Panic函数写日志消息,触发panic,打印堆栈然后退出
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// Panic is equivalent to Print() followed by a call to panic().
func Panic(v ...interface{}) {
s := fmt.Sprint(v...)
std.Output(2, s)
panic(s)
}
// Panicf is equivalent to Printf() followed by a call to panic().
func Panicf(format string, v ...interface{}) {
s := fmt.Sprintf(format, v...)
std.Output(2, s)
panic(s)
}
// Panicln is equivalent to Println() followed by a call to panic().
func Panicln(v ...interface{}) {
s := fmt.Sprintln(v...)
std.Output(2, s)
panic(s)
}
  • Print函数是写日志消息的标准方法
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// Print calls Output to print to the standard logger.
// Arguments are handled in the manner of fmt.Print.
func Print(v ...interface{}) {
std.Output(2, fmt.Sprint(v...))
}
// Printf calls Output to print to the standard logger.
// Arguments are handled in the manner of fmt.Printf.
func Printf(format string, v ...interface{}) {
std.Output(2, fmt.Sprintf(format, v...))
}
// Println calls Output to print to the standard logger.
// Arguments are handled in the manner of fmt.Println.
func Println(v ...interface{}) {
std.Output(2, fmt.Sprintln(v...))
}

定制的日志记录器

  • 创建一个定制的日志记录器,需要创建一个Logger类型值
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
var (
Trace *log.Logger // 记录所有日志
Info *log.Logger // 重要的信息
Warning *log.Logger // 警告
Error *log.Logger // 错误
)
func init() {
file, err := os.OpenFile("errors.txt", os.O_CREATE | os.O_WRONLY | os.O_APPEND, 0666)
if err != nil {
log.Fatalln("Failed to open error log file:", err)
}
Trace = log.New(ioutil.Discard, "TRACE: ", log.Ldate | log.Ltime | log.Lshortfile)
Info = log.New(os.Stdout, "INFO: ", log.Ldate | log.Ltime | log.Lshortfile)
Warning = log.New(os.Stdout, "WARNING: ", log.Ldate | log.Ltime | log.Lshortfile)
Error = log.New(io.MultiWriter(file, os.Stderr), "TRACE: ", log.Ldate | log.Ltime | log.Lshortfile)
}
func main() {
Trace.Println("I have something standard to say")
Info.Println("Special Information")
Warning.Println("There is something you need to know")
Error.Println("Something has failed")
}
  • log包的New()函数完成Logger类型的初始化,设定输出位置,日志前缀,及日志输出内容
1
2
3
func New(out io.Writer, prefix string, flag int) *Logger {
return &Logger{out: out, prefix: prefix, flag: flag}
}
  • ioutil.Discard实现了io.Writer接口,但是并不会发生实际的写入,使用Discard可以禁用这个类型的日志
  • os.Stdin、os.Stdout、os.Stderr分别指向标准输入,标准输出,标准错误
  • io.MultiWriter()方法可以向多个实现了io.Writer接口的位置写入日志

log包中的其余函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
func Flags() int {
return std.Flags()
}
func SetFlags(flag int) {
std.SetFlags(flag)
}
func Prefix() string {
return std.Prefix()
}
func SetPrefix(prefix string) {
std.SetPrefix(prefix)
}
// SetOutput sets the output destination for the standard logger.
func SetOutput(w io.Writer) {
std.mu.Lock()
defer std.mu.Unlock()
std.out = w
}
func Output(calldepth int, s string) error {
return std.Output(calldepth+1, s) // +1 for this frame.
}
  • logger结构体
1
2
3
4
5
6
7
type Logger struct {
mu sync.Mutex // 互斥锁,支持原子写入
prefix string // 日志前缀
flag int // 日志输出内容标志位
out io.Writer // 日志输出位置
buf []byte // 要追加的日志内容
}
  • logger核心方法 Output
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
// s为要写入的日志,calldepth目前均为2
func (l *Logger) Output(calldepth int, s string) error {
now := time.Now() // get this early.
var file string
var line int
l.mu.Lock()
defer l.mu.Unlock()
if l.flag&(Lshortfile|Llongfile) != 0 {
// Release lock while getting caller info - it's expensive.
l.mu.Unlock()
var ok bool
_, file, line, ok = runtime.Caller(calldepth)
if !ok {
file = "???"
line = 0
}
l.mu.Lock()
}
l.buf = l.buf[:0]
l.formatHeader(&l.buf, now, file, line)
l.buf = append(l.buf, s...)
if len(s) == 0 || s[len(s)-1] != '\n' {
l.buf = append(l.buf, '\n')
}
_, err := l.out.Write(l.buf)
return err
}

编码/解码

  • xml/json包可以完成json和xml的编码解码

解码JSON

  • 使用json包的NewDecode函数和Decode方法来完成json解码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
type gResponse struct {
ErrCode int `json:"errcode"`
ErrMsg string `json:"errmsg"`
}
func main() {
uri := "https://api.weixin.qq.com/cgi-bin/user/info"
resp, err := http.Get(uri)
if err != nil {
log.Println("ERROR: ", err)
return
}
defer resp.Body.Close()
var gr gResponse
err = json.NewDecoder(resp.Body).Decode(&gr)
if err != nil {
log.Println("ERROR: ", err)
return
}
fmt.Println(gr)
}
// {41001 access_token missing hints: [xHkBIOLoRa-GmALMa!]}
  • json中NewDecoder方法返回Decoder对象
  • Decoder结构体从输入流中读取数据并解码成json
  • Decode方法完成JSON的解析映射
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
func NewDecoder(r io.Reader) *Decoder {
return &Decoder{r: r}
}
// A Decoder reads and decodes JSON values from an input stream.
type Decoder struct {
r io.Reader
buf []byte
d decodeState
scanp int // start of unread data in buf
scanned int64 // amount of data already scanned
scan scanner
err error
tokenState int
tokenStack []int
}
func (dec *Decoder) Decode(v interface{}) error {
if dec.err != nil {
return dec.err
}
if err := dec.tokenPrepareForDecode(); err != nil {
return err
}
if !dec.tokenValueAllowed() {
return &SyntaxError{msg: "not at beginning of value", Offset: dec.offset()}
}
// Read whole value into buffer.
n, err := dec.readValue()
if err != nil {
return err
}
dec.d.init(dec.buf[dec.scanp : dec.scanp+n])
dec.scanp += n
err = dec.d.unmarshal(v)
dec.tokenValueEnd()
return err
}
  • json文档以string的形式存在,此时使用json.Unmarshal方法完成反序列化处理
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
type gResponse struct {
ErrCode int `json:"errcode"`
ErrMsg string `json:"errmsg"`
}
func main() {
uri := "https://api.weixin.qq.com/cgi-bin/user/info"
resp, err := http.Get(uri)
if err != nil {
log.Println("ERROR: ", err)
return
}
defer resp.Body.Close()
bytes, err := ioutil.ReadAll(resp.Body)
if err != nil {
log.Println("ERROR: ", err)
return
}
var gr gResponse
err = json.Unmarshal(bytes, &gr)
if err != nil {
log.Println("ERROR: ", err)
return
}
fmt.Println(gr)
}

编码JSON

  • MarshalIndent方法可以完成map和结构体到json的映射

输入和输出

Writer和Reader接口

  • io包的核心就是Writer和Reader接口
    • Writer接口
      • Writer接口接收字节切片,返回两个值,写入的字节数和错误输出
      • Writer接口从p里向底层的数据流写入len(p)字节的数据
      • Writer无论如何也不能改p中的数据
    • Reader接口
      • Reader接口接收字节切片,返回两个值,读取的字节数和错误输出
      • Read最多读入len(p)个字节,保存到p
      • 可以使用所有的p的空间来存储临时数据
      • 当字节长度不足len(p),Read会立刻返回可用的数据,而不是等待更多的数据
      • 当成功读取了部分字节后,发生错误或者读取完成,Read方法会返回读入的字节数
      • 即使有错误发生,当读取的字节数>0时,需要先处理读取的数据,再处理错误
      • 读取的实现,不鼓励返回0个字节并且返回nil值的错误
        1
        2
        3
        4
        5
        6
        7
        type Writer interface {
        Write(p []byte) (n int, err error)
        }
        type Reader interface {
        Read(p []byte) (n int, err error)
        }

例子

1
2
3
4
5
6
7
8
func main() {
var b bytes.Buffer
b.Write([]byte("Hello "))
fmt.Fprintf(&b, "World")
b.WriteTo(os.Stdout)
}
  • Buffe的Write方法
1
2
3
4
5
6
7
8
9
10
11
// Write appends the contents of p to the buffer, growing the buffer as
// needed. The return value n is the length of p; err is always nil. If the
// buffer becomes too large, Write will panic with ErrTooLarge.
func (b *Buffer) Write(p []byte) (n int, err error) {
b.lastRead = opInvalid
m, ok := b.tryGrowByReslice(len(p))
if !ok {
m = b.grow(len(p))
}
return copy(b.buf[m:], p), nil
}

curl

  • 一个简单的curl,第一个参数指定curl的网站,,第二个参数指定将curl获取的response的body写入的文件名
  • defer 打开关闭的资源
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
func main() {
r, err := http.Get(os.Args[1])
if err != nil {
log.Fatalln(err)
}
file, err := os.Create(os.Args[2])
if err != nil {
log.Fatalln(err)
}
defer file.Close()
dest := io.MultiWriter(os.Stdout, file)
io.Copy(dest, r.Body)
if err := r.Body.Close(); err != nil {
log.Fatalln(err)
}
}

小结

  • 标准库由特殊的保证,并且被社区广泛应用
  • 使用标准库会让你的代码更易于管理
  • 接口允许你的代码组合已有的功能
  • 阅读标准库的代码是熟悉go语言习惯的好方法

go源码解读-ring

发表于 2020-04-17 | 分类于 go , 源码解读

ring

  • ring为环结构,没有开始和结束
  • 本质是双向链表
  • 空ring的next和prev都指向自己

ring结构体

  • Ring结构体包含三个参数
    • next 指向当前节点的下一个节点
    • prev 指向当前节点的上一个节点
    • Value 当前节点的值
  • New方法为创建一个包含n个元素的ring的方法
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
type Ring struct {
next, prev *Ring
Value interface{} // for use by client; untouched by this library
}
// New creates a ring of n elements.
func New(n int) *Ring {
if n <= 0 {
return nil
}
r := new(Ring)
p := r
for i := 1; i < n; i++ {
p.next = &Ring{prev: p}
p = p.next
}
p.next = r
r.prev = p
return r
}

Ring结构体的方法

  • init为初始一个0个节点的Ring结构体的方法
  • Next返回当前节点的下一个节点
  • Prev返回当前节点的上一个节点
  • Move将当前节点移动n个位置
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
func (r *Ring) init() *Ring {
r.next = r
r.prev = r
return r
}
// Next returns the next ring element. r must not be empty.
func (r *Ring) Next() *Ring {
if r.next == nil {
return r.init()
}
return r.next
}
// Prev returns the previous ring element. r must not be empty.
func (r *Ring) Prev() *Ring {
if r.next == nil {
return r.init()
}
return r.prev
}
// Move moves n % r.Len() elements backward (n < 0) or forward (n >= 0)
// in the ring and returns that ring element. r must not be empty.
//
func (r *Ring) Move(n int) *Ring {
if r.next == nil {
return r.init()
}
switch {
case n < 0:
for ; n < 0; n++ {
r = r.prev
}
case n > 0:
for ; n > 0; n-- {
r = r.next
}
}
return r
}

链接操作

  • Link 将s和r链接到一起,返回r.next
    • s和r在一个圆环里,操作会将r和s之间的元素移除到圆环
    • s和r不在一个圆环里,操作会将r和s合并成一个圆环
  • Unlink移除n%r.len个元素,本质上是对Link方法的封装
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
func (r *Ring) Link(s *Ring) *Ring {
n := r.Next()
if s != nil {
p := s.Prev()
// Note: Cannot use multiple assignment because
// evaluation order of LHS is not specified.
r.next = s
s.prev = r
n.prev = p
p.next = n
}
return n
}
// Unlink removes n % r.Len() elements from the ring r, starting
// at r.Next(). If n % r.Len() == 0, r remains unchanged.
// The result is the removed subring. r must not be empty.
func (r *Ring) Unlink(n int) *Ring {
if n <= 0 {
return nil
}
return r.Link(r.Move(n + 1))
}

Len和Do方法

  • Len方法返回圆环的长度
  • Do方法的参数为一个函数f,对于圆环上的每个元素的值,执行该函数
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
func (r *Ring) Len() int {
n := 0
if r != nil {
n = 1
for p := r.Next(); p != r; p = p.next {
n++
}
}
return n
}
// Do calls function f on each element of the ring, in forward order.
// The behavior of Do is undefined if f changes *r.
func (r *Ring) Do(f func(interface{})) {
if r != nil {
f(r.Value)
for p := r.Next(); p != r; p = p.next {
f(p.Value)
}
}
}

go源码解读-list

发表于 2020-04-17 | 分类于 go , 源码解读

list

  • go语言中的列表采用双向链表来实现
  • 实现过程中主要依赖于两个结构体,Element和List对象
  • 显然list是线程不安全的包

Element详解

  • Element中包含四个字段,类似于Java中的成员变量
    • next为当前元素的下一个元素
    • pre为当前元素的上一个元素
    • &l.root为链表最后一个元素的下一个元素,同时也是链表第一个元素的上个元素,形如一个环
    • list标识当前元素归属于哪个链表
    • Value采用interface{}类型,可以保存任意的数据结构,也就是说go中的链表可以保存一个类似[‘abc’,12,12.03]这样的数据
1
2
3
4
5
6
7
8
// Element is an element of a linked list.
type Element struct {
next, prev *Element
list *List
Value interface{}
}
  • Element结构体实现了两个方法
    • Next()方法返回当前节点的下一个节点数据
    • Prev()方法返回当前节点的上一个节点数据
    • 两个方法都屏蔽掉了list.root的情况,也就是如果找到的节点是根节点,则不做返回
1
2
3
4
5
6
7
8
9
10
11
12
13
func (e *Element) Next() *Element {
if p := e.next; e.list != nil && p != &e.list.root {
return p
}
return nil
}
func (e *Element) Prev() *Element {
if p := e.prev; e.list != nil && p != &e.list.root {
return p
}
return nil
}

List

  • 链表包含两个元素
    • root 根节点,只用在&root, root.prev, root.next这三种情况
    • len 链表中的节点数量,不包含根节点
1
2
3
4
5
6
// List represents a doubly linked list.
// The zero value for List is an empty list ready to use.
type List struct {
root Element // sentinel list element, only &root, root.prev, and root.next are used
len int // current list length excluding (this) sentinel element
}

初始化列表

  • list提供一个可外部调用的初始化方法New,实际上是调用了List的Init()方法
  • Init方法返回一个空列表,也就是root的前后节点都指向自己,并且列表长度为0
1
2
3
4
5
6
7
8
9
10
// New returns an initialized list.
func New() *List { return new(List).Init() }
// Init initializes or clears list l.
func (l *List) Init() *List {
l.root.next = &l.root
l.root.prev = &l.root
l.len = 0
return l
}

Len, Front, Back方法

  • Len返回list中的元素数量,获取复杂度O(1)
  • Front方法返回列表第一个元素,也就是l.root.next
  • Back方法返回列表最后一个元素,也就是l.root.prev
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
func (l *List) Len() int { return l.len }
func (l *List) Front() *Element {
if l.len == 0 {
return nil
}
return l.root.next
}
func (l *List) Back() *Element {
if l.len == 0 {
return nil
}
return l.root.prev
}

Remove方法

  • Remove方法移除列表中指定元素,这个地方会对e.list做校验,如果不符合则不会进入remove方法
  • remove方法完成移除元素e的操作,将e.next和e.prev设置为nil,防止内存泄露
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
func (l *List) Remove(e *Element) interface{} {
if e.list == l {
// if e.list == l, l must have been initialized when e was inserted
// in l or l == nil (e is a zero Element) and l.remove will crash
l.remove(e)
}
return e.Value
}
// remove removes e from its list, decrements l.len, and returns e.
func (l *List) remove(e *Element) *Element {
e.prev.next = e.next
e.next.prev = e.prev
e.next = nil // avoid memory leaks
e.prev = nil // avoid memory leaks
e.list = nil
l.len--
return e
}

插入操作

  • lazyInit方法,懒加载,延迟初始化一个空链表
  • insertValue方法是对insert的一层封装
  • insert方法将节点e插入到节点at后面
  • PushFront 将节点插入到root节点之后
  • PushBach 将节点插入到root.prev之后
  • InsertBefore 将节点插入到mark节点之前
  • InsertAfter 将节点插入到mark节点之后
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
func (l *List) PushFront(v interface{}) *Element {
l.lazyInit()
return l.insertValue(v, &l.root)
}
func (l *List) PushBack(v interface{}) *Element {
l.lazyInit()
return l.insertValue(v, l.root.prev)
}
func (l *List) InsertBefore(v interface{}, mark *Element) *Element {
if mark.list != l {
return nil
}
return l.insertValue(v, mark.prev)
}
func (l *List) InsertAfter(v interface{}, mark *Element) *Element {
if mark.list != l {
return nil
}
// see comment in List.Remove about initialization of l
return l.insertValue(v, mark)
}
func (l *List) lazyInit() {
if l.root.next == nil {
l.Init()
}
}
func (l *List) Init() *List {
l.root.next = &l.root
l.root.prev = &l.root
l.len = 0
return l
}
func (l *List) insertValue(v interface{}, at *Element) *Element {
return l.insert(&Element{Value: v}, at)
}
func (l *List) insert(e, at *Element) *Element {
n := at.next
at.next = e
e.prev = at
e.next = n
n.prev = e
e.list = l
l.len++
return e
}

移动节点操作

  • move为节点移动的核心方法,将节点e移动到节点at后面
  • MoveToFront将节点e移动到root节点之后
  • MoveToBack将节点e移动到root.prev节点之后
  • MoveBefore将节点移动到mark节点之前,这个地方如果e.next == mark的时候是不需要移动的
  • MoveAfter将节点移动到mark节点之后,这个地方如果mark.next == e 也是不需要移动的
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
func (l *List) MoveToFront(e *Element) {
if e.list != l || l.root.next == e {
return
}
// see comment in List.Remove about initialization of l
l.move(e, &l.root)
}
func (l *List) MoveToBack(e *Element) {
if e.list != l || l.root.prev == e {
return
}
// see comment in List.Remove about initialization of l
l.move(e, l.root.prev)
}
func (l *List) MoveBefore(e, mark *Element) {
if e.list != l || e == mark || mark.list != l {
return
}
l.move(e, mark.prev)
}
func (l *List) MoveAfter(e, mark *Element) {
if e.list != l || e == mark || mark.list != l {
return
}
l.move(e, mark)
}
// move moves e to next to at and returns e.
func (l *List) move(e, at *Element) *Element {
if e == at {
return e
}
e.prev.next = e.next
e.next.prev = e.prev
n := at.next
at.next = e
e.prev = at
e.next = n
n.prev = e
return e
}

列表方法

  • PushBackList方法将other列表插入到l列表root.prev之后
  • PushFrontList方法将other列表插入到l列表root节点之后
1
2
3
4
5
6
7
8
9
10
11
12
13
func (l *List) PushBackList(other *List) {
l.lazyInit()
for i, e := other.Len(), other.Front(); i > 0; i, e = i-1, e.Next() {
l.insertValue(e.Value, l.root.prev)
}
}
func (l *List) PushFrontList(other *List) {
l.lazyInit()
for i, e := other.Len(), other.Back(); i > 0; i, e = i-1, e.Prev() {
l.insertValue(e.Value, &l.root)
}
}

go源码解读-heap

发表于 2020-04-17 | 分类于 go , 源码解读

heap

  • go中的堆采用小根堆实现
  • 采用接口形式,也就是需要实现Push,Pop,Len,Less,Swap方法
1
2
3
4
5
type Interface interface {
sort.Interface
Push(x interface{}) // add x as element Len()
Pop() interface{} // remove and return element Len() - 1.
}

堆的构建

  • 调用init来完成对于堆的构建
  • 比较i与2*i+1, 2*i+2的大小进行建堆
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
func Init(h Interface) {
// heapify
n := h.Len()
for i := n/2 - 1; i >= 0; i-- {
down(h, i, n)
}
}
func down(h Interface, i0, n int) bool {
i := i0
for {
j1 := 2*i + 1
if j1 >= n || j1 < 0 { // j1 < 0 after int overflow
break
}
j := j1 // left child
if j2 := j1 + 1; j2 < n && h.Less(j2, j1) {
j = j2 // = 2*i + 2 // right child
}
if !h.Less(j, i) {
break
}
h.Swap(i, j)
i = j
}
return i > i0
}

Push, Pop方法

  • Push方法将数据防止在末尾,逐个节点向上调整
  • Pop方法将待出堆的节点和末尾节点交换,向下调整根节点
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
func Push(h Interface, x interface{}) {
h.Push(x)
up(h, h.Len()-1)
}
// Pop is equivalent to Remove(h, 0).
func Pop(h Interface) interface{} {
n := h.Len() - 1
h.Swap(0, n)
down(h, 0, n)
return h.Pop()
}
func up(h Interface, j int) {
for {
i := (j - 1) / 2 // parent
if i == j || !h.Less(j, i) {
break
}
h.Swap(i, j)
j = i
}
}

Fix, Remove方法

  • Remove方法移除树第i个节点,并调整堆结构
  • Fix方法在数据值便跟时调整堆结构
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
func Remove(h Interface, i int) interface{} {
n := h.Len() - 1
if n != i {
h.Swap(i, n)
if !down(h, i, n) {
up(h, i)
}
}
return h.Pop()
}
func Fix(h Interface, i int) {
if !down(h, i, h.Len()) {
up(h, i)
}
}

示例-优先队列

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
// An Item is something we manage in a priority queue.
type Item struct {
value string // The value of the item; arbitrary.
priority int // The priority of the item in the queue.
// The index is needed by update and is maintained by the heap.Interface methods.
index int // The index of the item in the heap.
}
// A PriorityQueue implements heap.Interface and holds Items.
type PriorityQueue []*Item
func (pq PriorityQueue) Len() int { return len(pq) }
func (pq PriorityQueue) Less(i, j int) bool {
// We want Pop to give us the highest, not lowest, priority so we use greater than here.
return pq[i].priority > pq[j].priority
}
func (pq PriorityQueue) Swap(i, j int) {
pq[i], pq[j] = pq[j], pq[i]
pq[i].index = i
pq[j].index = j
}
func (pq *PriorityQueue) Push(x interface{}) {
n := len(*pq)
item := x.(*Item)
item.index = n
*pq = append(*pq, item)
}
func (pq *PriorityQueue) Pop() interface{} {
old := *pq
n := len(old)
item := old[n-1]
item.index = -1 // for safety
*pq = old[0 : n-1]
return item
}
// update modifies the priority and value of an Item in the queue.
func (pq *PriorityQueue) update(item *Item, value string, priority int) {
item.value = value
item.priority = priority
heap.Fix(pq, item.index)
}
// This example creates a PriorityQueue with some items, adds and manipulates an item,
// and then removes the items in priority order.
func main() {
// Some items and their priorities.
items := map[string]int{
"banana": 3, "apple": 2, "pear": 4,
}
// Create a priority queue, put the items in it, and
// establish the priority queue (heap) invariants.
pq := make(PriorityQueue, len(items))
i := 0
for value, priority := range items {
pq[i] = &Item{
value: value,
priority: priority,
index: i,
}
i++
}
heap.Init(&pq)
// Insert a new item and then modify its priority.
item := &Item{
value: "orange",
priority: 1,
}
heap.Push(&pq, item)
pq.update(item, item.value, 5)
// Take the items out; they arrive in decreasing priority order.
for pq.Len() > 0 {
item := heap.Pop(&pq).(*Item)
fmt.Printf("%.2d:%s ", item.priority, item.value)
}
// Output:
// 05:orange 04:pear 03:banana 02:apple
}
1234…11
cdx

cdx

Be a better man!

110 日志
36 分类
31 标签
GitHub E-Mail
© 2020 cdx
由 Hexo 强力驱动
|
主题 — NexT.Mist v5.1.2