参考链接:https://www.cnblogs.com/zhangboyu/p/7623712.html
1.编码
参考链接:https://www.cnblogs.com/liupp123/articles/8023861.html
首先明确一点计算机里面存的始终是二进制的数据,但是为了让数据变为可读的,于是就出现了编码,比如ascll、gbk、utf-8等编码方式。
-
Ascll编码
对于英语而言,其字符状态空间较小,只有52个字符,外加一些标点符号,1byte的长度(2^8个状态=256)就可以满足了,即ascll编码就可以满足(ASCII码一共规定了128个字符的编码,只占用了一个字节的后面7位,最前面的1位统一规定为0) -
中文编码
但是对于其他语言,比如中文,字符状态空间极大,128个字符完全不够,于是要求更大的编码长度来表示。比如,简体中文常见的编码方式是GB2312,使用2byte表示一个汉字,所以理论上最多可以表示256x256=65536个符号 -
Unicode编码
世界上存在着多种编码方式,同一个二进制数字可以被解释成不同的符号。因此,要想打开一个文本文件,就必须知道它的编码方式,否则用错误的编码方式解读,就会出现乱码。为什么电子邮件常常出现乱码?就是因为发信人和收信人使用的编码方式不一样。
可以想象,如果有一种编码,将世界上所有的符号都纳入其中。每一个符号都给予一个独一无二的编码,那么乱码问题就会消失。这就是Unicode,就像它的名字都表示的,这是一种所有符号的编码。
Unicode当然是一个很大的集合,现在的规模可以容纳100多万个符号。每个符号的编码都不一样,比如,U+0639表示阿拉伯字母Ain,U+0041表示英语的大写字母A,U+4E25表示汉字"严"。具体的符号对应表,可以查询unicode.org,或者专门的汉字对应表。 -
UTF-8编码
由于Unicode 编码的字符空间太大,编码占用的空间太大,为了压缩编码空间,于是出现了utf-8。其他实现方式还包括UTF-16(字符用两个字节或四个字节表示)和UTF-32(字符用四个字节表示),不过在互联网上基本不用。重复一遍,这里的关系是,UTF-8是Unicode的实现方式之一。
UTF-8最大的一个特点,就是它是一种变长的编码方式。它可以使用1~4个byte表示一个符号,根据不同的符号而变化字节长度。
UTF-8的编码规则很简单,只有二条:
1)对于单字节的符号,字节的第一位设为0,后面7位为这个符号的unicode码。因此对于英语字母,UTF-8编码和ASCII码是相同的。
2)对于n字节的符号(n>1),第一个字节的前n位都设为1,第n+1位设为0,后面字节的前两位一律设为10。剩下的没有提及的二进制位,全部为这个符号的unicode码。
2.字符
在上面说过了所有的可读性字符(串)都是通过解码字节序列而呈现的,在go中默认是utf-8编码,所以对于一个字符而言其最大的字节(byte[])长度为4,所以需要使用32bit的大小来存,即int32(go的基本类型,相当于是对32bit的数据的封装),更进一步,go通过rune类型来作为int32在字符方面的别名,所以使用单引号的字面量其类型就是rune(最底层还是int32),类似于其他语言的char。
2.[]byte
上面说了编码方式,于是引出[]byte。从上面可知要想以可读字符形式呈现在计算机中,必须要将字符文本以某种编码方式存入。比如,如果要存为utf-8编码的文本,就要存入1~4个byte大小单位的[]byte。所以说实际上所有文本都是以byte单位存入到计算机中的。程序要想打印数据到屏幕上,需要根据不同的编码方式将机器码按照编码所规定的字符byte大小的长度进行解码翻译,从而以可读方式呈现。
3.string
简单的来说字符串是一系列8位字节的集合,通常但不一定代表UTF-8编码的文本。字符串可以为空,但不能为nil。而且字符串的值是不能改变的。
不同的语言字符串有不同的实现,在go的源码中src/runtime/string.go,string的定义如下:
type stringStruct struct {
str unsafe.Pointer
len int
}
可以看到str其实是个指针,指向某个数组的首地址,另一个字段是len长度。那到这个数组是什么呢? 在实例化这个stringStruct的时候:
func gostringnocopy(str *byte) string {
ss := stringStruct{str: unsafe.Pointer(str), len: findnull(str)}
s := *(*string)(unsafe.Pointer(&ss))
return s
}
哈哈,其实就是byte数组,而且要注意string其实就是个struct,所以索引字符串时s[i],是取的其字节byte,而非字符。
所以对于计算string的长度,在go中是看其byte[]所占的长度,而非字符总长度,这与其它语言很不同,因为其他语言是按照字符来计算string的长度,比如:
java中
main(){
String s = "长安a"
System.out.println(s.length())
}
————output————
3
而在go中
package main
import (
"fmt"
)
func main() {
s := "长安a"
fmt.Println(len(s))
}
——————————output——————————
7
因为utf-8中文占3byte,字母数字占1byte,所以最终长度为7
4.字符串的遍历方式和修改
在go中遍历字符串,有两种方式,byte和rune
package main
import (
"fmt"
)
func main() {
s := "长安a"
//byte
for index := 0; index < len(s); index++ {
fmt.Printf("%d: %c\n", index, s[index])
}
//rune
for i, c := range s {
fmt.Printf("%d: %c\n", i, c)
}
}
————output————
0: é 1: • 2: ¿ 3: å 4: ® 5: ‰ 6: a
0: 长 3: 安 6: a
从上面可看出,byte方式由于按照1byte方式取,每次取的值都只有长度为1byte,所以最终解码出来和中文对不上
rune方式是按照字符长度取的,索引间隔是实际一个字符的byte长度,所以最终文字对得上。
5.[]byte与string取舍
既然string就是一系列字节,而[]byte也可以表达一系列字节,那么实际运用中应当如何取舍?
- string可以直接比较,而[]byte不可以,所以[]byte不可以当map的key值。
- 因为无法修改string中的某个字符,需要粒度小到操作一个字符时,用[]byte。
- string值不可为nil,所以如果你想要通过返回nil表达额外的含义,就用[]byte。
- []byte切片这么灵活,想要用切片的特性就用[]byte。
- 需要大量字符串处理的时候用[]byte,性能好很多。