文本处理
1 strings包
1.1 字符串比较
package main
import (
"fmt"
"strings")
func main() {
a := "a"
b := "A"
/*
Compare 函数,用于比较两个字符串的大小,如果两个字符串相等,返回为 0。如果 a 小于 b ,返回 -1 ,反之返回 1 。不推荐使用这个函数,直接使用 == != > < >= <= 等一系列运算符更加直观。
*/
fmt.Println(strings.Compare(a, b)) // 区分大小
fmt.Println(strings.EqualFold(a, b)) // 不区分大小
}
/*
1
true
*/
1.2 是否存在某个字符
有好几个函数可以进行判断。
// 子串 substr 在 s 中,返回 true
func Contains(s, substr string) bool
// chars 中任何一个 Unicode 代码点在 s 中,返回 true
func ContainsAny(s, chars string) bool
// Unicode 代码点 r 在 s 中,返回 true
func ContainsRune(s string, r rune) bool
// 返回子字符串在字符串中第一次出现的位置,不存在则返回 -1。
func Index(s, substr string) int
// IndexAny 返回 s 中字符的任何 Unicode 代码点的第一个实例的索引,如果 s 中不存在字符的 Unicode 代码点,则返回 -1。
func IndexAny(s, chars string) int
// 返回rune中在s中的位置,否则返回-1
func IndexRune(s string, r rune) int
// 带Last前缀的函数返回最后一次出现的位置
看下运行这些函数返回的结果。
func main() {
s := "yes"
fmt.Println(strings.Contains(s, "ye"))
fmt.Println(strings.ContainsAny(s, "y e"))
fmt.Println(strings.ContainsRune(s, 'y'))
fmt.Println(strings.Index(s, "ye"))
fmt.Println(strings.IndexAny(s, "ye"))
fmt.Println(strings.IndexRune(s, 'e'))
}
/*
true
true
true
0
0
1
*/
其实查看前面三个函数的源代码就会发现其实调用的都是相对应类型Index
函数。
// Contains reports whether substr is within s.
func Contains(s, substr string) bool {
return Index(s, substr) >= 0
}
// ContainsAny reports whether any Unicode code points in chars are within s.
func ContainsAny(s, chars string) bool {
return IndexAny(s, chars) >= 0
}
// ContainsRune reports whether the Unicode code point r is within s.
func ContainsRune(s string, r rune) bool {
return IndexRune(s, r) >= 0
}
1.3 子串出现的次数
go中计算子串在字符串中出现次数的函数是Count
。
func main() {
s := "chesesese"
fmt.Println(strings.Count(s, "ese")) // 2
}
特别要注意的是Count返回的是在字符串中无重叠的次数,所以这里返回的是2.当substr为空的时候返回的是utf8.RuneCountInString(s) + 1
1.4 字符串分割
字符串分割最常用的就是Split
系列函数。
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) 等价。带After
的函数会把分隔符保留下来。
带 N 的方法可以通过最后一个参数 n 控制返回的结果中的 slice 中的元素个数,当 n < 0 时,返回所有的子字符串;当 n == 0 时,返回的结果是 nil;当 n > 0 时,表示返回的 slice 中最多只有 n 个元素,其中,最后一个元素不会分割,比如:
func main() {
s := "I,love,you,as,who,you,are"
fmt.Println(strings.Split(s, ","))
fmt.Println(strings.SplitN(s, ",", 2))
fmt.Println(strings.SplitAfter(s, ","))
fmt.Println(strings.SplitAfterN(s, ",", 2))
}
/*
[I love you as who you are]
[I love,you,as,who,you,are]
[I, love, you, as, who, you, are]
[I, love,you,as,who,you,are]
*/
1.5 判断字符串前缀或后缀
// 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。
1.6 Join操作
可以将字符串切片跳过sep分隔符连接起来。
func Join(a []string, sep string) string
1.7 构建重复字符
函数签名如下:
func Repeat(s string, count int) string
将 s 重复 count 次,如果 count 为负数或返回值长度 len(s)*count 超出 string 上限会导致 panic,这个函数使用很简单:
fmt.Println("ba" + strings.Repeat("na", 2))
输出结果:
banana
1.8 字符替换
可以使用Map
或Replace
对字符进行替换,但这两个方法粒度不同。
func Map(mapping func(rune) rune, s string) string
// 如果 n < 0,则不限制替换次数,即全部替换
func Replace(s, old, new string, n int) string
// 该函数内部直接调用了函数 Replace(s, old, new , -1)
func ReplaceAll(s, old, new string) string
Map
方法可以设置一个函数按照字面量粒度对字符进行映射替换。如果 mapping 返回值 <0 ,则舍弃该字符。该方法只能对每一个字符做处理,但处理方式很灵活,可以方便的过滤,筛选汉字等。
var mapping = func(r rune) rune {
switch {
case r >= 'A' && r <= 'Z':
return r + 32
case r >= 'a' && r <= 'z':
return r
}
return -1
}
func main() {
s := "yeyeyeyeeyAAAAAA"
fmt.Println(strings.Map(mapping, s)) // yeyeyeyeeyaaaaaa
}
Replace
函数可以直接替换字符串中的子串。
func main() {
s := "yeyeyeyeeyAAAAAA"
fmt.Println(strings.Replace(s, "A", "T", -1)) // yeyeyeyeeyTTTTTT
}
Replce
方法只能对单个子串进行替换,如果想进行多子串替换,可以定义一个Replacer
对象。
func main() {
// NewReplacer panics if given an odd number of arguments.
r := strings.NewReplacer("y", "Y", "s", "S") // 参数为一个old->new映射列表
fmt.Println(r.Replace("ysysy"))
b := &bytes.Buffer{}
r.WriteString(b, "ysysysysys") // 替换后的字符写入io.Writer
fmt.Println(b)
}
/*
YSYSY
YSYSYSYSYS
*/
1.9 大小写转换
在strings包中提供了四种大小写转换的函数。
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
这四种函数都用来把字符串全部转换成大小或小写。但ToLowerSpecial
和ToUpperSpecial
可以转换特殊字符的大小写。
func main() {
// 自定义大小写映射规则
specialCase := unicode.SpecialCase{
unicode.CaseRange{
Lo: 0x41, // 'A'
Hi: 0x5A, // 'Z'
Delta: [unicode.MaxCase]rune{32, 0, 0}, // 将大写字母转换为小写
},
unicode.CaseRange{
Lo: 0x61, // 'a'
Hi: 0x7A, // 'z'
Delta: [unicode.MaxCase]rune{-32, 0, 0}, // 将小写字母转换为大写
},
}
fmt.Println(strings.ToLowerSpecial(specialCase, "HELLO, WORLD")) // "hello, world"
fmt.Println(strings.ToLowerSpecial(specialCase, "hello, world")) // "HELLO, WORLD"
}
1.10 修剪字符串
// 将 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
示例:
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###$$$
!!!@@@你好
*/
1.11 Builder 类型
该类型实现了 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 的 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你好,世界
*/
1.12 字符串切片
在Go中字符串的底层其实就是一个[]byte,所以也可以使用切片的方法获取子串。
/*
s[start:end(exclusive)]
*/
func main() {
s := "abcdefgh"
fmt.Println(s[:4]) //省略代表从头开始
}
2 bytes包
该包定义了一些操作 byte slice 的便利操作。因为字符串可以表示为 []byte,因此,bytes 包定义的函数、方法等和 strings 包很类似。
2.1 Rune类型转换
// 将 []byte 转换为 []rune
func Runes(s []byte) []rune
该函数将 []byte 转换为 []rune ,适用于汉字等多字节字符,示例:
b:=[]byte("你好,世界")
for k,v:=range b{
fmt.Printf("%d:%s |",k,string(v))
}
r:=bytes.Runes(b)
for k,v:=range r{
fmt.Printf("%d:%s|",k,string(v))
}
运行结果:
0:ä |1:½ |2: |3:å |4:¥ |5:½ |6:ï |7:¼ |8: |9:ä |10:¸ |11: |12:ç |13: |14: |
0:你|1:好|2:,|3:世|4:界|
2.2 Buffer类型
type Buffer struct {
buf []byte
off int
lastRead readOp
}
bytes.Buffer 类型,该类型实现了 io 包下的 ByteScanner, ByteWriter, ReadWriter, Reader, ReaderFrom, RuneReader, RuneScanner, StringWriter, Writer, WriterTo 等接口,可以方便的进行读写操作。
Buffer 可以通过 3 中方法初始化对象:
a := bytes.NewBufferString("Hello World")
b := bytes.NewBuffer([]byte("Hello World"))
c := bytes.Buffer{}
fmt.Println(a)
fmt.Println(b)
fmt.Println(c)
}
// 读取到字节 delim 后,以字节数组的形式返回该字节及前面读取到的字节。如果遍历 b.buf 也找不到匹配的字节,则返回错误(一般是 EOF)
func (b *Buffer) ReadBytes(delim byte) (line []byte, err error)
// 读取到字节 delim 后,以字符串的形式返回该字节及前面读取到的字节。如果遍历 b.buf 也找不到匹配的字节,则返回错误(一般是 EOF)
func (b *Buffer) ReadString(delim byte) (line string, err error)
// 截断 b.buf , 舍弃 b.off+n 之后的数据。n == 0 时,调用 Reset 方法重置该对象,当 n 越界时(n < 0 || n > b.Len() )方法会触发 panic.
func (b *Buffer) Truncate(n int)
a := bytes.NewBufferString("Good Night")
x, err := a.ReadBytes('t')
if err != nil {
fmt.Println("delim:t err:", err)
} else {
fmt.Println(string(x))
}
a.Truncate(0)
a.WriteString("Good Night")
fmt.Println(a.Len())
a.Truncate(5)
fmt.Println(a.Len())
y, err := a.ReadString('N')
if err != nil {
fmt.Println("delim:N err:", err)
} else {
fmt.Println(y)
}
/*
Good Night
10
5
delim:N err: EOF
*/
3 strconv:字符串与基本类型转换
由于将字符串转为其他数据类型可能会出错,strconv 包定义了两个 error 类型的变量:ErrRange 和 ErrSyntax。其中,ErrRange 表示值超过了类型能表示的最大范围,比如将 "128" 转为 int8 就会返回这个错误;ErrSyntax 表示语法错误,比如将 "" 转为 int 类型会返回这个错误。
然而,在返回错误的时候,不是直接将上面的变量值返回,而是通过构造一个 NumError 类型的 error 对象返回。NumError 结构的定义如下:
// A NumError records a failed conversion.
type NumError struct {
Func string // the failing function (ParseBool, ParseInt, ParseUint, ParseFloat)
Num string // the input
Err error // the reason the conversion failed (ErrRange, ErrSyntax)
}
可见,该结构记录了转换过程中发生的错误信息。该结构不仅包含了一个 error 类型的成员,记录具体的错误信息,而且它自己也实现了 error 接口:
func (e *NumError) Error() string {
return "strconv." + e.Func + ": " + "parsing " + Quote(e.Num) + ": " + e.Err.Error()
}
包的实现中,定义了两个便捷函数,用于构造 NumError 对象:
func syntaxError(fn, str string) *NumError {
return &NumError{fn, str, ErrSyntax}
}
func rangeError(fn, str string) *NumError {
return &NumError{fn, str, ErrRange}
}
3.1 字符串和整型之间的转换
最常用的有三个函数。
func ParseInt(s string, base int, bitSize int) (i int64, err error)
func ParseUint(s string, base int, bitSize int) (n uint64, err error)
func Atoi(s string) (i int, err error)
其中,Atoi 是 ParseInt 的便捷版,内部通过调用 ParseInt(s, 10, 0) 来实现的;ParseInt 转为有符号整型;ParseUint 转为无符号整型。
参数 base 代表字符串按照给定的进制进行解释。一般的,base 的取值为 2~36,如果 base 的值为 0,则会根据字符串的前缀来确定 base 的值:"0x" 表示 16 进制; "0" 表示 8 进制;否则就是 10 进制。
参数 bitSize 表示的是整数取值范围,或者说整数的具体类型。取值 0、8、16、32 和 64 分别代表 int、int8、int16、int32 和 int64。
这里有必要说一下,当 bitSize==0 时的情况。
Go 中,int/uint 类型,不同系统能表示的范围是不一样的,目前的实现是,32 位系统占 4 个字节;64 位系统占 8 个字节。当 bitSize==0 时,应该表示 32 位还是 64 位呢?这里没有利用 runtime.GOARCH 之类的方式,而是巧妙的通过如下表达式确定 intSize:
const intSize = 32 << uint(^uint(0)>>63)
const IntSize = intSize // number of bits in int, uint (32 or 64)
package main
import (
"fmt"
"strconv")
func main() {
s := "128"
n, err := strconv.ParseInt(s, 10, 8) // 如果位数容纳不了,会返回最大的值,n=127
if err != nil {
fmt.Println(err)
}
fmt.Printf("%T\t%v\n", n, n)
p, err := strconv.ParseUint(s, 10, 8)
fmt.Printf("%T\t%v\n", p, p)
m, err := strconv.Atoi(s)
fmt.Printf("%T\t%v\n", m, m)
}
/*
strconv.ParseInt: parsing "128": value out of range
int64 127
uint64 128
int 128
*/
3.2 整型转换为字符串
实际应用中,我们经常会遇到需要将字符串和整型连接起来,在 Java 中,可以通过操作符 "+" 做到。不过,在 Go 语言中,你需要将整型转为字符串类型,然后才能进行连接。这个时候,strconv 包中的整型转字符串的相关函数就派上用场了。这些函数签名如下:
func FormatUint(i uint64, base int) string // 无符号整型转字符串
func FormatInt(i int64, base int) string // 有符号整型转字符串
func Itoa(i int) string
其中,Itoa 内部直接调用 FormatInt(i, 10) 实现的。base 参数可以取 2~36(0-9,a-z)。
标准库还提供了另外两个函数:AppendInt 和 AppendUint,这两个函数不是将整数转为字符串,而是将整数转为字符数组 append 到目标字符数组中。(最终,我们也可以通过返回的 []byte 得到字符串)
除了使用上述方法将整数转为字符串外,经常见到有人使用 fmt 包来做这件事。如:
fmt.Sprintf("%d", 127)
那么,这两种方式我们该怎么选择呢?我们主要来考察一下性能。
startTime := time.Now()
for i := 0; i < 10000; i++ {
fmt.Sprintf("%d", i)
}
fmt.Println(time.Now().Sub(startTime))
startTime := time.Now()
for i := 0; i < 10000; i++ {
strconv.Itoa(i)
}
fmt.Println(time.Now().Sub(startTime))
我们分别循环转换了 10000 次。Sprintf 的时间是 3.549761ms,而 Itoa 的时间是 848.208us,相差 4 倍多。
Sprintf 性能差些可以预见,因为它接收的是 interface,需要进行反射等操作。个人建议使用 strconv 包中的方法进行转换。
3.3 字符串与布尔值之间的转换
Go 中字符串和布尔值之间的转换比较简单,主要有三个函数:
// 接受 1, t, T, TRUE, true, True, 0, f, F, FALSE, false, False 等字符串;
// 其他形式的字符串会返回错误
func ParseBool(str string) (value bool, err error)
// 直接返回 "true" 或 "false"
func FormatBool(b bool) string
// 将 "true" 或 "false" append 到 dst 中
// 这里用了一个 append 函数对于字符串的特殊形式:append(dst, "true"...)
func AppendBool(dst []byte, b bool)
3.4 字符串与浮点数之间的转换
类似的,包含三个函数:
func ParseFloat(s string, bitSize int) (f float64, err error)
func FormatFloat(f float64, fmt byte, prec, bitSize int) string
func AppendFloat(dst []byte, f float64, fmt byte, prec int, bitSize int)
函数的命名和作用跟上面讲解的其他类型一致。
关于 FormatFloat 的 fmt 参数, 在第一章第三节格式化 IO 中有详细介绍。而 prec 表示有效数字(对 fmt='b' 无效),对于 'e', 'E' 和 'f',有效数字用于小数点之后的位数;对于 'g' 和 'G',则是所有的有效数字。例如:
strconv.FormatFloat(1223.13252, 'e', 3, 32) // 结果:1.223e+03
strconv.FormatFloat(1223.13252, 'g', 3, 32) // 结果:1.22e+03
由于浮点数有精度的问题,精度不一样,ParseFloat 和 FormatFloat 可能达不到互逆的效果。如:
s := strconv.FormatFloat(1234.5678, 'g', 6, 64)
strconv.ParseFloat(s, 64)
另外,fmt='b' 时,得到的字符串是无法通过 ParseFloat 还原的。
3.5 Quote
除了这两种方法,strconv 包还提供了函数这做件事(Quote 函数)。我们称 "studygolang.com" 这种用双引号引起来的字符串为 Go 语言字面值字符串(Go string literal)。
fmt.Println("This is", strconv.Quote("studygolang.com"), "website")
// 等价于
fmt.Println(`This is "studygolang.com" website`)
4 Regexp包
Go中使用Regexp包来使用正则表达式,简单的正则可以使用以下两个函数。
// byte切片
func Match(pattern string, b []byte) (matched bool, err error)
// 字符串,正则表达式错误时才会返回错误
func MatchString(pattern string, s string) (matched bool, err error)
示例:
func main() {
matched, err := regexp.Match("as.*", []byte(`asdasdasd`))
fmt.Println(matched, err)
matched, err = regexp.MatchString("a(b", "ashjhj") // 正则语法错误
fmt.Println(matched, err)
}
/*
true <nil>
false error parsing regexp: missing closing ): `a(b`
*/
4.1 Regexp类型
前面两种方法适合于简单的正则匹配,如果想进行复杂的匹配,需要使用Regexp类型。创建Regexp类型的方法有四种。
func Compile(expr string) (*Regexp, error)
// 跟Compile方法类似,但语法为egrep,且匹配语义改为leftmost-longest
// Compile创建出来的Regexp对象也可以调用Longest方法修改匹配语义
func CompilePOSIX(expr string) (*Regexp, error)
// 跟Compile类似,但语法错误直接panic
func MustCompile(str string) *Regexp
func MustCompilePOSIX(str string) *Regexp
4.1.1 匹配操作
会检查是否包含对应正则匹配模式。
func (re *Regexp) Match(b []byte) bool
func (re *Regexp) MatchReader(r io.RuneReader) bool
func (re *Regexp) MatchString(s string) bool
4.2 查找操作
// 直接返回匹配的第一个子串
func (re *Regexp) Find(b []byte) []byte
// 返回匹配的最多n个子串,n如果为负,返回所有
func (re *Regexp) FindAll(b []byte, n int) [][]byte
// 查找并返回正则表达式匹配的所有子串的起始和结束位置,最多返回n个。如果n为负数,则返回所有匹配的子串的位置。
func (re *Regexp) FindAllIndex(b []byte, n int) [][]int
func (re *Regexp) FindAllString(s string, n int) []string
func (re *Regexp) FindAllStringIndex(s string, n int) [][]int
// 查找并返回正则表达式在 `s` 中匹配的所有子串及其分组,最多返回 `n` 个。如果 `n` 为负数,则返回所有匹配的子串及其分组。
func (re *Regexp) FindAllStringSubmatch(s string, n int) [][]string
func (re *Regexp) FindAllStringSubmatchIndex(s string, n int) [][]int
func (re *Regexp) FindAllSubmatch(b []byte, n int) [][][]byte
func (re *Regexp) FindAllSubmatchIndex(b []byte, n int) [][]int
// 查找并返回正则表达式在 b 中匹配的第一个子串的起始和结束位置。如果没有匹配,则返回 nil。
func (re *Regexp) FindIndex(b []byte) (loc []int)
func (re *Regexp) FindReaderIndex(r io.RuneReader) (loc []int)
func (re *Regexp) FindReaderSubmatchIndex(r io.RuneReader) []int
// 查找并返回正则表达式在 `s` 中匹配的第一个子串。如果没有匹配,则返回空字符串。
func (re *Regexp) FindString(s string) string
func (re *Regexp) FindStringIndex(s string) (loc []int)
// 查找并返回正则表达式在 `s` 中匹配的第一个子串及其分组。如果没有匹配,则返回 `nil`。
func (re *Regexp) FindStringSubmatch(s string) []string
func (re *Regexp) FindStringSubmatchIndex(s string) []int
func (re *Regexp) FindSubmatch(b []byte) [][]byte
func (re *Regexp) FindSubmatchIndex(b []byte) []int
4.2.1 替换操作
// 用 `repl` 替换 `src` 中所有匹配 `re` 的子串。
func (re *Regexp) ReplaceAll(src, repl []byte) []byte
// 用 `repl` 函数的返回值替换 `src` 中所有匹配 `re` 的子串。`repl` 函数接收匹配的子串并返回替换的字节切片。
func (re *Regexp) ReplaceAllFunc(src []byte, repl func([]byte) []byte) []byte
// 用 `repl` 字面值替换 `src` 中所有匹配 `re` 的子串。与 `ReplaceAll` 不同的是,`ReplaceAllLiteral` 不会对 `repl` 进行任何特殊处理(如转义)。
func (re *Regexp) ReplaceAllLiteral(src, repl []byte) []byte
// 用 `repl` 字面值替换 `src` 中所有匹配 `re` 的子串。与 `ReplaceAllString` 不同的是,`ReplaceAllLiteralString` 不会对 `repl` 进行任何特殊处理(如转义)。
func (re *Regexp) ReplaceAllLiteralString(src, repl string) string
// 用 `repl` 替换 `src` 中所有匹配 `re` 的子串。
func (re *Regexp) ReplaceAllString(src, repl string) string
// 用 `repl` 函数的返回值替换 `src` 中所有匹配 `re` 的子串。`repl` 函数接收匹配的子串并返回替换的字符串。
func (re *Regexp) ReplaceAllStringFunc(src string, repl func(string) string) string
4.2.2 切割操作
// 将切片 s 拆分为由表达式分隔的子字符串,并返回这些表达式匹配之间的子字符串切片。
func (re *Regexp) Split(s string, n int) []string
示例:
func main() {
rex := regexp.MustCompile(`a*`) //如果不包含正则元字符就相当于strings.SplitN
s := "abaabaccadaaae"
fmt.Println(rex.Split(s, 5))
}
/*
[ b b c cadaaae]
*/
4.2.3 其他操作
// 返回正则表达式的字符串表示。
func (re *Regexp) String() string
// 返回正则表达式中的分组数量。
func (re *Regexp) NumSubexp() int
5 unicode包:Unicode码点
世界中的字符有许许多多,有英文,中文,韩文等。我们强烈需要一个大大的映射表把世界上的字符映射成计算机可以阅读的二进制数字(字节)。 这样,每个字符都给予一个独一无二的编码,就不会出现写文字的人和阅读文字的人编码不同而出现无法读取的乱码现象了。
于是 Unicode 就出现了,它是一种所有符号的编码映射。最开始的时候,unicode 认为使用两个字节,也就是 16 位就能包含所有的字符了。 但是非常可惜,两个字节最多只能覆盖 65536 个字符,这显然是不够的,于是附加了一套字符编码,即 unicode4.0,附加的字符用 4 个字节表示。 现在为止,大概 Unicode 可以覆盖 100 多万个字符了。
Unicode 只是定义了一个字符和一个编码的映射,但是呢,对应的存储却没有制定。 比如一个编码 0x0041 代表大写字母 A,那么可能有一种存储至少有 4 个字节,那可能 0x00000041 来存储代表 A。 这个就是 unicode 的具体实现。unicode 的具体实现有很多种,UTF-8 和 UTF-16 就是其中两种。
UTF-8 表示最少用一个字节就能表示一个字符的编码实现。它采取的方式是对不同的语言使用不同的方法,将 unicode 编码按照这个方法进行转换。 我们只要记住最后的结果是英文占一个字节,中文占三个字节。这种编码实现方式也是现在应用最为广泛的方式了。
UTF-16 表示最少用两个字节能表示一个字符的编码实现。同样是对 unicode 编码进行转换,它的结果是英文占用两个字节,中文占用两个或者四个字节。 实际上,UTF-16 就是最严格实现了 unicode4.0。但由于英文是最通用的语言,所以推广程度没有 UTF-8 那么普及。
go 对 unicode 的支持包含三个包 :
unicode
unicode/utf8
unicode/utf16
unicode 包包含基本的字符判断函数。utf8 包主要负责 rune 和 byte 之间的转换。utf16 包负责 rune 和 uint16 数组之间的转换。
由于字符的概念有的时候比较模糊,比如字符(小写 a)普通显示为 a,在重音字符中(grave-accented)中显示为à。 这时候字符(character)的概念就有点不准确了,因为 a 和à显然是两个不同的 unicode 编码,但是却代表同一个字符,所以引入了 rune。 一个 rune 就代表一个 unicode 编码,所以上面的 a 和à是两个不同的 rune。
这里有个要注意的事情,go 语言的所有代码都是 UTF8 的,所以如果我们在程序中的字符串都是 utf8 编码的,但是我们的单个字符(单引号扩起来的)却是 unicode 的。
5.1 unicode包
常用函数:
func IsControl(r rune) bool // 是否控制字符
func IsDigit(r rune) bool // 是否阿拉伯数字字符,即 0-9
func IsGraphic(r rune) bool // 是否图形字符
func IsLetter(r rune) bool // 是否字母
func IsLower(r rune) bool // 是否小写字符
func IsMark(r rune) bool // 是否符号字符
func IsNumber(r rune) bool // 是否数字字符,比如罗马数字Ⅷ也是数字字符
func IsOneOf(ranges []*RangeTable, r rune) b ool // 是否是 RangeTable 中的一个
func IsPrint(r rune) bool // 是否可打印字符
func IsPunct(r rune) bool // 是否标点符号
func IsSpace(r rune) bool // 是否空格
func IsSymbol(r rune) bool // 是否符号字符
func IsTitle(r rune) bool // 是否 title case
func IsUpper(r rune) bool // 是否大写字符
func Is(rangeTab *RangeTable, r rune) bool // r 是否为 rangeTab 类型的字符
func In(r rune, ranges ...*RangeTable) bool // r 是否为 ranges 中任意一个类型的字符
5.2 utf8 包
utf8 里面的函数就有一些字节和字符的转换。
判断是否符合 utf8 编码的函数:
func Valid(p []byte) bool
func ValidRune(r rune) bool
func ValidString(s string) bool
判断 rune 所占字节数:
func RuneLen(r rune) int
判断字节串或者字符串的 rune 数:
func RuneCount(p []byte) int
func RuneCountInString(s string) (n int)
编码和解码到 rune:
func EncodeRune(p []byte, r rune) int
func DecodeRune(p []byte) (r rune, size int)
func DecodeRuneInString(s string) (r rune, size int)
func DecodeLastRune(p []byte) (r rune, size int)
func DecodeLastRuneInString(s string) (r rune, size int)
是否为完整 rune:
func FullRune(p []byte) bool
func FullRuneInString(s string) bool
是否为 rune 第一个字节:
func RuneStart(b byte) bool
示例:
word:=[]byte("界")
fmt.Println(utf8.Valid(word[:2]))
fmt.Println(utf8.ValidRune('界'))
fmt.Println(utf8.ValidString("世界"))
fmt.Println(utf8.RuneLen('界'))
fmt.Println(utf8.RuneCount(word))
fmt.Println(utf8.RuneCountInString("世界"))
p:=make([]byte,3)
utf8.EncodeRune(p,'好')
fmt.Println(p)
fmt.Println(utf8.DecodeRune(p))
fmt.Println(utf8.DecodeRuneInString("你好"))
fmt.Println(utf8.DecodeLastRune([]byte("你好")))
fmt.Println(utf8.DecodeLastRuneInString("你好"))
fmt.Println(utf8.FullRune(word[:2]))
fmt.Println(utf8.FullRuneInString("你好"))
fmt.Println(utf8.RuneStart(word[1]))
fmt.Println(utf8.RuneStart(word[0]))
运行结果:
false
true
true
3
1
2
[229 165 189]
22909 3
20320 3
22909 3
22909 3
false
true
false
true
5.3 utf16 包
utf16 的包的函数就比较少了。
将 uint16 和 rune 进行转换
func Encode(s []rune) []uint16
func EncodeRune(r rune) (r1, r2 rune)
func Decode(s []uint16) []rune
func DecodeRune(r1, r2 rune) rune
func IsSurrogate(r rune) bool // 是否为有效代理对
unicode 有个基本字符平面和增补平面的概念,基本字符平面只有 65535 个字符,增补平面(有 16 个)加上去就能表示 1114112 个字符。 utf16 就是严格实现了 unicode 的这种编码规范。
而基本字符和增补平面字符就是一个代理对(Surrogate Pair)。一个代理对可以和一个 rune 进行转换。
示例:
words :=[]rune{'𝓐','𝓑'}
u16:=utf16.Encode(words)
fmt.Println(u16)
fmt.Println(utf16.Decode(u16))
r1,r2:=utf16.EncodeRune('𝓐')
fmt.Println(r1,r2)
fmt.Println(utf16.DecodeRune(r1,r2))
fmt.Println(utf16.IsSurrogate(r1))
fmt.Println(utf16.IsSurrogate(r2))
fmt.Println(utf16.IsSurrogate(1234))
输出结果:
[55349 56528 55349 56529]
[120016 120017]
55349 56528
120016
true
true
false
6 字符串拼接方法汇总
最后更新于