13|基本数据类型:为什么Go要原生支持字符串类型?
该思维导图由 AI 生成,仅供参考
原生支持字符串有什么好处?
- 深入了解
- 翻译
- 解释
- 总结
Go语言原生支持字符串类型,这一设计带来了多重好处。首先,字符串类型的数据是不可变的,提高了字符串的并发安全性和存储利用率。其次,Go语言中字符串没有结尾'\0',获取长度的时间复杂度是常数时间,消除了获取字符串长度的开销。此外,Go语言原生支持“所见即所得”的原始字符串,大大降低构造多行字符串时的心智负担。最后,Go语言对非ASCII字符提供原生支持,消除了源码在不同环境下显示乱码的可能。 文章还介绍了Go字符串的组成,从字节视角和字符序列视角分别进行了解释。通过这些特点,读者可以快速了解Go语言原生支持字符串类型的优势和机制。 此外,文章还介绍了rune类型与字符字面值的关系,以及字符串字面值的构成方式。另外,文章还详细介绍了UTF-8编码方案,以及Go语言中字符串类型的内部表示方式。通过对字符串类型的实现原理的解析,读者可以更好地理解字符串类型的性质和操作。 总的来说,本文通过深入浅出的方式介绍了Go语言中字符串类型的特点、构成和内部表示,为读者提供了全面的了解和认识。文章还介绍了Go字符串的常见操作,包括下标操作、字符迭代、字符串连接、字符串比较和字符串转换。通过这些操作的介绍,读者可以更好地掌握Go语言中字符串类型的使用方法和注意事项。 总的来说,本文通过深入浅出的方式介绍了Go语言中字符串类型的特点、构成和内部表示,为读者提供了全面的了解和认识。
《Tony Bai · Go 语言第一课》,新⼈⾸单¥59
全部留言(44)
- 最新
- 精选
- Darrenfunc plusConcat(n int, str string) string { // +号拼接 } func sprintfConcat(n int, str string) string { //fmt.Sprintf拼接 } func builderConcat(n int, str string) string { var builder strings.Builder for i := 0; i < n; i++ { builder.WriteString(str) } return builder.String() } func bufferConcat(n int, s string) string { buf := new(bytes.Buffer) for i := 0; i < n; i++ { buf.WriteString(s) } return buf.String() } func byteConcat(n int, str string) string { buf := make([]byte, 0) for i := 0; i < n; i++ { buf = append(buf, str...) } return string(buf) } func preByteConcat(n int, str string) string { buf := make([]byte, 0, n*len(str)) for i := 0; i < n; i++ { buf = append(buf, str...) } return string(buf) } func builderGrowConcat(n int, str string) string { var builder strings.Builder builder.Grow(n * len(str)) // 与builderConcat相同 } func bufferGrowConcat(n int, s string) string { buf := new(bytes.Buffer) buf.Grow(n * len(s)) // 与bufferConcat相同 } benchmem测试: 24 47124538 ns/op 530996721 B/op 10011 allocs/op 13 81526461 ns/op 834307836 B/op 37463 allocs/op 13263 90613 ns/op 505841 B/op 24 allocs/op 12730 94213 ns/op 423537 B/op 13 allocs/op 12992 94185 ns/op 612338 B/op 25 allocs/op 23606 50058 ns/op 212992 B/op 2 allocs/op 24326 49660 ns/op 106496 B/op 1 allocs/op 16762 71860 ns/op 212993 B/op 2 allocs/op 如果能知道拼接字符串的个数,那么使用bytes.Buffer和strings.Builder的Grows申请空间后,性能是最好的;如果不能确定长度,那么bytes.Buffer和strings.Builder也比“+”和fmt.Sprintf性能好很多。 bytes.Buffer与strings.Builder,strings.Builder更合适,因为bytes.Buffer 转化为字符串时重新申请了一块空间,存放生成的字符串变量,而 strings.Builder 直接将底层的 []byte 转换成了字符串类型返回了回来。 bytes.Buffer 的注释中还特意提到了: To build strings more efficiently, see the strings.Builder type.
作者回复: 简直就是标准答案👍
2021-11-112145 - Vfeelitrune 是 int32 别名 Unicode编码没有负的吧 为何不是 uint32的别名?
作者回复: 好问题。这个问题我也曾想过,官方没有答案。但从社区给出的观点来看,主要考虑两点:1.int32足够表示unicode所有码点 2. int32可以为负数,便于检测溢出(overflow)或其他基于int32的计算错误。
2022-01-0923 - 大大大大大泽有个问题不太懂。。。UTF-8 编码使用的字节数量从 1 个到 4 个不等。那么如何确定几个字节确定一个字符呢? 比如说 中国人 是 \xe4\xb8\xad\xe5\x9b\xbd\xe4\xba\xba,3个字节确定一个字符,分配为3 3 3。为什么不会分割成1 1 2 2 3
作者回复: 摘自网络:) unicode 符号范围 | utf-8 编码方式 00000000 ~ 0000007F | 0xxxxxxx 00000080 ~ 000007FF | 110xxxxx 10xxxxxx 00000800 ~ 0000FFFF | 1110xxxx 10xxxxxx 10xxxxxx 00010000 ~ 0010FFFF | 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx 总结下来,针对UTF8,编码规则其实只有两条: 1)单字节规则: 对于 单字节 的符号,字节的第一位(最高位)设为 0,后面 7 位为这个符号的 unicode 码。 2)n字节规则: 对于 n 字节的符号(n>1),第一个字节的前 n 位都设为 1,第 n+1 位设为 0,后面字节的前两位一律设为 10。剩下的没有提及的二进制位,全部为这个符号的 unicode 码。
2022-03-01317 - 你好呀, 朋友.是不是可以理解成[]rune里存的是Unicode码点或者说UTF-32编码,而[]byte和string存的是UTF-8编码
作者回复: 一个rune存储一个unicode码点或utf-32的四字节编码;从字节视角,string对应的底层存储存放的是utf8编码。
2021-11-1212 - qinsi那么问题来了,raw string里要怎么使用反引号?
作者回复: 反引号是唯一的“漏网之鱼”:)。
2021-11-11212 - William Ning现代CPU计算时一次都能装载多个字节(如32位计算机一次装载4字节),多字节的数值在内存中高低位的排列方式会影响所表示的数值,以数值0x01020304为例,在内存中用4个字节存储,4个字节的内容分别是0x01、0x02、0x03、0x04。根据字节高低位排序方式的不同,可以分为:大端字节序(big endian)和小端字节序(little endian)。 大端字节序 大端字节序是指一个整数的高位字节(如上例中的0x01)存储在内存的低地址处,高字节在前。 C语言数组存储例: 0x01020304 bufe[0] = 0x01; bufe[1] = 0x02; bufe[2] = 0x03; bufe[3] = 0x04; 小端字节序 小端字节序把数值的低位字节(如上例中的0x04)存储在内存的低地址处,低字节在前。PC计算机和单片机常见都是小端字节序。 C语言数组存储例: 0x01020304 bufe[0] = 0x04; bufe[1] = 0x03; bufe[2] = 0x02; bufe[3] = 0x01; 常见的memcpy函数复制float字节到数组中,数组中的float就是小端字节序 memcpy(&listDataSoft[0] ,&f,sizeof(float)); 主机字节序 现代计算机大多采用小端字节序,所以小端字节序又叫主机字节序。 网络字节序 不同的计算机可能会采用不同的字节序,甚至同一计算机上不同进程会采用不同的字节序,如JAVA虚拟机采用大端字节序,可能和采用小端字节序计算机上的其他进程不同。所以在网络通信(或进程间通信)时,如果都按自己存储的顺序收发数据,有可能会出现一些误解,为了避免这个问题,约定数据在不同计算机之间传递时都采用大端字节序,也叫作网络字节序。通信时,发送方需要把数据转换成网络字节序(大端字节序)之后再发送,接收方再把网络字节序转成自己的字节序。
作者回复: 👍
2022-03-0110 - 布凡strings.Builder的效率要比+/+=的效率高 因为string.Builder 是先将第一个字符串的地址取出来,然后将builder的字符串拼接到后面, func (b *Builder) copyCheck() { if b.addr == nil { // This hack works around a failing of Go's escape analysis // that was causing b to escape and be heap allocated. // See issue 23382. // TODO: once issue 7921 is fixed, this should be reverted to // just "b.addr = b". b.addr = (*Builder)(noescape(unsafe.Pointer(b))) } else if b.addr != b { panic("strings: illegal use of non-zero Builder copied by value") } } // String returns the accumulated string. func (b *Builder) String() string { return *(*string)(unsafe.Pointer(&b.buf)) } +/+=是将两个字符串连接后分配一个新的空间,当连接字符串的数量少时,两者没有什么区别,但是当连接字符串多时,Builder的效率要比+/+=的效率高很多。如有理解不正确的地方希望老师同学指正!(*^_^*)
作者回复: 点个赞。
2021-11-108 - 多选参数老师讲编码是我见过讲的最清晰的。有个小问题,就是 Go 中的 string 在内存中存的应该还是 UTF-8 编码之后的数据?而 rune 的方式是在我们使用的时候 Go 源码隐式的进行了转换?
作者回复: 没错,string 在内存中存的就是utf8编码后的字节。像for range这种循环得到的rune,是Go编译器在编译时做的替换。
2021-11-207 - 多选参数老师,关于 utf-8 不考虑字节序的问题。能否这么理解,utf-8 的一个字符是由 3 个字节逐个字节进行编码比较决定的,比如第一个字节编码的值在这个值之间,那肯定采用的是单字节编码,第二个字节编码的值在这之间,那肯定是双字节编码,而 utf-32 需要 4 字节一起考虑?那么,一旦 4 个字节一起考虑了的话,就需要涉及到这 4 个字节是大端序还是小端序?
作者回复: 对的。
2021-11-206 - Bynow& 和 unsafe.Pointer 有什么区别?
作者回复: 以a:=1; var p = &a为例,&是取地址操作符。unsafe.Pointer是go语言中的通用指针类型,任何指针都可以转型为unsafe.Pointer类型,反之unsafe.Pointer也可以转回任意指针类型。例子: i := 11 var p = unsafe.Pointer(&i) // int指针 -> unsafe.Pointer pi := (*int)(p) // unsafe.Pointer -> int指针
2021-11-255