4.1.字符串

字符串

1. 基础知识

1.1. 编码方式

Go 语言默认编码方式是 UTF-8。

8 个 bit 表示一个 byte (字节) :

  • 0000 0000 表示 数字 0
  • 1111 1111 表示数字 255。

unicode 是定长编码,表示如下

字符 编码
e 0000 0000 0000 0000 0000 0000 0110 0101
0000 0000 0000 0000 0100 1110 0001 0111

由于 unicode 占用较多空间,于是有了 变长编码,这就是 UTF-8

编号 编码模板 说明
[0,127] 0??? ???? 表示 1 个 byte
[128,2047] 110? ???? 10?? ???? 表示 2 个 byte
[2048,65535] 1110 ???? 10?? ???? 10?? ???? 表示 3 个 byte

通过去掉标识位,获得二进制

字符 十进制 二进制 UTF-8 编码
e 101 0110 0101 0110 0101
19990 0100 1110 0001 0110 1110 0100 1011 1000 1001 0110

1.2. 不可修改

1
2
3
s := "left foot"
t := s
s += ", right foot"

这并不会导致原始的字符串值被改变,但是变量s将因为+=语句持有一个新的字符串值,但是t依然是包含原先的字符串值。

字符串是不可修改的,编译器会将字符串内容分配到只读内存段。需要修改则重新赋值,指针没有修改原来的内存,而是创建了新的内存。

1
2
// Bad
s[0]='a'
1
2
// Good
s = "abc"

2. 使用案例

2.1. 计算字符串长度

len() 函数用于求长度,字符串是以 UTF-8 格式进行存储的,len() 获取的是字节数组长度

遇到中文有以下几种方法处理

1
2
3
utf8.RuneCountInString(str)   //  按 utf8 编码

len([]rune("字符串"))					

2.2. 字符串遍历

1
2
3
for i,v := range []rune(str) {
  fmt.Printf("(%d,%c)\n",idx, val)
}

2.3.字符串拼接

字符串底层是不可修改的数组,若使用 + 拼接则会创建新的字符串,使用以下两种方式更节省内存

1
2
3
4
5
6
func TestStringsBuuilder(t *testing.T) {
	var a strings.Builder
	a.WriteString("hello,")
	a.WriteString("world")
	fmt.Printf(a.String())
}
1
2
3
4
5
func TestBytesBuffer(t *testing.T)  {
	var buf bytes.Buffer
	buf.WriteString("hello,")
	fmt.Printf(buf.String())
}

2.4. 字符串和数字的转换

将一个整数转为字符串,一种方法是用fmt.Sprintf返回一个格式化的字符串;另一个方法是用strconv.Itoa(“整数到ASCII”):

fmt.Sprintf 会用到反射。

strconv.Itoa 效率更高。

1
2
3
x := 123
y := fmt.Sprintf("%d", x)
fmt.Println(y, strconv.Itoa(x)) // "123 123"

2.5. 更多

检查字符串前缀

1
2
3
func HasPrefix(s, prefix string) bool {
    return len(s) >= len(prefix) && s[:len(prefix)] == prefix
}

后缀

1
2
3
func HasSuffix(s, suffix string) bool {
    return len(s) >= len(suffix) && s[len(s)-len(suffix):] == suffix
}

包含子串测试

1
2
3
4
5
6
7
8
func Contains(s, substr string) bool {
    for i := 0; i < len(s); i++ {
        if HasPrefix(s[i:], substr) {
            return true
        }
    }
    return false
}

使用 utf8 包

1
2
3
4
5
import "unicode/utf8"

s := "Hello, 世界"
fmt.Println(len(s))                    // "13" 字节长度
fmt.Println(utf8.RuneCountInString(s)) // "9"  字符长度

3. 注意

containers 和 正则表达式 哪个性能高??

标准库中有四个包对字符串处理尤为重要:bytes、strings、strconv和unicode包。strings包提供了许多如字符串的查询、替换、比较、截断、拆分和合并等功能。

bytes包也提供了很多类似功能的函数,但是针对和字符串有着相同结构的[]byte类型。因为字符串是只读的,因此逐步构建字符串会导致很多分配和复制。在这种情况下,使用bytes.Buffer类型将会更有效

strconv包提供了布尔型、整型数、浮点数和对应字符串的相互转换,还提供了双引号转义相关的转换。

path和path/filepath包提供了关于文件路径名更一般的函数操作。使用斜杠分隔路径可以在任何操作系统上工作。

strings 包的函数

1
2
3
4
5
6
func Contains(s, substr string) bool
func Count(s, sep string) int
func Fields(s string) []string
func HasPrefix(s, prefix string) bool
func Index(s, sep string) int
func Join(a []string, sep string) string

bytes 包

1
2
3
4
5
6
func Contains(b, subslice []byte) bool
func Count(s, sep []byte) int
func Fields(s []byte) [][]byte
func HasPrefix(s, prefix []byte) bool
func Index(s, sep []byte) int
func Join(s [][]byte, sep []byte) []byte

当向bytes.Buffer添加任意字符的UTF8编码时,最好使用bytes.Buffer的WriteRune方法,但是WriteByte方法对于写入类似’[‘和’]‘等ASCII字符则会更加有效。

写入时 , 尽量避免 强制类型转换, 这会拷贝数据, 浪费内存

4. 了解源码

Go语言字符串的底层结构在reflect.StringHeader中定义。

1
2
3
4
type StringHeader struct {
    Data uintptr  // 起始地址
    Len  int			// byte 长度
}

image-20220401191706013

Licensed under CC BY-NC-SA 4.0
本文阅读量 次, 总访问量 ,总访客数
Built with Hugo .   Theme Stack designed by Jimmy