聊聊Go语言中的字符串

写在文章开头

字符类型是开发中最常用到的类型,不同的语言有着不同的实现,这篇文章我们来聊聊go语言的字符串类型,本文会从go语言底层实现的角度分析字符串的设计与实现,相信读者通过对本文的阅读会对go语言中字符的实现有着不错的理解和掌握。

Hi,我是 sharkChili ,是个不断在硬核技术上作死的 java coder ,是 CSDN的博客专家 ,也是开源项目 Java Guide 的维护者之一,熟悉 Java 也会一点 Go ,偶尔也会在 C源码 边缘徘徊。写过很多有意思的技术博客,也还在研究并输出技术的路上,希望我的文章对你有帮助,非常欢迎你关注我的公众号: 写代码的SharkChili

因为近期收到很多读者的私信,所以也专门创建了一个交流群,感兴趣的读者可以通过上方的公众号获取笔者的联系方式完成好友添加,点击备注 "加群" 即可和笔者和笔者的朋友们进行深入交流。

详解go语言中的字符串

打印不同字符串的长度

我们通过unsafe.Sizeof打印这几个变量类型的长度:

css 复制代码
 s := "hello"
 fmt.Println(unsafe.Sizeof(s))

 s1 := "你好"
 fmt.Println(unsafe.Sizeof(s1))
 
 s2 := "你好 sharkChili"
 fmt.Println(unsafe.Sizeof(s2))
 

可以看到任何的字符串打印出来的长度都是16:

复制代码
16
16
16

go语言字符串底层分析

查看源代码文件string.go,可以看到go语言对于字符串类型的封装,它用一个指针变量str记录字符串的指针,它是由一个指针和一个整型变量len构成的:

go 复制代码
type stringStruct struct {
 //指向字符串的指针
 str unsafe.Pointer
 //记录当前字符串的长度
 len int
}

任何指针长度都是8字节,加上在64位的操作系统上,整型默认也是8字节,这也是为什么我们打印的字符串类型长度都是16的原因:

go语言对字符串的优化

所以我们如果需要知道字符串的长度,则需要使用len方法:

css 复制代码
func main() {

 s := "hello"
 fmt.Println(len(s))

 s1 := "你好"
 fmt.Println(len(s1))

 s2 := "你好 sharkChili"
 fmt.Println(len(s2))

}

对应的输出结果如下,可以看到通过计算,英文一个1个字母1个字节,而中文一个字符3字节,很明显go语言底层对于不同类型的文字的内存空间做了一定的优化处理:

复制代码
5
6 
17

我们都知道Unicode是一种几乎可以涵盖世界上所有字符的字符集,它将所有英文字母排在前128位,go语言对于字符串使用Unicode字符集utf8格式的编码格式,由于前128位都是英文字母,所以采用ASCII编码标识,即英文只需1个字节即可存储,而中文在后续的字符集中,所以需要用3个字节来表示。

内置的字符串解码实现字符串安全迭代

对于go语言的迭代的操作,我们可以使用for-range语法进行格式化输出,对应代码示例如下:

go 复制代码
func main() {

 s := "你好 sharkChili"
 for _, i := range s {
  fmt.Printf("%c", i)
 }

}

输出结果也正如预期:

复制代码
你好 sharkChili

因为我们采用utf8编码,所以go语言通过utf8.godecoderuneencoderune确保我们的在使用前得到正确的编码和解码的字符串。 以上我们迭代的例子,在迭代时go语言就会走到decoderune函数,初次迭代时会从字符串数组索引0"你"这个字符串开始,判断该数组位置字符大小,如果在0800-FFFF之间,说明底层要截取3个字节才能正确拿到该元素,decoderune就会截取3字节后得到一个字符(用go的术语叫rune),并将pos加上3确保偏移到下个字节的起始位置:

对应的源码如下:

scss 复制代码
//传入字符串和当前字符串索引位置,返回字符r和下一个字符的起始位置
func decoderune(s string, k int) (r rune, pos int) {
 pos = k

 if k >= len(s) {
  return runeError, k + 1
 }

//截取当前索引位置后的字符
 s = s[k:]
 //获取截取的第一个位置的字符串的字节数,截取相应字节后计算偏移量直接返回
 switch {
 //2字节字符的截取和偏移计算
 case t2 <= s[0] && s[0] < t3:
  // 0080-07FF two byte sequence
  if len(s) > 1 && (locb <= s[1] && s[1] <= hicb) {
   r = rune(s[0]&mask2)<<6 | rune(s[1]&maskx)
   pos += 2
   if rune1Max < r {
    return
   }
  }
 //3字节字符的截取和偏移计算
 case t3 <= s[0] && s[0] < t4:
  // 0800-FFFF three byte sequence
  if len(s) > 2 && (locb <= s[1] && s[1] <= hicb) && (locb <= s[2] && s[2] <= hicb) {
   r = rune(s[0]&mask3)<<12 | rune(s[1]&maskx)<<6 | rune(s[2]&maskx)
   pos += 3
   if rune2Max < r && !(surrogateMin <= r && r <= surrogateMax) {
    return
   }
  }
 //4字节字符的截取和偏移计算
 case t4 <= s[0] && s[0] < t5:
  // 10000-1FFFFF four byte sequence
  if len(s) > 3 && (locb <= s[1] && s[1] <= hicb) && (locb <= s[2] && s[2] <= hicb) && (locb <= s[3] && s[3] <= hicb) {
   r = rune(s[0]&mask4)<<18 | rune(s[1]&maskx)<<12 | rune(s[2]&maskx)<<6 | rune(s[3]&maskx)
   pos += 4
   if rune3Max < r && r <= maxRune {
    return
   }
  }
 }

 return runeError, k + 1
}

小结

本文通过字符串的底层结构和内存分配及迭代机制上深入剖析了go语言对于字符串类型的设计和实现,希望对你有帮助。

我是 sharkchiliCSDN Java 领域博客专家开源项目---JavaGuide contributor ,我想写一些有意思的东西,希望对你有帮助,如果你想实时收到我写的硬核的文章也欢迎你关注我的公众号: 写代码的SharkChili 。 因为近期收到很多读者的私信,所以也专门创建了一个交流群,感兴趣的读者可以通过上方的公众号获取笔者的联系方式完成好友添加,点击备注 "加群" 即可和笔者和笔者的朋友们进行深入交流。

本文使用 markdown.com.cn 排版

相关推荐
大学生资源网13 分钟前
基于springboot的万亩助农网站的设计与实现源代码(源码+文档)
java·spring boot·后端·mysql·毕业设计·源码
苏三的开发日记22 分钟前
linux端进行kafka集群服务的搭建
后端
苏三的开发日记40 分钟前
windows系统搭建kafka环境
后端
爬山算法1 小时前
Netty(19)Netty的性能优化手段有哪些?
java·后端
Tony Bai1 小时前
Cloudflare 2025 年度报告发布——Go 语言再次“屠榜”API 领域,AI 流量激增!
开发语言·人工智能·后端·golang
想用offer打牌1 小时前
虚拟内存与寻址方式解析(面试版)
java·后端·面试·系统架构
無量1 小时前
AQS抽象队列同步器原理与应用
后端
9号达人2 小时前
支付成功订单却没了?MyBatis连接池的坑我踩了
java·后端·面试
用户497357337982 小时前
【轻松掌握通信协议】C#的通信过程与协议实操 | 2024全新
后端
草莓熊Lotso2 小时前
C++11 核心精髓:类新功能、lambda与包装器实战
开发语言·c++·人工智能·经验分享·后端·nginx·asp.net