String的底层实现(1)

2年前 (2022) 程序员胖胖胖虎阿
304 0 0

String的底层实现(1)

原理

字符串可以分为两种:

  1. 在编译时确定其长度,值不可以修改
  2. 不在编译时确定其长度,在运行时可以动态修改其长度,值可以修改

计算机中使用二进制来表现数据,字符串在计算机中也是以二进制进行表示。那么问题来了:怎么确定哪段二进制表示的是字符串而不是其他类型的数据?

字符串的首地址我们可以很容易得到,重点在于怎么确定字符串的结束位置。

所以我们需要拆分二进制,可以通过分隔符来拆分(遇到分隔符表示从zi),也可以通过实现确定字符串的长度来拆分。

  1. C语言通过字符数组的最后一位为"\0"作为字符串的定界符
  2. Go使用长度来记录字符串边界,字符的长度就是数组 大小

使用分隔符进行拆分需要N+1的字符数组来表示一个长度为N的字符串,并且字符数组最后一位总是空字符"\0"

String的底层实现(1)

string的概念

// string is the set of all strings of 8-bit bytes, conventionally but not
// necessarily representing UTF-8-encoded text. A string may be empty, but
// not nil. Values of string type are immutable.
type string string
  1. string 是所有 8 位字节字符串的集合,通常但不一定代表 UTF-8 编码的文本。
  2. 字符串可以为空(长度为 0),但不会是 nil。
  3. 字符串类型的值是不可变的。

Go中的字符串实际上是一个只读的字节切片[]byte。这回答了"当我在位置n索引 Go 字符串时,为什么我没有得到第 n 个字符?"这个问题:字符串是从字节构建的,因此对它们进行索引会产生字节,而不是字符。字符串甚至可能不包含字符。

字符串常量会在编译期分配到只读段,对应数据地址不可写入,相同的字符串常量不会重复存储

如果是代码中存在的字符串,编译器会将其标记成只读数据 SRODATA

package main

func main() {
    s := "golang"
    println(s)
}
go.string."golang" SRODATA dupok size=6
        0x0000 67 6f 6c 61 6e 67                                golang

stringStruct

type stringStruct struct {
    // str是字符串的首地址
    str unsafe.Pointer
    // len是字符串的长度
    len int
}

String的底层实现(1)

为什么是只读的呢?

string 通常指向字符串字面量,字面量存储的位置是只读段,并不是堆或栈上,所以 string 不能被修改。

使用长度来记录字符串的好处是什么?

如果使用分隔符则获取字符串的长度必须遍历整个字符串,时间复杂度为O(N);

因为stringStruct中记录了字符串的长度,所以时间复杂度为O(1)。

解析

Go中的字符串可以使用反引号(``)和双引号("")。

s := `Golang`
s := "Golang"

双引号声明与其他语言一致,它只能用于单行字符串的初始化,如果字符串内部出现双引号,需要使用 \ 符号避免编译器的解析错误,而反引号声明的字符串可以摆脱单行的限制。当使用反引号时,因为双引号不再负责标记字符串的开始和结束,我们可以在字符串内部直接使用 "

func (s *scanner) stdString() {
    ok := true
    s.nextch()

    for {
        if s.ch == '"' {
            s.nextch()
            break
        }
        if s.ch == '\\' {
            s.nextch()
            if !s.escape('"') {
                ok = false
            }
            continue
        }
        if s.ch == '\n' {
            s.errorf("newline in string")
            ok = false
            break
        }
        if s.ch < 0 {
            s.errorAtf(0, "string not terminated")
            ok = false
            break
        }
        s.nextch()
    }

    s.setLit(StringLit, ok)
}
  1. 标准字符串使用双引号表示开头和结尾
  2. 标准字符串需要使用反斜杠 \ 来逃逸双引号
  3. 标准字符串不能出现隐式换行 \n
func (s *scanner) rawString() {
    ok := true
    s.nextch()

    for {
        if s.ch == '`' {
            s.nextch()
            break
        }
        if s.ch < 0 {
            s.errorAtf(0, "string not terminated")
            ok = false
            break
        }
        s.nextch()
    }
    // We leave CRs in the string since they are part of the
    // literal (even though they are not part of the literal
    // value).

    s.setLit(StringLit, ok)
}

rawString会将非反引号的所有字符都划分到当前字符串的范围中,所以我们可以使用它支持复杂的多行字符串:

版权声明:程序员胖胖胖虎阿 发表于 2022年9月13日 上午12:24。
转载请注明:String的底层实现(1) | 胖虎的工具箱-编程导航

相关文章

暂无评论

暂无评论...