1、字符集
- 计算机里1bit可以是0也可以是1
- 8bit组成1byte,全为0时表示数字0,全为1时表示数字255
- 2byte可以表示65536个数字,更多字节数可以表示更大的数字范围
- 字符如何表示呢?
- ASCII字符集:一共收录了128个字符,其扩展字符集也只有256个(包括英文字母、阿拉伯数字、西文字、控制字符)
- GB2312:包括了简体中文、拉丁字母、日文假名等等
- BIG5:包括了繁体字等
- unicode:于1990年开始研发,1994年正式公布,全球统一化字符编码
2、如何表示混合字符
- 比如"abc字母歌"这种字符如何存储呢?
- 如果直接用unicode字符集表示,如下图
func main() { //string转化为unicode编码切片 str := "hello,world世界" unicode := []rune(str) //打印unicode编码 fmt.Println(unicode) for _, v := range unicode { //打印二进制 fmt.Printf("%b\n", v) } }
- 问题来了
- 这么多二进制编码有长有短,你怎么知道哪个是哪个呢?
- 所以就会有字符边界划分的问题?
- 定长编码可以解决这个问题,但是确实有点浪费空间
- UTF-8编码:它它是一种变长编码,能有效地解决上述问题,可以根据字符大小范围指定字符边界
- 0-127之间的,占用1字节,以0标识在字节开头
- 128-2047之间的,占用2字节,以110和10标识在字节开头
- 2048-65535之间的,占用3字节,分别以1110、10、10标识在字节开头
- 以此类推,更多字节的开头都遵循这样的规则
3、Go语言string
- Go语言默认采用UTF-8编码
func main() { //string转化为unicode编码切片 str := "hello,world世界" //打印str的utf-8二进制编码 strSlice := []byte(str) for _, v := range strSlice { fmt.Printf("%b\n", v) } } /* 1101000 - h 1100101 - e 1101100 - l 1101100 - l 1101111 - o 101100 - , 1110111 - w 1101111 - o 1110010 - r 1101100 - l 1100100 - d 11100100 10111000 10010110 - 世 11100111 10010101 10001100 - 界 */
- Go语言中上述的 str 变量是什么样的结构呢?
- 对于string变量,Go语言认为它不可被修改,所以string变量会记录执行只读字符串的内存起始地址
- 如何找到字符内存地址的结尾地址呢?在C语言中,会在字符的结尾带上\0,但这样就不能写入\0本身这种字符了
- 为了要找到结尾标识,go语言会在变量后面标识只读字符的字节数
- string底层数据结构
type stringStruct struct { str unsafe.Pointer // 底层数组指针 len int // 字符串长度,可以通过 len(string) 返回 }
- 如何修改字符串内容呢?
- slice底层数据结构
type slice struct { array unsafe.Pointer // 底层数组指针,真正存放数据的地方 len int // 切片长度,通过 len(slice) 返回 cap int // 切片容量,通过 cap(slice) 返回 }
- 可以把字符串变量的值赋值给[]byte这样的切片,会给变量从新分配内存,并且会拷贝字符对应的utf-8编码到切片中
- 如何使用实现 []byte 和字符串之间的零拷贝转换?
func StringToBytes(str string) []byte { var b []byte // 切片的底层数组、len字段,指向字符串的底层数组,len字段 *(*string)(unsafe.Pointer(&b)) = str // 切片的 cap 字段赋值为 len(str)的长度,切片的指针、len 字段各占八个字节,直接偏移16个字节 *(*int)(unsafe.Pointer(uintptr(unsafe.Pointer(&b)) + 2*uintptr(8))) = len(str) return b } func BytesToString(data []byte) string { // 直接转换 return *(*string)(unsafe.Pointer(&data)) }