字符编码基础
- Go语言中标识符可以包含“任何Unicode编码可以表示的字母字符”。
- 当将整数转换为string类型时,如果整数值不能代表一个有效的Unicode代码点,转换的结果就会是"�"。
- 当一个string类型的值被转换为[]rune类型时,其中的字符串会被拆分成一个一个的Unicode字符。
Go语言采用的字符编码方案从属于Unicode编码规范,所有源代码,都必须按照Unicode编码规范中的UTF-8编码格式进行编码
。
ASCII编码
- ASCII是
American Standard Code for Information Interchange
的缩写,中文翻译为美国信息交换标准代码
,它是由美国国家标准学会(ANSI)制定的单字节字符编码方案
,可用于基于文本的数据交换。最初是美国的国家标准,后又被国际标准化组织(ISO)定为国际标准,称为ISO 646标准,并适用于所有的拉丁文字字母。
- ASCII编码方案使用单个字节(byte)的二进制数来编码一个字符。标准的ASCII编码用一个字节的最高比特(bit)位作为奇偶校验位,而扩展的ASCII编码将此位也用于表示字符。
- ASCII编码支持的可打印字符和控制字符的集合,也被叫做ASCII编码集。
Unicode编码规范
- Unicode编码规范是另一个更通用的,针对
书面字符和文本字符
的编码标准,为世界上现存的所有自然语言中的每一个字符,都设定了一个唯一的二进制编码。
- 它定义了不同自然语言的文本数据在国际间交换的统一方式,并为全球化软件创建了一个重要的基础。
Unicode编码规范以ASCII编码集为出发点,并突破了ASCII只能对拉丁字母进行编码的限制
。
- 在计算机系统内部,抽象的字符会被编码为整数,这些整数的范围被称为
代码点
,在代码空间之内,每一个特定的整数都被称为一个代码点。
- Unicode编码规范通常使用
十六进制
来表示Unicode代码点的整数值,并使用U+
作为前缀,比如,英文字母"a"的Unicode代码点是U+0061
。
- Unicode编码规范提供了三种编码格式:
UTF-8、UTF-16、UTF-32
,UTF是UCS Transformation Format的缩写,UCS又是Universal(或Unicode)Character Set的缩写,所以UTF也可以被翻译为Unicode转换格式。它代表的是字符与字节序列之间的转换方式。末尾的数字表示以多少个比特位作为一个编码单元
。比如UTF-8表示以8比特作为一个编码单元,并且它和标准的ASCII编码完全兼容,也就是说在[0x00,0x7F]范围内,这两种编码表示的字符都是相同的,这也是UTF-8编码格式的巨大优势之一
- UTF-8是可变宽的编码方案,它会用一个或多个字节的二进制数表示某个字符,最多四个字节。比如一个英文字符只需要一个字节,中文字符需要3个字节。
string类型的值在底层如何表达?
在底层一个string类型的值是由一系列相对应的Unicode代码点的UTF-8编码值来表达的。
在Go语言中,string类型既可以拆分成一个包含多个字符的序列([]rune),也可以被拆分成包含多个字节的序列([]byte)
。
- rune(int32的别名类型)的一个值就代表一个Unicode字符。一个rune类型的值在底层其实就是一个 UTF-8 编码值。前者是(便于我们人类理解的)外部展现,后者是(便于计算机系统理解的)内在表达。
str := "Go爱好者"
fmt.Printf("The string: %q\n", str)
fmt.Printf(" => runes(char): %q\n", []rune(str))
fmt.Printf(" => runes(hex): %x\n", []rune(str))
fmt.Printf(" => bytes(hex): [% x]\n", []byte(str))
fmt.Println(len(str))
for i, c := range str {
fmt.Printf("%d: %q [% x]\n", i, c, []byte(string(c)))
}
The string: "Go爱好者"
=> runes(char): ['G' 'o' '爱' '好' '者']
=> runes(hex): [47 6f 7231 597d 8005]
=> bytes(hex): [47 6f e7 88 b1 e5 a5 bd e8 80 85]
11
0: 'G' [47]
1: 'o' [6f]
2: '爱' [e7 88 b1]
5: '好' [e5 a5 bd]
8: '者' [e8 80 85]
- 从输出可以看出一个汉字3个字节,同时汉字的十六进制数字也都比较大。
- 一个string类型的值会由若干个Unicode字符组成,每个Unicode字符都可以由一个rune类型的值来承载。
- 这些字符在底层都会被转换成为UTF-8编码值,而这些UTF-8编码值又会以字节序列形式表达和存储。所以,一个string类型的值在底层就是一个能够表达若干UTF-8编码值得字节序列([]byte)
- 字符串的长度是底层字节切片的长度。
- 此外,在使用
range
遍历字符串时,如果字符串中存在需要多个字节来表示的字符(比如中文),那么得到的索引就会跳跃
。而另外一个变量是rune类型的。相邻的 Unicode 字符的索引值并不一定是连续的。这取决于前一个 Unicode 字符是否为单字节字符
对于4字节的UTF-8编码,如何判断是两个2字节还是一个1字节一个3字节?
const (
RuneError = '\uFFFD'
RuneSelf = 0x80
MaxRune = '\U0010FFFF'
UTFMax = 4
)
const (
surrogateMin = 0xD800
surrogateMax = 0xDFFF
)
const (
t1 = 0x00
tx = 0x80
t2 = 0xC0
t3 = 0xE0
t4 = 0xF0
t5 = 0xF8
maskx = 0x3F
mask2 = 0x1F
mask3 = 0x0F
mask4 = 0x07
rune1Max = 1<<7 - 1
rune2Max = 1<<11 - 1
rune3Max = 1<<16 - 1
locb = 0x80
hicb = 0xBF
xx = 0xF1
as = 0xF0
s1 = 0x02
s2 = 0x13
s3 = 0x03
s4 = 0x23
s5 = 0x34
s6 = 0x04
s7 = 0x44
)
var first = [256]uint8{
as, as, as, as, as, as, as, as, as, as, as, as, as, as, as, as,
as, as, as, as, as, as, as, as, as, as, as, as, as, as, as, as,
as, as, as, as, as, as, as, as, as, as, as, as, as, as, as, as,
as, as, as, as, as, as, as, as, as, as, as, as, as, as, as, as,
as, as, as, as, as, as, as, as, as, as, as, as, as, as, as, as,
as, as, as, as, as, as, as, as, as, as, as, as, as, as, as, as,
as, as, as, as, as, as, as, as, as, as, as, as, as, as, as, as,
as, as, as, as, as, as, as, as, as, as, as, as, as, as, as, as,
xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx,
xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx,
xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx,
xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx,
xx, xx, s1, s1, s1, s1, s1, s1, s1, s1, s1, s1, s1, s1, s1, s1,
s1, s1, s1, s1, s1, s1, s1, s1, s1, s1, s1, s1, s1, s1, s1, s1,
s2, s3, s3, s3, s3, s3, s3, s3, s3, s3, s3, s3, s3, s4, s3, s3,
s5, s6, s6, s6, s7, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx,
}
func RuneCountInString(s string) (n int) {
ns := len(s)
for i := 0; i < ns; n++ {
c := s[i]
if c < RuneSelf {
i++
continue
}
x := first[c]
if x == xx {
i++
continue
}
size := int(x & 7)
if i+size > ns {
i++
continue
}
accept := acceptRanges[x>>4]
if c := s[i+1]; c < accept.lo || accept.hi < c {
size = 1
} else if size == 2 {
} else if c := s[i+2]; c < locb || hicb < c {
size = 1
} else if size == 3 {
} else if c := s[i+3]; c < locb || hicb < c {
size = 1
}
i += size
}
return n
}