unicode
请先查看 字符编码笔记:ASCII,Unicode 和 UTF-8
go的字符串是如何编码的
根据golang官方博客https://blog.golang.org/strings的原文:
Go source code is always UTF-8.
A string holds arbitrary bytes.
A string literal, absent byte-level escapes, always holds valid UTF-8 sequences.
大致意思如下:
- go中的代码总是用UTF-8编码,并且字符串能够存储任何字节
- 没有经过字节级别的转义,那么字符串是一个标准的utf8序列
byte和rune
在Go语言中,一个string类型的值既可以被拆分为一个包含多个字符的序列([]rune),也可以被拆分为一个包含多个字节的序列([]byte)
rune是Go语言特有的一个基本数据类型,它的一个值就代表一个字符,即:一个Unicode字符。比如,'G'、'o'、'爱'、'好'、'者'代表的就都是一个个Unicode字符。rune的底层表达使用的是Unicode代码点,底层的存储用UTF-8编码
通过unicode部分我们已经知道,UTF-8编码方案会把一个Unicode字符编码为一个长度在[1, 4]范围内的字节序列。所以rune类型的值也可以由一个或多个字节来代表。
type rune = int32 //rune实际上就是int32类型的一个别名类型
举几个栗子
string转[]byte/[]rune
str := "Go爱好者"
fmt.Printf(" => runes(char): %q\n", []rune(str))
fmt.Printf(" => runes(hex): %x\n", []rune(str))
fmt.Printf(" => bytes(hex): [% x]\n", []byte(str))
// output
=> runes(char): ['G' 'o' '爱' '好' '者'] //这里用%q来安全的转义成单引号围绕的字符字面值,%s无法转义成字符字面值
=> runes(hex): [47 6f 7231 597d 8005]
=> bytes(hex): [47 6f e7 88 b1 e5 a5 bd e8 80 85]
分析:
- []rune中的前两个元素即47和6f与[]byte中的前两个元素是一致的,因为UTF-8是兼容ASCII的,都用一个字节表示
- []rune中的后三个元素分别对应[]byte中的后九个元素,其中连续的三个对应[]rune中的一个,因为rune中的中文字符使用UTF-8编码,占用三个字节,同时也是UTF-8编码对应Unicode码点
如下是Unicode编码中UTF-8的编码规则
Unicode编码(十六进制) UTF-8 字节流(二进制)
000000-00007F 0xxxxxxx
000080-0007FF 110xxxxx 10xxxxxx
000800-00FFFF 1110xxxx 10xxxxxx 10xxxxxx
010000-10FFFF 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx
以'爱'为例,其对应的Unicode编码是16进制的7231
,对应二进制111001000110001
, 那怎么对应上[]byte中的e7 88 b1
呢?
这里有一个转换过程,如下:
- 因为0x010000> 0x7231 > 0x07FF,所以套用UTF-8的模板是
1110xxxx 10xxxxxx 10xxxxxx
- 根据模板中x数量对特殊字符的高位补0。x的数量是16,所以需要对
111001000110001
的高位补1个0,此时特殊字符的二进制表示为:0111001000110001
- 模板中包含x的有3个部分,且长度分别是4、6和6,所以
0111001000110001
由底位向高位分别截取6、6和4位,得到110001
、001000
和0111
- 将得到的截取位依次填充至模板,可得到UTF-8的完整二进制序列为:
11100111 10001000 10110001
,也就对应上了e7 88 b1
整数转string
通过上面已知,'爱'的Unicode编码对应16进制是7231
,换算成整数就是29233
fmt.Printf("%q\n", 29233)
// output
'爱'
所以我们如果对整数进行强制转换成string,需要注意是否是对应的合法的Unicode码点
str := string(235234234234)
fmt.Printf("%q\n", str)
// output
"�"
这里因为235234234234
不是合法的Unicode码点,所以强制转换后的结果是非预期的
for...range处理字符串
见代码:
str := "go,你好"
for i, v := range str {
fmt.Printf("%d %q\n", i, v)
}
// output
// 注意这里输出的索引值是以字节为跨度计算的
0 'g'
1 'o'
2 ','
5 '你'
8 '好'
带有range子句的for语句会先把被遍历的字符串值拆成一个字节序列,然后再试图找出这个字节序列中包含的每一个UTF-8编码值,或者说每一个Unicode字符
网友评论