Golang string 源码

它是一种基本类型,并且是一个不可改变的UTF-8字符序列

回过来看 GO 里面的字符串,字符也是根据英文和中文不一样,一个字符所占用的字节数也是不一样的,大体分为如下 2 种

  • 英文的字符,按照ASCII 码来算,占用 1 个字节
  • 其他的字符,包括中文字符在内的,根据不同字符,占用字节数是 2 -- 4个字节

字符串的数据结构是啥样的?

说到字符串的数据结构,我们先来看看 GO 里面的字符串,是在哪个包里面

不难发现,我们随便在 GOLANG 里面 定义个string 变量,就能够知道 string 类型是在哪个包里面,例如

var name  string

GO 里面的字符串对应的包是 builtin

1 // string is the set of all strings of 8-bit bytes, conventionally but not
2 // necessarily representing UTF-8-encoded text. A string may be empty, but
3 // not nil. Values of string type are immutable.
4 type string string
  • 字符串这个类型,是所有8-bits 字符串的集合,通常但不一定表示utf -8编码的文本
  • 字符串可以为空,但不能为 nil ,此处的字符串为空是 ""
  • 字符串类型的值是不可变的

另外,找到 string 在 GO 里面对应的源码文件中src/runtime/string.go , 有这么一个结构体,只提供给包内使用,我们可以看到string的数据结构 stringStruct 是这个样子的

 

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

整个结构体,就 2 个成员,string 类型是不是很简单呢

  • str

是对应到字符串的首地址

  • len

这个就是不难理解,是字符串的长度

那么,在创建一个字符串变量的时候,stringStruct 是在哪里使用到的呢?

我们看看 GO string.go 文件中的源码

 1 //go:nosplit
 2 func gostringnocopy(str *byte) string {
 3    ss := stringStruct{str: unsafe.Pointer(str), len: findnull(str)}  // 构建成 stringStruct
 4    s := *(*string)(unsafe.Pointer(&ss))  // 强转成 string
 5    return s
 6 }
 7 //go:nosplit
 8 func findnull(s *byte) int {
 9    if s == nil {
10       return 0
11    }
12 
13    // Avoid IndexByteString on Plan 9 because it uses SSE instructions
14    // on x86 machines, and those are classified as floating point instructions,
15    // which are illegal in a note handler.
16    if GOOS == "plan9" {
17       p := (*[maxAlloc/2 - 1]byte)(unsafe.Pointer(s))
18       l := 0
19       for p[l] != 0 {
20          l++
21       }
22       return l
23    }
24 
25    // pageSize is the unit we scan at a time looking for NULL.
26    // It must be the minimum page size for any architecture Go
27    // runs on. It's okay (just a minor performance loss) if the
28    // actual system page size is larger than this value.
29    const pageSize = 4096
30 
31    offset := 0
32    ptr := unsafe.Pointer(s)
33    // IndexByteString uses wide reads, so we need to be careful
34    // with page boundaries. Call IndexByteString on
35    // [ptr, endOfPage) interval.
36    safeLen := int(pageSize - uintptr(ptr)%pageSize)
37 
38    for {
39       t := *(*string)(unsafe.Pointer(&stringStruct{ptr, safeLen}))
40       // Check one page at a time.
41       if i := bytealg.IndexByteString(t, 0); i != -1 {
42          return offset + i
43       }
44       // Move to next page
45       ptr = unsafe.Pointer(uintptr(ptr) + uintptr(safeLen))
46       offset += safeLen
47       safeLen = pageSize
48    }
49 }

简单分为 2 步:

  • 先将字符数据构建程 stringStruct
  • 再通过 gostringnocopy 函数 转换成 string

字符串中的数据为什么不能被修改呢?

从上述官方说明中,我们可以看到,字符串类型的值是不可变的

可是这是为啥呢?

我们以前在写C/C++的时候,为啥可以开辟空间存放多个字符,并且还可以修改其中的某些字符呢?

可是在 C/C++里面的字面量也是不可以改变的

GO 里面的 string 类型,是不是也和 字面量一样的呢?我们来看看吧

字符串类型,本身也是拥有对应的内存空间的,那么修改string类型的值应该是要支持的。

可是,XDM 在 Go 的实现中,string 类型是不包含内存空间,只有一个内存的指针,这里就有点想C/C++里面的案例:

char * str = "XMTONG"

上述的 str是绝对不能做修改的,str只是作为可读,不能写的。

在GO 里面的字符串,就与上述类似。这样做的好处是 string 变得非常轻量,可以很方便的进行传递而不用担心内存拷贝(这也避免了内存带来的诸多问题)

GO 中的 string类型一般是指向字符串字面量,字符串字面量存储位置是在虚拟内存分区的只读段上面,而不是堆或栈上。因此,GO 的 string 类型不可修改的。

posted @ 2021-10-31 17:51  林锅  阅读(528)  评论(0编辑  收藏  举报