上一篇:
Go源码学习:bytes包 - 1.2 - bytes.go -(2)
21、asciiSpace:ASCII空白字符映射
// asciiSpace 是一个长度为 256 的数组,用于表示 ASCII 码中的空白字符。
// '\t': 水平制表符,'\n': 换行符,'\v': 垂直制表符,'\f': 换页符,'\r': 回车符,' ': 空格。
var asciiSpace = [256]uint8{'\t': 1, '\n': 1, '\v': 1, '\f': 1, '\r': 1, ' ': 1}
解释:
asciiSpace
是一个长度为 256 的数组,用于表示 ASCII 码中的空白字符。- 每个元素的索引对应一个 ASCII 码,而元素的值表示该字符是否为空白字符,1 表示是,0 表示不是。
作用:
- 该数组提供了一种快速的方式来检查一个字符是否是ASCII空白字符。
22、Fields:切分 UTF-8 编码的字符串为字段
// Fields 将 s 视为 UTF-8 编码的码点序列。
// 它根据 unicode.IsSpace 定义的一个或多个连续的空白字符来切分切片 s,
// 返回 s 的子切片组成的切片,如果 s 只包含空白字符,则返回一个空切片。
func Fields(s []byte) [][]byte {
// 首先计算字段数。
// 如果 s 是 ASCII,则这是一个准确的计数,否则这是一个近似值。
n := 0
wasSpace := 1
// setBits 用于跟踪 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 {
// 输入切片中有一些不是 ASCII 的字符。
return FieldsFunc(s, unicode.IsSpace)
}
// ASCII 快速路径
a := make([][]byte, n)
na := 0
fieldStart := 0
i := 0
// 在输入的开头跳过空格。
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++
// 在字段之间跳过空格。
for i < len(s) && asciiSpace[s[i]] != 0 {
i++
}
fieldStart = i
}
if fieldStart < len(s) { // 最后一个字段可能在文件结束。
a[na] = s[fieldStart:len(s):len(s)]
}
return a
}
解释:
Fields
方法将切片s
视为 UTF-8 编码的码点序列,根据空白字符的定义切分切片,并返回由 s 的子切片组成的切片,如果 s 只包含空白字符,则返回一个空切片。- 该方法采用一种快速路径,如果输入 s 是 ASCII,直接通过预先计算空白字符个数来分配内存,避免了调用
FieldsFunc
方法。 - 通过遍历切片中的字符,计算字段数,然后根据计算结果进行切分。
作用:
Fields
方法用于将字符串切分为字段,其中字段之间由一个或多个连续的空白字符分隔。- 该方法对于处理 ASCII 字符串具有高效的路径,提高了性能。
23、FieldsFunc:根据自定义函数切分字符串为字段
// FieldsFunc 将 s 视为 UTF-8 编码的码点序列。
// 它根据满足 f(c) 的码点 c 运行的连续码点来切分切片 s,
// 返回 s 的子切片组成的切片。如果 s 中的所有码点都满足 f(c),或者 len(s) == 0,则返回一个空切片。
//
// FieldsFunc 不保证调用 f(c) 的顺序,并假设对于给定的 c,f 总是返回相同的值。
func FieldsFunc(s []byte, f func(rune) bool) [][]byte {
// span 用于记录 s 的形式为 s[start:end] 的子切片。
// 开始索引包含在内,结束索引不包含在内。
type span struct {
start int
end int
}
spans := make([]span, 0, 32)
// 查找字段的起始和结束索引。
// 将这个过程分开进行(而不是立即对字符串 s 进行切片并收集结果子字符串)会更高效,可能是由于缓存效应。
start := -1 // 如果 >= 0,则 start 为有效的 span 开始索引
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 start >= 0 {
spans = append(spans, span{start, i})
start = -1
}
} else {
if start < 0 {
start = i
}
}
i += size
}
// 最后一个字段可能在文件结束。
if start >= 0 {
spans = append(spans, span{start, len(s)})
}
// 从记录的字段索引创建子切片。
a := make([][]byte, len(spans))
for i, span := range spans {
a[i] = s[span.start:span.end:span.end]
}
return a
}
解释:
FieldsFunc
方法将切片s
视为 UTF-8 编码的码点序列。根据满足自定义函数f(c)
的码点c
运行的连续码点来切分切片s
,并返回由s
的子切片组成的切片。如果s
中的所有码点都满足f(c)
,或者len(s) == 0
,则返回一个空切片。- 该方法通过创建
span
结构体记录字段的起始和结束索引,然后根据记录的字段索引创建子切片。
作用:
FieldsFunc
方法用于根据自定义函数切分字符串为字段,通过自定义函数f(c)
来确定切分的规则,适用于更灵活的字符串处理需求。
24、Join:连接切片元素为新的字节切片
// Join 将切片 s 的元素连接起来,创建一个新的字节切片。分隔符 sep 被放置在结果切片中的元素之间。
func Join(s [][]byte, sep []byte) []byte {
if len(s) == 0 {
return []byte{}
}
if len(s) == 1 {
// 只返回副本。
return append([]byte(nil), s[0]...)
}
var n int
if len(sep) > 0 {
if len(sep) >= maxInt/(len(s)-1) {
panic("bytes: Join output length overflow")
}
n += len(sep) * (len(s) - 1)
}
for _, v := range s {
if len(v) > maxInt-n {
panic("bytes: Join output length overflow")
}
n += len(v)
}
b := bytealg.MakeNoZero(n)
bp := copy(b, s[0])
for _, v := range s[1:] {
bp += copy(b[bp:], sep)
bp += copy(b[bp:], v)
}
return b
}
解释:
Join
方法连接切片s
的元素,使用分隔符sep
在结果切片中的元素之间放置。- 如果切片
s
为空,直接返回一个空字节切片。 - 如果切片
s
只有一个元素,直接返回该元素的副本。 - 计算连接后的结果切片的总长度
n
,包括分隔符的长度。 - 确保结果切片的长度不会溢出,如果溢出则抛出 panic。
- 创建一个新的字节切片
b
,长度为n
。 - 使用循环将切片
s
的元素拷贝到结果切片中,同时在元素之间插入分隔符。
作用:
Join
方法用于连接字节切片的元素,形成一个新的字节切片。- 可以指定分隔符,在连接元素之间插入分隔符。
- 适用于将多个字节切片合并为一个字符串的场景。
25、HasPrefix:判断字节切片是否以指定前缀开头
// HasPrefix 报告字节切片 s 是否以前缀开头。
func HasPrefix(s, prefix []byte) bool {
return len(s) >= len(prefix) && Equal(s[0:len(prefix)], prefix)
}
解释:
HasPrefix
函数用于判断字节切片s
是否以指定前缀prefix
开头。- 返回值为布尔型,表示是否满足条件。
- 条件判断:字节切片
s
的长度必须大于等于前缀prefix
的长度,并且通过Equal
函数比较切片的前缀与指定前缀是否相等。
作用:
- 用于检查字节切片是否以指定前缀开头。
26、HasSuffix:判断字节切片是否以指定后缀结尾
// HasSuffix 报告字节切片 s 是否以后缀结尾。
func HasSuffix(s, suffix []byte) bool {
return len(s) >= len(suffix) && Equal(s[len(s)-len(suffix):], suffix)
}
解释:
HasSuffix
函数用于判断字节切片s
是否以指定后缀suffix
结尾。- 返回值为布尔型,表示是否满足条件。
- 条件判断:字节切片
s
的长度必须大于等于后缀suffix
的长度,并且通过Equal
函数比较切片的后缀与指定后缀是否相等。
作用:
- 用于检查字节切片是否以指定后缀结尾。
27、Map:对字节切片进行字符映射
// Map 返回字节切片 s 的副本,其中的每个字符根据映射函数进行修改。
// 如果映射函数返回负值,该字符将从字节切片中删除且不替换。
// 字节切片 s 和输出都被解释为UTF-8编码的码点。
func Map(mapping func(r rune) rune, s []byte) []byte {
// 在最坏的情况下,切片在映射时可能会增长,使情况变得复杂。
// 但这种情况非常罕见,我们假设这是可以接受的。
// 它也可能会缩小,但那自然而然地发生。
b := make([]byte, 0, len(s))
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 {
b = utf8.AppendRune(b, r)
}
i += wid
}
return b
}
解释:
Map
函数返回字节切片s
的副本,其中的每个字符根据映射函数进行修改。- 映射函数
mapping
接受一个rune
类型的参数,并返回一个rune
类型的结果。 - 如果映射函数返回负值,表示该字符将从字节切片中删除且不替换。
- 字节切片
s
和输出都被解释为 UTF-8 编码的码点。 - 在循环中,遍历字节切片
s
中的每个字符,根据映射函数进行处理,并将结果追加到新的字节切片b
中。
作用:
- 用于对字节切片中的字符进行映射操作,可以根据映射函数修改字符或删除特定字符。
28、Repeat:重复生成字节切片
// Repeat 返回一个新的字节切片,其中包含 b 的 count 个副本。
//
// 如果 count 为负数或者 (len(b) * count) 溢出,则引发 panic。
func Repeat(b []byte, count int) []byte {
if count == 0 {
return []byte{}
}
// 由于我们无法在溢出时返回错误,
// 如果重复将导致溢出,我们应该引发 panic。
// 参见 golang.org/issue/16237。
if count < 0 {
panic("bytes: negative Repeat count")
}
if len(b) >= maxInt/count {
panic("bytes: Repeat output length overflow")
}
n := len(b) * count
if len(b) == 0 {
return []byte{}
}
// 超过一定的块大小时,使用更大的块作为写入的源是得不偿失的,
// 因为当源太大时,我们基本上只是在折腾 CPU D-cache。
// 因此,如果结果长度大于经验性找到的限制(8KB),我们停止在达到限制后增长源字符串,
// 并继续重用相同的源字符串 - 这应该总是驻留在 L1 缓存中 - 直到完成结果的构建。
// 在结果长度较大(大约超过 L2 缓存大小)的情况下,这会产生显着的速度提升(最多+100%)。
const chunkLimit = 8 * 1024
chunkMax := n
if chunkMax > chunkLimit {
chunkMax = chunkLimit / len(b) * len(b)
if chunkMax == 0 {
chunkMax = len(b)
}
}
nb := bytealg.MakeNoZero(n)
bp := copy(nb, b)
for bp < n {
chunk := bp
if chunk > chunkMax {
chunk = chunkMax
}
bp += copy(nb[bp:], nb[:chunk])
}
return nb
}
解释:
Repeat
函数返回一个新的字节切片,其中包含字节切片b
的count
个副本。- 如果
count
为零,直接返回空字节切片[]byte{}
。 - 如果
count
为负数,引发 panic,表示重复次数为负数。 - 如果
(len(b) * count)
溢出,引发 panic,表示输出长度溢出。 - 计算新字节切片的长度
n
为len(b) * count
。 - 如果字节切片
b
本身为空,直接返回空字节切片[]byte{}
。 - 通过循环复制源字节切片
b
到新建的字节切片nb
中,直到达到重复次数。
作用:
- 用于生成包含指定次数副本的字节切片。
- 注意:如果重复次数为负数或者输出长度溢出,会引发 panic。
29、ToUpper:将字节切片中的字母转换为大写
// ToUpper 返回字节切片 s 的一个副本,其中所有 Unicode 字母都映射为它们的大写形式。
func ToUpper(s []byte) []byte {
isASCII, hasLower := true, false
for i := 0; i < len(s); i++ {
c := s[i]
if c >= utf8.RuneSelf {
isASCII = false
break
}
hasLower = hasLower || ('a' <= c && c <= 'z')
}
if isASCII { // 优化仅包含 ASCII 字节的情况。
if !hasLower {
// 只需返回副本。
return append([]byte(""), s...)
}
b := bytealg.MakeNoZero(len(s))
for i := 0; i < len(s); i++ {
c := s[i]
if 'a' <= c && c <= 'z' {
c -= 'a' - 'A'
}
b[i] = c
}
return b
}
return Map(unicode.ToUpper, s)
}
解释:
ToUpper
函数返回一个字节切片s
的副本,其中所有 Unicode 字母都映射为它们的大写形式。- 首先,通过遍历判断是否为纯 ASCII 字符串(只包含单字节字符),以及是否包含小写字母。
- 如果是纯 ASCII,且不包含小写字母,直接返回字节切片的副本。
- 如果不是纯 ASCII 或包含小写字母,则通过循环将小写字母转换为大写形式,得到新的字节切片。
- 如果是纯 ASCII 但包含小写字母,调用
Map
函数使用 Unicode 大写映射进行转换。
作用:
- 将字节切片中的字母转换为大写形式。
- 优化了只包含 ASCII 字符的情况,避免调用
Map
函数。
30、ToLower:将字节切片中的字母转换为小写
// ToLower 返回字节切片 s 的一个副本,其中所有 Unicode 字母都映射为它们的小写形式。
func ToLower(s []byte) []byte {
isASCII, hasUpper := true, false
for i := 0; i < len(s); i++ {
c := s[i]
if c >= utf8.RuneSelf {
isASCII = false
break
}
hasUpper = hasUpper || ('A' <= c && c <= 'Z')
}
if isASCII { // 优化仅包含 ASCII 字节的情况。
if !hasUpper {
return append([]byte(""), s...)
}
b := bytealg.MakeNoZero(len(s))
for i := 0; i < len(s); i++ {
c := s[i]
if 'A' <= c && c <= 'Z' {
c += 'a' - 'A'
}
b[i] = c
}
return b
}
return Map(unicode.ToLower, s)
}
解释:
ToLower
函数返回一个字节切片s
的副本,其中所有 Unicode 字母都映射为它们的小写形式。- 首先,通过遍历判断是否为纯 ASCII 字符串(只包含单字节字符),以及是否包含大写字母。
- 如果是纯 ASCII,且不包含大写字母,直接返回字节切片的副本。
- 如果不是纯 ASCII 或包含大写字母,则通过循环将大写字母转换为小写形式,得到新的字节切片。
- 如果是纯 ASCII 但包含大写字母,调用
Map
函数使用 Unicode 小写映射进行转换。
作用:
- 将字节切片中的字母转换为小写形式。
- 优化了只包含 ASCII 字符的情况,避免调用
Map
函数。