Go 语言strings包 字符串操作


字符串常见操作 字符串长度 求子串
是否存在某个字符或子串
子串出现的次数(字符串匹配)
字符串分割(切分)为[]string
字符串是否有某个前缀或后缀
字符或子串在字符串中首次出现的位置或最后一次出现的位置
通过某个字符串将[]string连接起来
字符串重复几次
字符串中子串替换
大小写转换
Trim操作
string类型可看成特殊slice类型 因此获取长度可用内置函数 len 支持切片操作
字符指rune类型 即一个 UTF-8 字符(Unicode 代码点)

Strings是否有某个字符或子串

func Contains(s, substr string) bool   // 子串substr在s中 返回true
func ContainsAny(s, chars string) bool // chars 中任何一个 Unicode 代码点 在s中 返回true
func ContainsRune(s string, r rune) bool // Unicode代码点r 在s中 返回true
ContainsAny 函数
fmt.Println(strings.ContainsAny("team", "i"))//false
fmt.Println(strings.ContainsAny("failure", "u & i"))//true
fmt.Println(strings.ContainsAny("in failure", "s g"))//true
fmt.Println(strings.ContainsAny("foo", ""))//false
fmt.Println(strings.ContainsAny("", ""))//false
第二个参数 chars 中任意一个字符(Unicode Code Point)如果在第一个参数 s 中存在 则返回true
三个函数 是调用了相应的Index函数(子串出现的位置) 然后和 0 作比较返回true或fale
func Contains(s, substr string) bool {
    return Index(s, substr) >= 0
}

Strings子串出现次数(字符串匹配)

数据结构与算法 会讲解以下字符串匹配算法
朴素匹配算法
KMP算法
Rabin-Karp算法
Boyer-Moore算法
还有其他的算法
Go 查找子串出现次数 即字符串模式匹配 实现的是 Rabin-Karp 算法
Count 函数的签名
func Count(s, sep string) int 在 Count 的实现中 处理了几种特殊情况 属于字符匹配预处理的一部分
当 sep 为空时 Count 的返回值是 utf8.RuneCountInString(s) + 1
fmt.Println(strings.Count("five", "")) // before & after each rune
关于Rabin-Karp算法的实现 有兴趣的可以看 Count 源码
Count 是计算 子串在字符串中出现的无重叠的次数
fmt.Println(strings.Count("fivevev", "vev"))//输出 1

字符串分割为[]string

Strings 包提供了 六个 三组分割函数 Fields 和 FieldsFunc , Split 和 SplitAfter SplitN 和 SplitAfterN
Fields 和 FieldsFunc这两个函数的签名
func Fields(s string) []string
func FieldsFunc(s string, f func(rune) bool) []string
Fields 用一个或多个连续的空格分隔字符串 s 返回子字符串的数组 slice
如果字符串 s 只包含空格 则返回空列表([]string的长度为0)
空格的定义是 unicode.IsSpace
用空格分隔 因此结果中不会含有空格或空子字符串
fmt.Printf("Fields are: %q", strings.Fields("  foo bar  baz   "))//输出 Fields are: ["foo" "bar" "baz"]
FieldsFunc
用这样的Unicode代码点 c 进行分隔 满足 f(c) 返回 true
该函数返回 []string 如果字符串 s 中所有的代码点(unicode code points)都满足f(c)或者 s 是空 则 FieldsFunc 返回 空slice
也就是说 可以通过实现一个 回调函数 来指定分隔 字符串 s 的字符 通过 FieldsFunc 来实现
fmt.Println(strings.FieldsFunc("  foo bar  baz   ", unicode.IsSpace))
实际上 Fields 函数就是调用 FieldsFunc 实现的
func Fields(s string) []string {
    return FieldsFunc(s, unicode.IsSpace)
}
Split 和 SplitAfter, SplitN 和 SplitAfterN
这四个函数 通过同一内部函数 实现函数签名及其实现
func Split(s, sep string) []string { return genSplit(s, sep, 0, -1) }
func SplitAfter(s, sep string) []string { return genSplit(s, sep, len(sep), -1) }
func SplitN(s, sep string, n int) []string { return genSplit(s, sep, 0, n) }
func SplitAfterN(s, sep string, n int) []string { return genSplit(s, sep, len(sep), n) }
都调用genSplit 函数
这四个函数都是通过 sep 进行分割 返回[]string
如果 sep 为空 相当于分成一个个的 UTF-8 字符 如 Split("abc","") 得到的是[a b c]
Split(s, sep) 和 SplitN(s, sep, -1) 等价 SplitAfter(s, sep) 和 SplitAfterN(s, sep, -1) 等价
Split 和 SplitAfter 区别
fmt.Printf("%q\n", strings.Split("foo,bar,baz", ","))//["foo" "bar" "baz"]
fmt.Printf("%q\n", strings.SplitAfter("foo,bar,baz", ","))//["foo," "bar," "baz"]
也就是说 Split 会将 s 中的 sep 去掉 而 SplitAfter 会保留 sep
带 N 的方法可以通过最后一个参数 n 控制返回的结果中的 slice 中的元素个数
当 n < 0 时 返回所有的子字符串
当 n == 0 时 返回的结果是 nil
当 n > 0 时 表示返回的 slice 中最多只有 n 个元素 其中 最后一个元素不会分割 比如
fmt.Printf("%q\n", strings.SplitN("foo,bar,baz", ",", 2))//输出 ["foo" "bar,baz"]
官方文档 例子 注意 输出结果
fmt.Printf("%q\n", strings.Split("a,b,c", ","))//["a" "b" "c"]
fmt.Printf("%q\n", strings.Split("a man a plan a canal panama", "a "))//["" "man " "plan " "canal panama"]
fmt.Printf("%q\n", strings.Split(" xyz ", ""))//[" " "x" "y" "z" " "]
fmt.Printf("%q\n", strings.Split("", "Bernardo O'Higgins"))//[""]

