• Go 字符串类型的实现原理


    Go 字符串类型的实现原理

    Go 字符串性质

    • string 类型的数据是不可变的,提高了字符串的并发安全性和存储利用率
    • 获取长度的时间复杂度是常数时间
    • 原生支持"所见即所得"的原始字符串,大大降低构造多行字符串时的心智负担
    • 对非 ASCII 字符提供原生支持,消除了源码在不同环境下显示乱码的可能

    Go 字符串类型内部表示

    上面提到的 Go 字符串类型的这些优秀的性质,与Go 字符串在编译器和运行时中的内部表示是分不开的。Go 字符串在运行时的内部表示是什么样的呢?在标准库reflect包中,我们可以找到如下代码:

    // StringHeader is the runtime representation of a string.
    // It cannot be used safely or portably and its representation may
    // change in a later release.
    // Moreover, the Data field is not sufficient to guarantee the data
    // it references will not be garbage collected, so programs must keep
    // a separate, correctly typed pointer to the underlying data.
    type StringHeader struct {
    	Data uintptr
    	Len  int
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    我们可以看到,string 类型其实是一个"描述符",它本身并不真正存储字符串数据,而仅是由一个指向底层存储的指针和字符串的长度字段组成的。下面直观地展示了一个 string 类型变量在 Go 内存中的存储:

    image-20220812182940732

    Go 编译器把源码中的 string 类型映射为运行时的一个二元组(Data, Len),真实的字符串值数据就存储在一个被 Data 指向的底层数组中。通过 Data 字段,我们可以得到这个数组的内容,看看下面这段代码:

    func dumpBytesArray(arr []byte) {
    	fmt.Printf("[")
    	for _, b := range arr {
    		fmt.Printf("%c ", b)
    	}
    	fmt.Printf("]\n")
    }
    func displayGroundArray() {
    	var str string = "hello"
    	hdr := (*reflect.StringHeader)(unsafe.Pointer(&str)) // 将string类型变量地址显式转型为reflect.StringHeader
    	p := (*[5]byte)(unsafe.Pointer(hdr.Data))            // 获取Data字段所指向的数组的指针
    	dumpBytesArray((*p)[:])                              // 输出底层数组的内容
    }
    func main() {
    	displayGroundArray()
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16

    这段代码利用了 unsafe.Pointer 的通用指针转型能力,按照 StringHeader 给出的结构内存布局,“顺藤摸瓜”,一步步找到了底层数组的地址,并输出了底层数组内容。

    知道了 string 类型的实现原理后,我们再回头看看 Go 字符串类型性质中"获取长度的时间复杂度是常数时间"那句,就很好理解,之所以是常数时间,那是因为字符串类型中包含了字符串长度信息,当我们用 len 函数获取字符串长度时,len 函数只要简单地将这个信息提取出来就可以了。

    了解了 string 类型的实现原理后,我们还可以得到这样一个结论,那就是我们直接将 string 类型通过函数 / 方法参数传入也不会带来太多的开销。因为传入的仅仅是一个"描述符",而不是真正的字符串数据。

  • 相关阅读:
    EPB功能开发与测试(基于ModelBase实现)
    Java知识点07——输入/输出(File类、IO流、序列化、NIO)
    笔记-pandas读取mysql数据
    Day 51 | 309. 最佳买卖股票时机含冷冻期 & 714. 买卖股票的最佳时机含手续费 & 300. 最长递增子序列
    基于Android一周天气个人穿搭衣物喜好管理系统java
    Https中间人攻击
    《微信小程序-进阶篇》Lin-ui组件库源码分析-Button组件(二)
    NC200369 四舍五入(枚举)
    Ubuntu终端Terminator的安装
    Spark简单介绍,Windows下安装Scala+Hadoop+Spark运行环境,集成到IDEA中
  • 原文地址:https://blog.csdn.net/mall_lucy/article/details/126309710