0%

《Go语言学习笔记》读书笔记(7)数据结构

数据结构

字符串

字符串是不可变字节(byte)序列,其本身是一个符合结构.

1
2
3
4
type stringStruct struct {
str unsafe.Pointer
len int
}

头部指针指向字节数组,但没有NULL结尾。默认以UTF-8编码存储Unicode字符,字面量里允许使用十六进制、八进制和UTF编码格式。

内置函数len返回字节数组长度,cap不接受字符串类型参数。

字符串默认值不是nil, 而是"".

使用for遍历字符串是,分byterune两种方式。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
func main () {	// byte
s:="李建聪"
for i := 0; i < len(s); i++ {
fmt.Printf("%d: [%c]\n", i, s[i])
}


for i, c := range s { // rune: 返回数组索引号,以及Unicode字符
fmt.Printf("%d: [%c]\n", i, c)
}
}
///0: [æ]
///1: [
///2: []
///3: [å]
///4: [»]
///5: [º]
///6: [è]
///7: []
///8: [ª]
///0: [李]
///3: [建]
///6: [聪]

要修改字符串,须将其转换为可变类型([]rune[]byte), 待完成后再转换回来。但不管怎么转换,都须重新分配内存,并复制数据。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
func pp(format string, ptr interface{}) {
p := reflect.ValueOf(ptr).Pointer()
h := (*uintptr)(unsafe.Pointer(p))
fmt.Printf(format, *h)
}

func main () { // byte
s:="hello, world!"
pp("s: %x\n", &s)

bs := []byte(s)
s2 := string(bs)

pp("string to []byte, bs:%x\n", &bs)
pp("[]byte to string, s2:%x\n", &s2)

rs := []rune(s)
s3 := string(rs)

pp("string to []byte, rs:%x\n", &rs)
pp("[]byte to string, s3:%x\n", &s3)
}
/// 输出:
/// s: 2bcc91
/// string to []byte, bs:c0000a20a0
/// []byte to string, s2:c0000a20b0
/// string to []byte, rs:c0000b80c0
/// []byte to string, s3:c0000a20d0

编译器会为了某些场合进行专门优化,避免额外分配和复制操作:

  • []byte转换为string key, 去map[string]查询的时候。
  • string转换为[]byte, 进行for range迭代时,直接取字节赋值给局部变量。

除了类型转换外,动态构建字符串也容易造成性能问题。
用加法操作符拼接字符串时,每次都须重新分配内存。如此,在构建超大字符串时,性能就显得极差。
改进思路时预分配i足够大的空间。常用方法是用string.Join函数,他会统计所有参数长度,并一次性完成内存分配操作。
另外
utf8.ValidString(s) 返回s是不是一个有效的字符串
utf8.RuneCountInString(s) 替代len返回unicode的字符数量