这个问题看似简单,但是要总结一下,写代码的时候思路可以更清晰,顺便比较一下string, rune和byte[]几个数据类型的区别。
操作系统中的字符编码
ASCII码
上个世纪60年代,美国制定了一套字符编码,对英文字符与二进制位之间的关系,做了统一的规定,并称为ASCII码,一直沿用至今。ASCII码一共规定了128个字符编码,比如空格“SPACE”是32(二进制00100000),大写字母A是65(二进制01000001)。 这128个符号(包括32个不能打印出来的控制符号), 只占用了一个字节的后面7位,最前面的1位统一规定为0。简单一点来说,就是使用单子节的整数表示英文字符,我们称之为字符的ASCII码表示。以后有人问我们,“A”的ASCII码是什么?直接回答65就可以了。
Unicode字符集
在英文中,仅仅用128个符号就足够了,但是在其他文字中是远远不够的。这时候,Unicode就出现了。Unicode s将全世界的字符都纳入其中,每一个字符都用独一无二的编码。比如,U+0639表示阿拉伯字母Ain,U+0041表示英语的大写字母A,U+4E25表示汉字”严”。
这时候,我们来思考一个问题?表示中文的”严”至少需要两个字节,但是英文字符字符只需要一个字节。如果英文字符的前面字节全部变为0,就会造成极大的浪费。所以造成了Unicode字符集在世界上有多种存储方式,而且迟迟无法推广,直到互联网的兴起才解决了这个问题。
UTF-8
utf-8编码的出现,几乎统一整个互联网,形成了事实上标准。简单来说,UTF-8就是在互联网上使用最广的一种Unicode的实现方式。
String
在go语言中,string就是读取使用utf-8编码的字节切片(slice)。因此使用len函数得到的长度并不是字符个数,而是字节个数。for循环遍历也是输出各个字节。
a := "Randal";
for i := 0; i < len(a); i++ {
fmt.Printf("%x ", a[i])
fmt.Printf("%c ", a[i])
}
// 输出结果
52 61 6e 64 61 6c
Randal
a := "中国";
fmt.Println(len(a))
for i := 0; i < len(a); i++ {
fmt.Printf("%x ", a[i])
}
for i := 0; i < len(a); i++ {
fmt.Printf("%c ", a[i])
}
// 输出结果
6
E4 B8 AD E5 9B BD
ä¸å½
但我们在对中文使用格式化输出的时候,出现了乱码。原因是当字符的utf-8编码超过1个字节的时候,格式化输出单个字符就会出现乱码的情况,rune可以帮助我们解决乱码的问题。
rune
rune是int32的别名, 代表字符的unicode码,采用4个字节存储。将string转成rune就意味着任何一个字符都是使用4个字节才存储unicode码。这样每次遍历的时候返回的就是unicode,而不再是字节,便可以解决乱码的问题。
var s string
s = "中国"
r := []rune(s)
for i := 0; i < len(r); i++ {
fmt.Printf("%x", r[i])
}
for i := 0; i < len(r); i++ {
fmt.Printf("%c", r[i])
}
// 输出结果
4e2d 56fd
中国
通过for range对字符串进行遍历时,每次获取到的对象都是rune类型的,因此下面的方式也可以解决乱码问题。
var s string
s = "中国"
for _, item := range s {
fmt.Printf("%c", item)
}
// 输出结果
中国
bytes
byte操作的对象也是字节切片,与string的不可变不同,byte是可变的。go的字符串是utf-8编码的,每个字符长度是不确定的。
s1 := "abcd"
b1 := []byte(s1)
fmt.Println(b1) // [97 98 99 100]
s2 := "中文"
b2 := []byte(s2)
fmt.Println(b2) // [228 184 173 230 150 135], unicode,每个中文字符会由三个byte组成
// byte和string的转化
var data [10]byte
byte[0] = 'T'
byte[1] = 'E'
var str string = string(data[:])