1、字符串的定义
字符串是不可变值类型,内部用指针指向 UTF-8 字节数组。
Go 语言中可以使用反引号或者双引号来定义字符串。反引号表示原生的字符串,即不进行转义。Go 语言的字符串不支持单引号
• 默认值是空字符串 ""。
• 用索引号访问某字节,如 s[i]。
• 不能用序号获取字节元素指针,&s[i] 非法。
• 不可变类型,无法修改字节数组。
• 字节数组尾部不包含 NULL。
- 双引号
字符串使用双引号括起来,其中的相关的转义字符将被替换。例如:
str := "Hello World! \n Hello Gopher! \n"
输出:
Hello World!
Hello Gopher!
- 反引号
字符串使用反引号括起来,其中的相关的转义字符不会被替换。例如:
str := `Hello World! \n Hello Gopher! \n`
输出:
Hello World! \nHello Gopher! \n
总结:双引号中的转义字符被替换,而反引号中原生字符串中的 \n 会被原样输出。
2、字符串转义符
Go 语言的字符串常见转义符包含回车、换行、单双引号、制表符等,如下表所示。
转义 | 含义 |
---|---|
\r | 回车符(返回行首) |
\n | 换行符(直接跳到下一行的同列位置) |
\t | 制表符 |
’ | 单引号 |
" | 双引号 |
\\ | 反斜杠自身 |
举个例子:
fmt.Println("人生苦短\n我要转go")
3、多行字符串
Go语言中要定义一个多行字符串时,就必须使用反引号
字符
s1 := `第一行
第二行
第三行
`
fmt.Println(s1)
反引号间换行将被作为字符串中的换行,但是所有的转义字符均无效,文本将会原样输出。
4、byte 和 rune 类型
单引号 可 直接 打印 字符 对应的 ASCII码值
组成每个字符串的元素叫做“字符”,可以通过遍历或者单个获取字符串元素获得字符。 字符用单引号(’)包裹起来,如:
var s1 := 'a' // 97
var s2 := 'A' // 65
Go 语言的字符有以下两种:
- uint8类型,或者叫 byte 型,代表了ASCII码的一个字符。
- rune类型,代表一个 UTF-8字符。
字符串底层是一个byte数组,所以可以和[]byte类型相互转换。字符串是不能修改的 字符串是由byte字节组成,所以字符串的长度是byte字节的长度。 rune类型用来表示utf8字符,一个rune字符由一个或多个byte组成。
当需要处理中文、日文或者其他复合字符时,则需要用到rune
类型。rune
类型实际是一个int32
。 Go 使用了特殊的 rune
类型来处理 Unicode
,让基于 Unicode
的文本处理更为方便,也可以使用 byte
型进行默认字符串处理,性能和扩展性都有照顾。
实际上,Go语言的range循环在处理字符串的时候,会自动隐式解码UTF8字符串。
// 遍历字符串
func traversalString() {
s := "pprof.cn博客"
for i := 0; i < len(s); i++ { //byte
fmt.Printf("%v(%c) ", s[i], s[i])
}
fmt.Println()
for _, r := range s { //rune,用于遍历带有中文字符的字符串
fmt.Printf("%v(%c) ", r, r)
}
fmt.Println()
}
输出:
112(p) 112(p) 114(r) 111(o) 102(f) 46(.) 99(c) 110(n) 229(å) 141() 154() 229(å) 174(®) 162(¢)
112(p) 112(p) 114(r) 111(o) 102(f) 46(.) 99(c) 110(n) 21338(博) 23458(客)
因为UTF8编码下一个中文汉字由3~4
个字节组成,所以我们不能简单的按照字节去遍历一个包含中文的字符串,否则就会出现上面输出中第一行的结果。
字符串底层是一个byte数组,所以可以和[]byte类型相互转换。字符串是不能修改的 字符串是由byte字节组成,所以字符串的长度是byte字节的长度。 rune类型用来表示utf8字符,一个rune字符由一个或多个byte组成。
5、字符串遍历
5.1 字节长度
字符串的内容(纯字节)可以通过标准索引法来获取,在中括号 [] 内写入索引,索引从 0 开始计数:
-
字符串 str 的第 1 个字节:str[0] 第 1 个字节
-
str[i - 1] 最后 1 个字节:str[len(str) - 1]
-
获取字符串所占的字节长度,如:len(str)
注意:内置的len()函数获取的是每个字符的UTF-8编码的长度和,而不是直接的字符数量。
package main
import (
"fmt"
"unicode/utf8"
)
func main() {
// 一个中文字符编码需要三个字节
s := "其实就是rune"
fmt.Println(len(s)) // "16" ,字节数, 3 * 4 + 4 = 16 字节
fmt.Println(utf8.RuneCountInString(s)) // "8" ,字符数,共 8 个字符数
}
5.2 字符数量
for range循环处理字符时,不是按照字节的方式来处理的。v 其实际上是一个rune类型值。实际上,Go语言的range循环在处理字符串的时候,会自动隐式解码UTF8字符串。如字符串含有中文等字符,我们可以看到每个中文字符的索引值相差3。
package main
import (
"fmt"
)
func main() {
s := "Go语言四十二章经"
for k, v := range s {
fmt.Printf("k:%d,v:%c == %d\n", k, v, v)
}
}
因为一个中文字符编码需要三个字节,转化单个字节会出现乱码。如字符串含有中文等字符,我们可以看到每个中文字符的索引值相差3。
程序输出:
k:0,v:G == 71
k:1,v:o == 111
k:2,v:语 == 35821
k:5,v:言 == 35328
k:8,v:四 == 22235
k:11,v:十 == 21313
k:14,v:二 == 20108
k:17,v:章 == 31456
k:20,v:经 == 32463
5.3 字符串切片
在 Go 语言中,可以通过字符串切片实现获取子串的功能,切片区间可以对比数学中的区间概念来理解,它是一个左闭右开的区间(索引值非负):
str := "hello, golang 风清扬"
str1 := str[:5] // 获取索引5(不含)之前的子串
str2 := str[7:] // 获取索引7(含)之后的子串
str3 := str[0:5] // 获取从索引0(含)到索引5(不含)之间的子串
str4 := str[:] // 打印完整的字符串
fmt.Println("str1:", str1)
fmt.Println("str2:", str2)
fmt.Println("str3:", str3)
fmt.Println("str4:", str4)
输出:
str1: hello
str2: golang 风清扬
str3: hello
str4: hello, golang 风清扬
6、字符串修改
要修改字符串,需要先将其转换成[]rune或[]byte
,完成后再转换为string
。无论哪种转换,都会重新分配内存,并复制字节数组。
func changeString() {
s1 := "hello"
// 强制类型转换
byteS1 := []byte(s1)
byteS1[0] = 'H'
fmt.Println(string(byteS1))
s2 := "博客"
runeS2 := []rune(s2)
runeS2[0] = '狗'
fmt.Println(string(runeS2))
}
7、字符串类型转换
Go语言中只有强制类型转换,没有隐式类型转换。该语法只能在两个类型之间支持相互转换的时候使用。
强制类型转换的基本语法如下:
T(表达式)
其中,T表示要转换的类型。表达式包括变量、复杂算子和函数返回值等.
比如计算直角三角形的斜边长时使用math包的Sqrt()函数,该函数接收的是float64类型的参数,而变量a和b都是int类型的,这个时候就需要将a和b强制类型转换为float64类型。
func sqrtDemo() {
var a, b = 3, 4
var c int
// math.Sqrt()接收的参数是float64类型,需要强制转换
c = int(math.Sqrt(float64(a*a + b*b)))
fmt.Println(c)
}
8、strings 包 常用操作
说明:这里说的字符,指得是 rune 类型,即一个 UTF-8 字符(Unicode 代码点)。
8.1 字符串比较
-
对字符串的比较除了使用内置的
==
符号来比较,strings标准库还提供了两个方法分别是Compare
和EqualFold
-
需要明确的是,字符的比较是通过查出字符对应的ASCII码值,然后进行总和的计算再进行大小的比较
1)Compare
// Compare 函数,用于比较两个字符串的大小,如果两个字符串相等,返回为 0。
// 如果 a 小于 b ,返回 -1 ,反之返回 1 。
// 不推荐使用这个函数,直接使用 == != > < >= <= 等一系列运算符更加直观。
func Compare(a, b string) int
示例:比较字符串:相等输出0、大于输出1、小于输出-1
a := "gopher"
b := "golang"
fmt.Println(strings.Compare(a, b)) // 1 大于
fmt.Println(strings.Compare(a, a)) // 0 相等
fmt.Println(strings.Compare(b, a)) // -1 小于
2)EqualFold
// EqualFold 函数,计算 s 与 t 忽略字母大小写后是否相等。
func EqualFold(s, t string) bool
EqualFold
相比Compare
忽略了字母大小来比较- 返回的结果为布尔值,true代表相等,false代表不相等
fmt.Println(strings.EqualFold("GO", "go")) // true
fmt.Println(strings.EqualFold("1", "一")) // false
8.2 查询是否存在某个字符或子串
检查的字符串中是否包含有某个字串或字符的需求,比如“abcdefg”是否包含“efg”或者‘a’,strings包主要提供了Contains的方法供开发者使用,返回值是布尔值。
1)Contains方法系
三个函数签名如下:
// 子串 substr 在 s 中,返回 true
func Contains(s, substr string) bool
// chars 中任何一个 Unicode 代码点在 s 中,返回 true
// 注意空字符的情况是返回false,与上面的Contains区分开来
func ContainsAny(s, chars string) bool
// Unicode 代码点 r 在 s 中,返回 true
func ContainsRune(s string, r rune) bool
示例:
- Contains
// Contains
fmt.Println(strings.Contains("adbcadeadh", "adb")) // true,存在
fmt.Println(strings.Contains("adbcadeadh", "abc")) // false,不存在
// 注意空子串和空格子串的区别:
fmt.Println(strings.Contains("abc", "")) // true,存在
fmt.Println(strings.Contains("abc", " ")) // false,不存在
- ContainsAny:但凡字符串s 内包含 chars 任意一个字符(Unicode Code Point) 返回 true
// ConmtainsAny
/*
第二个参数 chars 中任意一个字符(Unicode Code Point)
如果在第一个参数 s 中存在,则返回 true,否则返回false
注意空字符的情况是返回false,与上面的Contains区分开来
*/
fmt.Println(strings.ContainsAny("team", "b")) // false
fmt.Println(strings.ContainsAny("team", "t")) // true
fmt.Println(strings.ContainsAny("team", "a & e ")) // true
fmt.Println(strings.ContainsAny("team", "a | e ")) // true
fmt.Println(strings.ContainsAny("team", "s g ")) // false
fmt.Println(strings.ContainsAny("team", " ")) // false
fmt.Println(strings.ContainsAny("team", "")) // false
fmt.Println(strings.ContainsAny("", "")) // false
fmt.Println(strings.ContainsAny("team", "ea")) // true
fmt.Println(strings.ContainsAny("team", "eg")) // true
fmt.Println(strings.ContainsAny("team", "ig")) // false
fmt.Println(strings.ContainsAny("team", "ge")) // true
- ContainsRune 查询单个字符的情况
// ContainsRune
// 查询单个字符的情况,注意 rune 为 单个字符,单引号
fmt.Println(strings.ContainsRune("team", 'a')) // ture
fmt.Println(strings.ContainsRune("team", 'k')) // false
fmt.Println(strings.ContainsRune("team", ' ')) // false
2)Contains方法系的内部实现
-
查看这三个函数的源码,发现它们只是调用了相应的 Index 函数(子串出现的位置)
-
然后和 0 作比较返回 true 或 fales。
func Contains(s, substr string) bool {
return Index(s, substr) >= 0
}
8.3 子串出现次数 ( 字符串匹配 )
在数据结构与算法中,可能会讲解以下字符串匹配算法:
- 朴素匹配算法
- KMP 算法
- Rabin-Karp 算法
- Boyer-Moore 算法
在 Go 中,查找子串出现次数即字符串模式匹配,实现的是 Rabin-Karp 算法。Count 函数的签名如下:
func Count(s, sep string) int
这里要特别说明一下的是当 sep 为空(“”)时,Count 的返回值是:utf8.RuneCountInString(s) + 1
fmt.Println(strings.Count("cheese", "e")) // 3
fmt.Println(len("百度中国")) // 12 个 字节
fmt.Println(strings.Count("百度中国", "")) // 5, utf8.RuneCountInString(s) + 1
fmt.Println(strings.Count("爱学习的你", "学")) // 1
- 另外,Count 是计算子串在字符串中出现的无重叠的次数,比如:
fmt.Println(strings.Count("fiveveve", "vev")) // 1
8.4 字符或子串在字符串中出现的位置
strings.Index可以在字符串中搜索某个子串,并得到对应子串起始索引下标,若不存在对应子串则返回-1。
// 在 s 中查找 sep 的第一次出现,返回第一次出现的索引
func Index(s, sep string) int
除了对子串进行搜索之外,也可以对某个字节,字符,字符集合进行搜索。
1)获取正向索引,从左往右匹配到第一个
// 字节搜索,在 s 中查找字节 c 的第一次出现,返回第一次出现的索引
func IndexByte(s string, c byte) int
// 字符搜索,Unicode 代码点 r 在 s 中第一次出现的位置
func IndexRune(s string, r rune) int
// 字符集合搜索,匹配chars中的任何一个字符
// 返回chars 中任何一个 Unicode 代码点在 s 中首次出现的位置
func IndexAny(s, chars string) int
// 查找字符 c 在 s 中第一次出现的位置,其中 c 满足 f(c) 返回 true
func IndexFunc(s string, f func(rune) bool) int
示例:
// 匹配字符第一次出现
fmt.Println(strings.Index("golang go go go", "g")) // 0
fmt.Println(strings.Index("golang go go go", "o")) // 1
// 匹配字节,单引号
fmt.Println(strings.IndexByte("hello", 'o')) // 4
// 匹配字符,单引号
fmt.Println(strings.IndexRune("风华正茂", '风')) // 0
// 匹配字符集合
fmt.Println(strings.IndexAny("golang", "l&g")) // 0
fmt.Println(strings.IndexAny("golang", "&o")) // 1
2)获取反向索引,从右往左 匹配到最后一个
- strings包也提供了一系列函数获取对应元素的最后一个匹配项的索引下标
- 对应于上面的每个Index函数,都有一个LastIndex函数
函数声明如下:
// 查找最后一次出现的位置
func LastIndex(s, sep string) int
func LastIndexByte(s string, c byte) int
func LastIndexAny(s, chars string) int
func LastIndexFunc(s string, f func(rune) bool) int
示例:
// 匹配字符最后一次出现
fmt.Println(strings.LastIndex("golang go go go", "go")) // 13
fmt.Println(strings.LastIndexByte("golang go go go", 'o')) // 14
// 匹配字节,单引号
fmt.Println(strings.LastIndexByte("hello", 'o')) // 4
// 匹配字符集合
fmt.Println(strings.LastIndexAny("golang", "l&g")) // 5
fmt.Println(strings.LastIndexAny("golang", "&o")) // 1
- 需要再次注意的是,
IndexAny
和LastIndexAny
返回的是Unicode码点对应的索引这 - 比如“你”和“好”都占用3个字节,“你好”匹配“好”,返回的就是3。
s := "你好帅!你好帅!"
fmt.Printf("\"%s\"的字节长度:%d\n", s, len(s)) // 24
fmt.Println(strings.LastIndexAny(s, "你")) // 12
3)IndexFunc 和 LastIndexFunc
IndexFunc
和LastIndexFunc
,用途是查找字符在字符串中第一次出现的位置,其中字符满足 func函数 并返回 true
// 查找字符串中汉字第一次出现的位置,没有汉字返回-1
han := func(c rune) bool {
return unicode.Is(unicode.Han, c) // 汉字
}
// 无匹配
fmt.Println(strings.IndexFunc("hello golang", han)) // -1
// 匹配 "你"
fmt.Println(strings.LastIndexFunc("你好,Golang", han)) // 3
// 匹配 "好"
fmt.Println(strings.IndexFunc("你好,Golang", han)) // 0
8.5 字符串分割为[]string
字符串分割很常见,一般分割后会返回对应的字符串切片,strings包提供了六个三组分割函数:Fields 和 FieldsFunc、Split 和 SplitAfter、SplitN 和 SplitAfterN。
1)Fields 和 FieldsFunc
函数的签名如下:
func Fields(s string) []string
func FieldsFunc(s string, f func(rune) bool) []string
Fields
Fields
用一个或多个连续的空格分隔字符串 s,返回子字符串的切片。- 如果字符串 s 只包含空格,则返回空列表 ([]string 的长度为 0)。
- 其中,空格的定义是 unicode.IsSpace
常见间隔符包括:’\t’, ‘\n’, ‘\v’, ‘\f’, ‘\r’, ‘ ‘, U+0085 (NEL), U+00A0 (NBSP)
由于是用空格分隔,因此结果中不会含有空格或空子字符串,例如:
fmt.Printf("Fields are: %q\n", strings.Fields(" foo bar baz ")) //Fields are: ["foo" "bar" "baz"]
fmt.Printf("Fields are: %q\n", strings.Fields(" ")) //Fields are: []
FieldsFunc
FieldsFunc
用这样的 Unicode 代码点 c 进行分隔:满足 f© 返回 则true,该函数返回[]string。- 如果字符串 s 中所有的代码点 (unicode code points) 都满足 f© 或者 s 是空,则 FieldsFunc 返回空 slice。
- 也就是说,我们可以通过实现一个回调函数来指定分隔字符串 s 的字符。
- 比如上面的例子,我们通过 FieldsFunc 来实现:
// FieldsFunc are: ["foo" "bar" "baz"]
fmt.Printf("FieldsFunc are: %q\n", strings.FieldsFunc(" foo bar baz ", unicode.IsSpace))
// FieldsFunc are: ["a" "b" "c"]
fmt.Printf("FieldsFunc are: %q\n", strings.FieldsFunc("a+b+c", func(r rune) bool {
return r=='+'
}))
-
通过查看源码发现,实际上,Fields 函数就是调用 FieldsFunc 实现的:
func Fields(s string) []string { return FieldsFunc(s, unicode.IsSpace) }
2)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
fmt.Printf("%q\n", strings.Split("a,b,c,d", ",")) // ["a" "b" "c" "d"]
fmt.Printf("%q\n", strings.Split("你是一个大帅比,你就是", "你")) // ["" "是一个大帅比," "就是"]
fmt.Printf("%q\n", strings.Split(" a bc", "")) // [" " "a" " " "b" "c"]
fmt.Printf("%q\n", strings.Split("", "xx")) // [""]
SplitAfter
那么,Split 和 SplitAfter 有啥区别呢?通过这两句代码的结果就知道它们的区别了:
fmt.Printf("%q\n", strings.Split("foo,bar,baz", ","))
fmt.Printf("%q\n", strings.SplitAfter("foo,bar,baz", ","))
输出:
["foo" "bar" "baz"]
["foo," "bar," "baz"]
也就是说,Split 会将 s 中的 sep 去掉,而 SplitAfter 会保留 sep。
SplitN和SplitAfterN
-
带 N 的方法可以通过最后一个参数 n 控制返回的结果中的 slice 中的元素个数
-
当n < 0 时,返回所有的子字符串;
-
当 n == 0 时,返回的结果是 nil;
-
当 n > 0 时,表示返回的 slice 中最多只有 n 个元素,其中,最后一个元素不会分割,比如:
// SplintN, Split 会将 s 中的 sep 去掉
// n > 0,表示返回的 slice 中最多只有 n 个元素,其中,最后一个元素不会分割
fmt.Printf("%q\n", strings.SplitN("cat,pig,bird", ",", 1)) // ["cat,pig,bird"]
fmt.Printf("%q\n", strings.SplitN("cat,pig,bird", ",", 2)) // ["cat" "pig","bird"]
fmt.Printf("%q\n", strings.SplitN("cat,pig,bird", ",", 3)) // ["cat" "pig" "bird"]
fmt.Printf("%q\n", strings.SplitN("cat,pig,bird", ",", 4)) // ["cat" "pig" "bird"]
// n = 0 , 返回的结果是 nil
fmt.Printf("%q\n", strings.SplitN("cat, pig, alex", ",", 0)) // []
// n < 0, 返回所有的子字符串
fmt.Printf("%q\n", strings.SplitN("cat, pig, alex", ",", -1)) // ["cat" " pig" " alex"]
// SplintAfterN, SplitAfter 会保留 sep
// n > 0,表示返回的 slice 中最多只有 n 个元素,其中,最后一个元素不会分割
fmt.Printf("%q\n", strings.SplitAfterN("cat,pig,bird", ",", 1)) // ["cat,pig,bird"]
fmt.Printf("%q\n", strings.SplitAfterN("cat,pig,bird", ",", 2)) // ["cat,","pig,bird"]
fmt.Printf("%q\n", strings.SplitAfterN("cat,pig,bird", ",", 3)) // ["cat,","pig,","bird"]
fmt.Printf("%q\n", strings.SplitAfterN("cat,pig,bird", ",", 4)) // ["cat,","pig,","bird"]
// n = 0 , 返回的结果是 nil
fmt.Printf("%q\n", strings.SplitAfterN("cat, pig, alex", ",", 0)) // []
// n < 0, 返回所有的子字符串
fmt.Printf("%q\n", strings.SplitAfterN("cat, pig, alex", ",", -1)) // ["cat" " pig" " alex"]
8.6 字符串是否有某个前缀或后缀
函数源码如下:
// 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
}
- 注:如果 prefix 或 suffix 为
""
, 返回值总是true
。
fmt.Println(strings.HasPrefix("Gopher", "Go")) // true
fmt.Println(strings.HasPrefix("Gopher", "C")) // false
fmt.Println(strings.HasPrefix("Gopher", "")) // true
fmt.Println(strings.HasSuffix("Amigo", "go")) // true
fmt.Println(strings.HasSuffix("Amigo", "Ami")) // false
fmt.Println(strings.HasSuffix("Amigo", "")) // true
8.7 字符串 JOIN 操作
Join函数用法简单,将字符串数组(或 slice)连接起来可以通过 Join 实现,函数签名如下:
func Join(a []string, sep string) string
示例如下:
fmt.Println(strings.Join([]string{"name=xxx", "age=xx"}, "&")) // name=xxx&age=xx
假如没有这个库函数,我们自己实现一个,我们会这么实现:
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 方式)。
8.8 字符串重复几次
函数签名如下:
func Repeat(s string, count int) string
将 s 重复 count 次,如果 count 为负数或返回值长度 len(s)*count 超出 string 上限会导致 panic,这个函数使用很简单:
fmt.Println(strings.Repeat("*", 5) + " Go " + strings.Repeat("*", 5)) // ***** Go *****
8.9 字符替换
函数签名如下:
func Map(mapping func(rune) rune, s string) string
-
map 函数,将 s 的每一个字符按照 mapping 的规则做映射替换
-
如果 mapping 返回值 <0 ,则舍弃该字符
-
该方法只能对每一个字符做处理,但处理方式很灵活,可以方便的过滤,筛选汉字等
示例:
mapping := func(r rune) rune {
switch {
case r >= 'A' && r <= 'Z': // 大写字母转小写
return r + 32
case r >= 'a' && r <= 'z': // 小写字母不处理
return r
case unicode.Is(unicode.Han, r): // 汉字换行
return '\n'
}
return -1 // 过滤所有非字母、汉字的字符
}
fmt.Println(strings.Map(mapping, "Hello你#¥%……\n('World\n,好Hello^(&(*界gopher..."))
输出:
hello
world
hello
gopher
8.10 字符串子串替换
-
进行字符串替换时,考虑到性能问题,能不用正则尽量别用,应该用这里的函数。
-
字符串替换的函数签名如下:
// 用 new 替换 s 中的 old,一共替换 n 个。
// 如果 n < 0,则不限制替换次数,即全部替换
func Replace(s, old, new string, n int) string
// 该函数内部直接调用了函数 Replace(s, old, new , -1)
func ReplaceAll(s, old, new string) 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
fmt.Println(strings.ReplaceAll("oink oink oink", "oink", "mmm")) // mmm mmm mmm
如果我们希望一次替换多个,比如我们希望替换 This is <b>HTML</b>
中的 <
和 >
为 <
和 >
,可以调用上面的函数两次。但标准库提供了另外的方法进行这种替换。
8.11 字符串大小写转换
- 大小写转换包含了 4 个相关函数
- ToLower,ToUpper 用于大小写转换
- ToLowerSpecial,ToUpperSpecial 可以转换特殊字符的大小写
函数签名如下:
func ToLower(s string) string
func ToLowerSpecial(c unicode.SpecialCase, s string) string
func ToUpper(s string) string
func ToUpperSpecial(c unicode.SpecialCase, s string) string
示例如下:
fmt.Println(strings.ToLower("HELLO WORLD")) // hello world
fmt.Println(strings.ToLower("Ā Á Ǎ À")) // ā á ǎ à
fmt.Println(strings.ToLowerSpecial(unicode.TurkishCase, "壹")) // 壹
fmt.Println(strings.ToLowerSpecial(unicode.TurkishCase, "HELLO WORLD")) // hello world
fmt.Println(strings.ToLower("Önnek İş")) // önnek iş
fmt.Println(strings.ToLowerSpecial(unicode.TurkishCase, "Önnek İş")) // önnek iş
fmt.Println(strings.ToUpper("hello world")) // HELLO WORLD
fmt.Println(strings.ToUpper("ā á ǎ à")) // Ā Á Ǎ À
fmt.Println(strings.ToUpperSpecial(unicode.TurkishCase, "一")) // 一
fmt.Println(strings.ToUpperSpecial(unicode.TurkishCase, "hello world")) // HELLO WORLD
fmt.Println(strings.ToUpper("örnek iş")) // ÖRNEK IŞ
fmt.Println(strings.ToUpperSpecial(unicode.TurkishCase, "örnek iş")) // ÖRNEK İŞ
8.12 字符串标题处理
- 标题处理包含 3 个相关函数
- 其中 Title 会将 s 每个单词的首字母大写,不处理该单词的后续字符。
- ToTitle 将 s 的每个字母大写。
- ToTitleSpecial 将 s 的每个字母大写,并且会将一些特殊字母转换为其对应的特殊大写字母。
函数签名如下:
func Title(s string) string
func ToTitle(s string) string
func ToTitleSpecial(c unicode.SpecialCase, s string) string
举例如下:
fmt.Println(strings.Title("hElLo wOrLd")) // HElLo WOrLd
fmt.Println(strings.ToTitle("hElLo wOrLd")) // HELLO WORLD
fmt.Println(strings.ToTitleSpecial(unicode.TurkishCase, "hElLo wOrLd")) // HELLO WORLD
fmt.Println(strings.Title("āáǎà ōóǒò êēéěè")) // Āáǎà Ōóǒò Êēéěè
fmt.Println(strings.ToTitle("āáǎà ōóǒò êēéěè")) // ĀÁǍÀ ŌÓǑÒ ÊĒÉĚÈ
fmt.Println(strings.ToTitleSpecial(unicode.TurkishCase, "āáǎà ōóǒò êēéěè")) // ĀÁǍÀ ŌÓǑÒ ÊĒÉĚÈ
fmt.Println(strings.Title("dünyanın ilk borsa yapısı Aizonai kabul edilir"))
// Dünyanın Ilk Borsa Yapısı Aizonai Kabul Edilir
fmt.Println(strings.ToTitle("dünyanın ilk borsa yapısı Aizonai kabul edilir"))
// DÜNYANIN ILK BORSA YAPISI AIZONAI KABUL EDILIR
fmt.Println(strings.ToTitleSpecial(unicode.TurkishCase, "dünyanın ilk borsa yapısı Aizonai kabul edilir"))
// DÜNYANIN İLK BORSA YAPISI AİZONAİ KABUL EDİLİR
8.13 字符串修剪
函数签名:
// 将 s 左侧和右侧中匹配 cutset 中的任一字符的字符去掉
func Trim(s string, cutset string) string
// 将 s 左侧的匹配 cutset 中的任一字符的字符去掉
func TrimLeft(s string, cutset string) string
// 将 s 右侧的匹配 cutset 中的任一字符的字符去掉
func TrimRight(s string, cutset string) string
// 如果 s 的前缀为 prefix 则返回去掉前缀后的 string , 否则 s 没有变化。
func TrimPrefix(s, prefix string) string
// 如果 s 的后缀为 suffix 则返回去掉后缀后的 string , 否则 s 没有变化。
func TrimSuffix(s, suffix string) string
// 将 s 左侧和右侧的间隔符去掉。常见间隔符包括:'\t', '\n', '\v', '\f', '\r', ' ', U+0085 (NEL)
func TrimSpace(s string) string
// 将 s 左侧和右侧的匹配 f 的字符去掉
func TrimFunc(s string, f func(rune) bool) string
// 将 s 左侧的匹配 f 的字符去掉
func TrimLeftFunc(s string, f func(rune) bool) string
// 将 s 右侧的匹配 f 的字符去掉
func TrimRightFunc(s string, f func(rune) bool) string
包含了 9 个相关函数用于修剪字符串。
举例如下:
x := "!!!@@@你好,!@#$ Gophers###$$$"
fmt.Println(strings.Trim(x, "@#$!%^&*()_+=-"))
fmt.Println(strings.TrimLeft(x, "@#$!%^&*()_+=-"))
fmt.Println(strings.TrimRight(x, "@#$!%^&*()_+=-"))
fmt.Println(strings.TrimSpace(" \t\n Hello, Gophers \n\t\r\n"))
fmt.Println(strings.TrimPrefix(x, "!"))
fmt.Println(strings.TrimSuffix(x, "$"))
f := func(r rune) bool {
return !unicode.Is(unicode.Han, r) // 非汉字返回 true
}
fmt.Println(strings.TrimFunc(x, f))
fmt.Println(strings.TrimLeftFunc(x, f))
fmt.Println(strings.TrimRightFunc(x, f))
输出如下:
你好,!@#$ Gophers
你好,!@#$ Gophers###$$$
!!!@@@你好,!@#$ Gophers
Hello, Gophers
!!@@@你好,!@#$ Gophers###$$$
!!!@@@你好,!@#$ Gophers###$$
你好
你好,!@#$ Gophers###$$$
!!!@@@你好
8.14 Replacer 类型
- 这是一个结构,没有导出任何字段
- 实例化通过
func NewReplacer(oldnew ...string) *Replacer
函数进行,其中不定参数 oldnew 是 old-new 对,即进行多个替换。 - 如果 oldnew 长度与奇数,会导致 panic.
示例:
r := strings.NewReplacer("<", "<", ">", ">")
fmt.Println(r.Replace("This is <b>HTML</b>!"))
输出结果:
This is <b>HTML</b>!
另外,Replacer 还提供了另外一个方法,它在替换之后将结果写入 io.Writer 中。
func (r *Replacer) WriteString(w io.Writer, s string) (n int, err error)
8.15 Builder 类型
Builder 结构如下:
type Builder struct {
addr *Builder // of receiver, to detect copies by value
buf []byte
}
- 该类型实现了 io 包下的
Writer
,ByteWriter
,StringWriter
等接口,可以向该对象内写入数据 Builder
没有实现Reader
等接口,所以该类型不可读,但提供了 String 方法可以获取对象内的数据。
函数签名:
// 该方法向 b 写入一个字节
func (b *Builder) WriteByte(c byte) error
// WriteRune 方法向 b 写入一个字符
func (b *Builder) WriteRune(r rune) (int, error)
// WriteRune 方法向 b 写入字节数组 p
func (b *Builder) Write(p []byte) (int, error)
// WriteRune 方法向 b 写入字符串 s
func (b *Builder) WriteString(s string) (int, error)
// Len 方法返回 b 的数据长度。
func (b *Builder) Len() int
// Cap 方法返回 b 的 cap。
func (b *Builder) Cap() int
// Grow 方法将 b 的 cap 至少增加 n (可能会更多)。如果 n 为负数,会导致 panic。
func (b *Builder) Grow(n int)
// Reset 方法将 b 清空 b 的所有内容。
func (b *Builder) Reset()
// String 方法将 b 的数据以 string 类型返回。
func (b *Builder) String() string
Builder
有 4 个与写入相关的方法,这 4 个方法的 error 都总是为 nil.Builder
的cap
会自动增长,一般不需要手动调用Grow
方法。String
方法可以方便的获取Builder
的内容。
示例:
b := strings.Builder{}
_ = b.WriteByte('7')
n, _ := b.WriteRune('夕')
fmt.Println(n)
n, _ = b.Write([]byte("Hello, World"))
fmt.Println(n)
n, _ = b.WriteString("你好,世界")
fmt.Println(n)
fmt.Println(b.Len())
fmt.Println(b.Cap())
b.Grow(100)
fmt.Println(b.Len())
fmt.Println(b.Cap())
fmt.Println(b.String())
b.Reset()
fmt.Println(b.String())
输出结果:
3
12
15
31
32
31
164
7夕Hello, World你好,世界
8.16 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 两个属性来控制。
9、字符串相关补充
标准库中有四个包对字符串处理尤为重要:bytes、strings、strconv和unicode包。
-
strings包提供了许多如字符串的查询、替换、比较、截断、拆分和合并等功能。
-
bytes包也提供了很多类似功能的函数,但是针对和字符串有着相同结构的[]byte类型。因为字符串是只读的,因此逐步构建字符串会导致很多分配和复制。在这种情况下,使用bytes.Buffer类型将会更有效,稍后我们将展示。
-
strconv包提供了布尔型、整型数、浮点数和对应字符串的相互转换,还提供了双引号转义相关的转换。
-
unicode包提供了IsDigit、IsLetter、IsUpper和IsLower等类似功能,它们用于给字符分类。
strings 包提供了很多操作字符串的简单函数,通常一般的字符串操作需求都可以在这个包中找到。
下面简单举几个例子:
-
判断是否以某字符串打头/结尾 strings.HasPrefix(s, prefix string) bool strings.HasSuffix(s, suffix string) bool
-
字符串分割 strings.Split(s, sep string) []string
-
返回子串索引 strings.Index(s, substr string) int strings.LastIndex 最后一个匹配索引
-
字符串连接 strings.Join(a []string, sep string) string 另外可以直接使用“+”来连接两个字符串
-
字符串替换 strings.Replace(s, old, new string, n int) string
-
字符串转化为大小写 strings.ToUpper(s string) string strings.ToLower(s string) string
-
统计某个字符在字符串出现的次数 strings.Count(s, substr string) int
-
判断字符串的包含关系 strings.Contains(s, substr string) bool