字符串是否有前缀或后缀

// s 中是否以 prefix 开始
func HasPrefix(s, prefix string) bool {
    return len(s) >= len(prefix) && s[0:len(prefix)] == prefix
}
// s 中是否以 suffix 结尾
func HasSuffix(s, suffix string) bool {
    return len(s) >= len(suffix) && s[len(s)-len(suffix):] == suffix
}

字符或子串 在字符串中出现的位置

// 在 s 中查找 sep 的第一次出现 返回第一次出现的索引
func Index(s, sep string) int
// chars 中任何一个 Unicode代码点 在s中首次出现的位置
func IndexAny(s, chars string) int
// 查找 字符 c 在 s 中第一次出现的位置 其中 c 满足 f(c) 返回 true
func IndexFunc(s string, f func(rune) bool) int
// Unicode 代码点 r  在 s 中第一次出现的位置
func IndexRune(s string, r rune) int
// 有三个对应的查找最后一次出现的位置
func LastIndex(s, sep string) int
func LastIndexAny(s, chars string) int
func LastIndexFunc(s string, f func(rune) bool) int
Contain 相关的函数内部调用的是响应的 Index 函数
这一序列函数 IndexFunc 例子
fmt.Printf("%d\n", strings.IndexFunc("studygolang", func(c rune) bool {
    if c > 'u' {
        return true
    }
    return false
}))//输出 4 因为 y 的 Unicode 代码点大于 u 的代码点

字符串 JOIN 操作

将 字符串数组(或slice)连接起来 通过 Join 实现
func Join(a []string, sep string) string
假如没有这个库函数 自己实现 会这么实现
func Join(str []string, sep string) string { // 特殊情况应该做处理
    if len(str) == 0 {
        return ""
    }
    if len(str) == 1 {
        return str[0]
    }
    buffer := bytes.NewBufferString(str[0])
    for _, s := range str[1:] {
        buffer.WriteString(sep)
        buffer.WriteString(s)
    }
    return buffer.String()
}这里使用了 bytes 包的 Buffer 类型 避免大量的字符串连接操作 因为 Go 中字符串是不可变的
标准库的实现
func Join(a []string, sep string) string {
    if len(a) == 0 {
        return ""
    }
    if len(a) == 1 {
        return a[0]
    }
    n := len(sep) * (len(a) - 1)
    for i := 0; i < len(a); i++ {
        n += len(a[i])
    }
    b := make([]byte, n)
    bp := copy(b, a[0])
    for _, s := range a[1:] {
        bp += copy(b[bp:], sep)
        bp += copy(b[bp:], s)
    }
    return string(b)
}
标准库的实现没有用 bytes 包 也不会简单的通过 + 号连接字符串
Go 不允许 循环依赖  标准库中很多时候 出现代码拷贝 而不是引入某个包
这里 Join 的实现方式挺好  不直接使用 bytes 包  是不想依赖 bytes 包(其实 bytes 中的实现也是 copy 方式)
简单使用示例
fmt.Println(Join([]string{"name=xxx", "age=xx"}, "&"))// 输出 name=xxx&age=xx

字符串重复几次

func Repeat(s string, count int) string
fmt.Println("ba" + strings.Repeat("na", 2))// 输出 banana

字符串 子串 替换

字符串 替换时 考虑到性能 尽量别用正则 应该用这里的函数
// 用 new 替换 s 中的 old 一共替换 n 个  // 如果 n < 0 不限制替换次数 即全部替换
func Replace(s, old, new string, n int) string
fmt.Println(strings.Replace("oink oink oink", "k", "ky", 2))//oinky oinky oink
fmt.Println(strings.Replace("oink oink oink", "oink", "moo", -1))//moo moo moo
如果 一次替换多个 希望替换 This is <b>HTML</b> 中的 < 和 > 为 &lt; 和 &gt; 可以调用上面的函数两次 但标准库提供了另外的方法进行这种替换

Replacer 类型

一个结构 没有导出任何字段 实例化通过 func NewReplacer(oldnew ...string) *Replacer 函数进行
其中不定参数 oldnew 是 old-new 对 即进行多个替换
解决上面说的替换问题
r := strings.NewReplacer("<", "&lt;", ">", "&gt;")
fmt.Println(r.Replace("This is <b>HTML</b>!"))
另外 Replacer 还提供了另外一个方法 func (r *Replacer) WriteString(w io.Writer, s string) (n int, err error)
它在替换之后将结果写入 io.Writer 中

Reader 类型

实现了 io 包中的接口 实现了 io.Reader (Read 方法)
io.ReaderAt  (ReadAt 方法)
io.Seeker    (Seek 方法)
io.WriterTo  (WriteTo 方法)
io.ByteReader(ReadByte 方法)
io.ByteScanner(ReadByte 和 UnreadByte 方法)
io.RuneReader (ReadRune 方法)
io.RuneScanner(ReadRune 和 UnreadRune 方法)
Reader 结构如下
type Reader struct {
    s        string    // Reader 读取的数据来源
    i        int // current reading index(当前读的索引位置)
    prevRune int // index of previous rune; or < 0(前一个读取的 rune 索引位置)
} 可见 Reader 结构没有导出任何字段 而是提供一个实例化方法
func NewReader(s string) *Reader
该方法接收一个字符串 返回的 Reader 实例就是 从 该参数字符串 读数据 在后面学习了 bytes 包之后 可以知道 bytes.NewBufferString 有类似的功能
不过 如果只是为了读取 NewReader 会更高效
看源码实现 大部分都是根据 i prevRune 两个属性来控制

Go strings 字符串和strings相关