随笔 - 21  文章 - 0  评论 - 0  阅读 - 6131

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

复制代码
posted on   博览天下with天涯海角  阅读(1362)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 分享4款.NET开源、免费、实用的商城系统
· 全程不用写代码,我用AI程序员写了一个飞机大战
· MongoDB 8.0这个新功能碉堡了,比商业数据库还牛
· 白话解读 Dapr 1.15:你的「微服务管家」又秀新绝活了
· 记一次.NET内存居高不下排查解决与启示
< 2025年3月 >
23 24 25 26 27 28 1
2 3 4 5 6 7 8
9 10 11 12 13 14 15
16 17 18 19 20 21 22
23 24 25 26 27 28 29
30 31 1 2 3 4 5

点击右上角即可分享
微信分享提示