1、字符串的存储原理

image.png
string 数据结构:源码包src/runtime/string.go:stringStruct定义了string的数据结构:

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

其数据结构很简单:

  • stringStruct.str:字符串的首地址;
  • stringStruct.len:字符串的长度;

string数据结构跟切片有些类似,只不过切片还有一个表示容量的成员,事实上string和切片,准确的说是byte切片经常发生转换。这个后面再详细介绍。

  1. s4 := "hello"
  2. s5 := s4[:]
  3. s6 := s4[1:]
  4. fmt.Println(&s4, (*reflect.StringHeader)(unsafe.Pointer(&s4)))
  5. fmt.Println(&s5, (*reflect.StringHeader)(unsafe.Pointer(&s5)))
  6. fmt.Println(&s6, (*reflect.StringHeader)(unsafe.Pointer(&s6)))
  1. /*
  2. A string type represents the set of string values. A string value is a (possibly empty) sequence of bytes. Strings are immutable: once created, it is impossible to change the contents of a string. The predeclared string type is string.
  3. The length of a string s (its size in bytes) can be discovered using the built-in function len. The length is a compile-time constant if the string is a constant. A string's bytes can be accessed by integer indices 0 through len(s)-1. It is illegal to take the address of such an element; if s[i] is the i'th byte of a string, &s[i] is invalid.
  4. */

字符串类型表示字符串值的集合。字符串值是一个字节序列(可能为空)。字符串是不可变的:一旦创建,就不可能改变字符串的内容。预先声明的字符串类型是string。

字符串s的长度(以字节为单位的大小)可以使用内置函数len来发现。如果字符串是常量,则长度为编译时常量。字符串的字节可以通过索引0到len(s)-1的整数来访问。取这样一个元素的地址是非法的;如果s[i]是字符串的第i个字节,&s[i]是无效的。

2、字符串和字节串

字节数组,就是一个数组,里面每一个元素都是字符,字符又跟字节划等号。所以字符串和字节数组之间可以相互转化。

  1. // (1) 字符串类型(string) 转为字节串类型([]byte)
  2. var s = "高高"
  3. fmt.Println(s,reflect.TypeOf(s)) // 苑昊 string
  4. var b = []byte(s) // 默认用uft-8进行编码
  5. fmt.Println(b,reflect.TypeOf(b)) // [232 139 145 230 152 138] []uint8
  6. s := "Hello, 世界"
  7. r1 := []byte(s)
  8. r2 := []rune(s)
  9. fmt.Println(r1) // 输出:[72 101 108 108 111 44 32 228 184 150 231 149 140]
  10. fmt.Println(r2) // 输出:[72 101 108 108 111 44 32 19990 30028]
  11. // (2) byte转为string
  12. fmt.Println(string(b))
  13. var data = []byte{121,117,97,110}
  14. fmt.Println(string(data)) // yuan

这里的转化不是将string结构体中指向的byte切片直接做赋值操作,而是通过copy实现的,在数据量比较大时,这里的转化会比较耗费内存空间。

3、字符串的遍历

  1. var s1 = "hello world"
  2. for i := 0; i < len(s1); i++ {
  3. //fmt.Println(s1[i])
  4. fmt.Printf("%d:%c\n",s1[i],s1[i])
  5. }
  6. // 如果是中文呢,可以使用range
  7. s2 := "我爱你中国"
  8. for byte_index, value := range s2 {
  9. fmt.Printf("%d %c\n", byte_index,value)
  10. }
  11. // 可以通过代码 len([]rune(s)) 来获得字符串中字符的数量, 但使用 utf8.RuneCountInString(s) 效率会更高一点.
  12. fmt.Println(len(s2) )
  13. fmt.Println(len([]rune(s2)) )
  14. fmt.Println(utf8.RuneCountInString(s2))

小练习:

  1. // 将字符串 "hello" 转换为 "cello":
  2. s := "hello"
  3. c := []byte(s)
  4. c[0] = 'c'
  5. s2 := string(c) //s2 == "cello"