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))
}