go 语言unicode/utf8包
参考:模块三 GO语言实战与应用-unicode与字符编码 - lvp - 博客园 (cnblogs.com)
Go 语言字符编码基础
Go 语言中的标识符可以包含“任何 Unicode 编码可以表示的字母字符”。虽然可以直接把一个整数值转换为一个string类型的值。但是,被转换的整数值应该可以代表一个有效的 Unicode 代码点,否则转换的结果就将会是"�",即:一个仅由高亮的问号组成的字符串值。
另外,当一个string类型的值被转换为[]rune类型值的时候,其中的字符串会被拆分成一个一个的 Unicode 字符。
显然,Go 语言采用的字符编码方案从属于 Unicode 编码规范。更确切地说,Go 语言的代码正是由 Unicode 字符组成的。Go 语言的所有源代码,都必须按照 Unicode 编码规范中的 UTF-8 编码格式进行编码。换句话说,Go 语言的源码文件必须使用。
Unicode 编码规范提供了三种不同的编码格式,即:UTF-8、UTF-16 和 UTF-32。其中的 UTF 是 UCS Transformation Format 的缩写。而 UCS 又是 Universal Character Set 的缩写,但也可以代表 Unicode Character Set。所以,UTF 也可以被翻译为 Unicode 转换格式。它代表的是字符与字节序列之间的转换方式。在这几种编码格式的名称中,“-”右边的整数的含义是,以多少个比特位作为一个编码单元。以 UTF-8 为例,它会以 8 个比特,也就是一个字节,作为一个编码单元。并且,它与标准的 ASCII 编码是完全兼容的。也就是说,在[0x00, 0x7F]的范围内,这两种编码表示的字符都是相同的。这也是 UTF-8 编码格式的一个巨大优势。UTF-8 是一种可变宽的编码方案。换句话说,它会用一个或多个字节的二进制数来表示某个字符,最多使用四个字节。比如,对于一个英文字符,它仅用一个字节的二进制数就可以表示,而对于一个中文字符,它需要使用三个字节才能够表示。不论怎样,一个受支持的字符总是可以由 UTF-8 编码为一个字节序列。以下会简称后者为 UTF-8 编码值。
一个string类型的值在底层是怎样被表达的?
是在底层,一个string类型的值是由一系列相对应的 Unicode 代码点的 UTF-8 编码值来表达的。
在 Go 语言中,一个string类型的值既可以被拆分为一个包含多个字符的序列,也可以被拆分为一个包含多个字节的序列。前者可以由一个以rune为元素类型的切片来表示,而后者则可以由一个以byte为元素类型的切片代表。rune是 Go 语言特有的一个基本数据类型,它的一个值就代表一个字符,即:一个 Unicode 字符。比如,'G'、'o'、'爱'、'好'、'者'代表的就都是一个 Unicode 字符。我们已经知道,UTF-8 编码方案会把一个 Unicode 字符编码为一个长度在[1, 4]范围内的字节序列。所以,一个rune类型的值也可以由一个或多个字节来代表。
type rune = int32
根据rune类型的声明可知,它实际上就是int32类型的一个别名类型。也就是说,一个rune类型的值会由四个字节宽度的空间来存储。它的存储空间总是能够存下一个 UTF-8 编码值。一个rune类型的值在底层其实就是一个 UTF-8 编码值。前者是(便于我们人类理解的)外部展现,后者是(便于计算机系统理解的)内在表达。
请看下面的代码:
func test_10() { str := "GO爱好者" fmt.Printf("%q", []rune(str)) fmt.Printf("%x", []rune(str)) fmt.Printf("%x", []byte(str)) }
字符串值"Go爱好者"如果被转换为[]rune类型的值的话,其中的每一个字符(不论是英文字符还是中文字符)就都会独立成为一个rune类型的元素值。因此,这段代码打印出的第二行内容就会如下所示:
['G' 'O' '爱' '好' '者']
又由于,每个rune类型的值在底层都是由一个 UTF-8 编码值来表达的,所以我们可以换一种方式来展现这个字符序列:
[47 4f 7231 597d 8005]
可以看到,五个十六进制数与五个字符相对应。很明显,前两个十六进制数47和6f代表的整数都比较小,它们分别表示字符'G'和'o'。因为它们都是英文字符,所以对应的 UTF-8 编码值用一个字节表达就足够了。一个字节的编码值被转换为整数之后,不会大到哪里去。而后三个十六进制数7231、597d和8005都相对较大,它们分别表示中文字符'爱'、'好'和'者'。这些中文字符对应的 UTF-8 编码值,都需要使用三个字节来表达。所以,这三个数就是把对应的三个字节的编码值,转换为整数后得到的结果。
我们还可以进一步地拆分,把每个字符的 UTF-8 编码值都拆成相应的字节序列。上述代码中的第五行就是这么做的。它会得到如下的输出:
[47 4f e7 88 b1 e5 a5 bd e8 80 85]
这里得到的字节切片比前面的字符切片明显长了很多。这正是因为一个中文字符的 UTF-8 编码值需要用三个字节来表达。这个字节切片的前两个元素值与字符切片的前两个元素值是一致的,而在这之后,前者的每三个元素值才对应字符切片中的一个元素值。
一个string类型的值会由若干个 Unicode 字符组成,每个 Unicode 字符都可以由一个rune类型的值来承载。这些字符在底层都会被转换为 UTF-8 编码值,而这些 UTF-8 编码值又会以字节序列的形式表达和存储。因此,一个string类型的值在底层就是一个能够表达若干个 UTF-8 编码值的字节序列。
使用带有range子句的for语句遍历字符串值的时候应该注意什么?
带有range子句的for语句会先把被遍历的字符串值拆成一个字节序列,然后再试图找出这个字节序列中包含的每一个 UTF-8 编码值,或者说每一个 Unicode 字符。这样的for语句可以为两个迭代变量赋值。如果存在两个迭代变量,那么赋给第一个变量的值,就将会是当前字节序列中的某个 UTF-8 编码值的第一个字节所对应的那个索引值。而赋给第二个变量的值,则是这个 UTF-8 编码值代表的那个 Unicode 字符,其类型会是rune。
func test_11() { str := "GO爱好者" for i, c := range str { fmt.Printf("%d: %q [% x]\n", i, c, []byte(string(c))) } }
0: 'G' [47] 1: 'O' [4f] 2: '爱' [e7 88 b1] 5: '好' [e5 a5 bd] 8: '者' [e8 80 85]
由此可以看出,这样的for语句可以逐一地迭代出字符串值里的每个 Unicode 字符。但是,相邻的 Unicode 字符的索引值并不一定是连续的。这取决于前一个 Unicode 字符是否为单字节字符。Go 语言中的一个string类型值会由若干个 Unicode 字符组成,每个 Unicode 字符都可以由一个rune类型的值来承载。这些字符在底层都会被转换为 UTF-8 编码值,而这些 UTF-8 编码值又会以字节序列的形式表达和存储。因此,一个string类型的值在底层就是一个能够表达若干个 UTF-8 编码值的字节序列。
unicode/utf8包
const ( RuneError = '\uFFFD' // 错误的Rune或"Unicode replacement character" RuneSelf = 0x80 // 低于RunSelf的字符用代表单字节的同一值表示 MaxRune = '\U0010FFFF' // 最大的合法unicode码值 UTFMax = 4 // 最大的utf-8编码的unicode字符的长度 )
func main() { var RuneError rune = '\uFFFD' var RuneSelf rune = 0x80 var MaxRune rune = '\U0010FFFF' var UTFMax rune = 4 fmt.Println(RuneError) //65533 fmt.Println(RuneSelf) //128 fmt.Println(MaxRune) //1114111 fmt.Println(UTFMax) //4 }
DecodeRune函数
函数解码p开始位置的第一个utf-8编码的码值,返回该码值和编码的字节数。 如果编码不合法,会返回(RuneError, 1)。该返回值在正确的utf-8编码情况下是不可能返回的。 如果一个utf-8编码序列格式不正确,或者编码的码值超出utf-8合法码值的范围,或者不是该码值的最短编码,该编码序列即是不合法的。函数不会执行其他的验证。
func DecodeRuneInString(s string) (r rune, size int)
func test_12() {
str := "爱好者"
b := []byte(str)
fmt.Println(b)
c, size := utf8.DecodeRune(b) //相当于把[231 136 177]解码为 爱
fmt.Printf("%c %d\n", c, size)
}
结果:
[231 136 177 229 165 189 232 128 133]
爱 3
DecodeRuneInString函数
函数类似DecodeRune但输入参数是字符串。
func DecodeRuneInString(s string) (r rune, size int)
func test_12() { str := "爱好者" c, size := utf8.DecodeRuneInString(str) fmt.Printf("%c, %d", c, size) }
结果:
爱, 3
ValidRune
判断r是否可以编码为合法的utf-8序列。 func ValidRune(r rune) bool
RuneLen
返回r编码后的字节数。如果r不是一个合法的可编码为utf-8序列的值,会返回-1。 func RuneLen(r rune) int
RuneStart
报告字节b是否可以作为某个rune编码后的第一个字节。第二个即之后的字节总是将左端两个字位设为10。 func RuneStart(b byte) bool
RuneCount
返回p中的utf-8编码的码值的个数。错误或者不完整的编码会被视为宽度1字节的单个码值。 func RuneCount(p []byte) int
RuneCountInString
函数类似RuneCount但输入参数是一个字符串。 func RuneCountInString(s string) (n int)
func main() { a := utf8.RuneCountInString("李陆豪") fmt.Println(a) //3 }
Valid
返回切片p是否包含完整且合法的utf-8编码序列。 func Valid(p []byte) bool
ValidString
报告s是否包含完整且合法的utf-8编码序列。 func ValidString(s string) bool
EncodeRune
EncodeRune将r的utf-8编码序列写入p(p必须有足够的长度),并返回写入的字节数。 func EncodeRune(p []byte, r rune) int
func test_12() { //str := "爱好者" p := make([]byte, 3) n := utf8.EncodeRune(p, '爱') fmt.Printf("[% x], %d", p, n) }
结果:
[e7 88 b1], 3
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 分享4款.NET开源、免费、实用的商城系统
· 全程不用写代码,我用AI程序员写了一个飞机大战
· MongoDB 8.0这个新功能碉堡了,比商业数据库还牛
· 白话解读 Dapr 1.15:你的「微服务管家」又秀新绝活了
· 记一次.NET内存居高不下排查解决与启示