Golang 字符串

早闻golang是utf-8存储的

utf-8到底是什么,你知道么?过了这么久,我一直不知道什么是utf-8,今天学了一下。

go中如何存储 hello 世界?

str := "hello 世界"

我们可以先使用golang打印看看,当然不是平时那么打印,我们需要用到一些fmt的格式化工具包,我们按照unicode、字符串、二进制、十六进制这样来打印看看。

1
2
3
4
u := []rune(str)
for i := 0; i < len(u); i++ {
 fmt.Printf("%U %s %#b %#x\n", u[i], string(u[i]), u[i], u[i])
}

打印前转化成rune字符,打印之后如下

1
2
3
4
5
6
7
8
U+0068 h 0b1101000 0x68
U+0065 e 0b1100101 0x65
U+006C l 0b1101100 0x6c
U+006C l 0b1101100 0x6c
U+006F o 0b1101111 0x6f
U+0020   0b100000 0x20
U+4E16  0b100111000010110 0x4e16
U+754C  0b111010101001100 0x754c

fmt能通过反射帮助我们把东西打印成想要的样子

  • %U 可以打印成 U+0068这种 unicode 形式
  • %s 就是普通的字符串打印
  • %#b可以携带 0b 前导打印二进制字符串
  • %#x 可以携带 0x 前导打印十六进制字符,当然 %#X 可以以 0X 为前导,打印全大写十六进制字符

可是这些都是我们想要的格式,但是golang是如何存储的呢?

说到这里我们先来看看 string 是如何在 golang 中存储的。

1
2
3
4
5
6
src/runtime/string.go: 

type stringStruct struct {
 str unsafe.Pointer  //str首地址
 len int             //str长度
}

在runtime中找到了对外呈现为stringstringStruct

为什么不能修改:看完这个结构你可能会有所感悟,gostring的内容没有在堆栈上,而是存在了.RODATE中

我们整理一下 golang 中的 runtime

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
高地址
--------
内核Kernel
--------
stack 执行调用栈,和调用参数函数有关的会在这里开辟
--------
heap 通过 new/make 创建的内容存在的地方
--------
.bss 未初始化数据区(全局变
量未初始化)
--------
.data 数据区(全局变量已初始化)
--------
.rodata 大数据区(只读)
--------
.text 代码区       <- string 也正是存在这里。 
--------
地地址

go 中的 string 不像cpp中的那样,有自己的存储空间,而是一个指针指向.rodata数据区。

因此str[0] ="h"这种做法是不被允许的,可以想象如果多个string共用一个内存,那么对一个的修改当然会让另一个不可预测的修改,因此我们通常只能使用str="hello"这种方式,让string指向内存中新的位置,原来的string所占用的应该会被gc回收掉掉。

[]byte(string)这个动大家估计都用过,但是其实这个方法会开辟新的内存,我们可以从https://github.com/golang/go/blob/master/src/runtime/string.go#L165读取到源代码

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
func stringtoslicebyte(buf *tmpBuf, s string) []byte {
 var b []byte
 if buf != nil && len(s) <= len(buf) {
  *buf = tmpBuf{}
  b = buf[:len(s)]
 } else {
  b = rawbyteslice(len(s))
 }
 copy(b, s)
 return b
}

你也可以使用一些骚版本,在不分配新内存的情况下直接修改它,使用一些runtime的不安全1的方法,

例如;

1
2
3
4
5
6
7
8
9
func s2b(s string) []byte {
 sh := (*reflect.StringHeader)(unsafe.Pointer(&s))
 bh := reflect.SliceHeader{
  Data: sh.Data,
  Len:  sh.Len,
  Cap:  sh.Len,
 }
 return *(*[]byte)(unsafe.Pointer(&bh))
}

  1. unsafe 这个包,大概就是比较接近内存的一些操作,容易在程序员不清楚自己在做什么时候造成崩溃。在论坛上有人给他换了个名字叫Believe me,笔者觉得比较形象。 ↩︎

Licensed under CC BY-NC-SA 4.0
Built with Hugo
Theme Stack designed by Jimmy