当前位置 博文首页 > Go语言中多字节字符的处理方法详解

    Go语言中多字节字符的处理方法详解

    作者:韩忠康 时间:2021-07-02 18:35

    1 概述

    Go语言的字符串是使用 UTF-8 编码的。UTF-8 是 Unicode 的实现方式之一。本文内容包括:UTF-8 和 Unicode 的关系,Go语言提供的 unicode 包和 unicode/utf8 包的使用。

    下面话不多说了,来一起看看详细的介绍吧

    2 UTF-8 和 Unicode 的关系

    Unicode一种字符集,是国际标谁化组织(ISO)设计的一个包括了地球上所有文化、所有字母和符号 的编码。他们叫它 Universal Multiple-Octet Coded Character Set,简称 UCS,也就是 Unicode。Unicode 为每一个 字符 分配一个唯一的 码点(Code Point),就是一个唯一的值。例如 康 的码点就是 24247,十六进制为 5eb7。

    Unicode 字符集仅仅定义了字符与码点的对应关系,但是并没有定义该如何编码(存储)这个码值,这就导致了很多问题。例如由于字符的码值不同,导致所需要的存储空间是不一致的,计算机不能确定接下来的字符是占用几个字节。还有就是如果采用固定的长度假设都是4个字节来存储码点值,那么会导致空间的额外浪费,因为 ascii 码字符其实仅仅需要一个字节的空间。

    UTF-8 就是解决如何为 Unicode 编码而设计的一种编码规则。可以说 UTF-8 是 Unicode 的实现方式之一。其特点是一种变长编码,使用1到4个字节表示一个字符,根据不同的符号而变化长度。UTF-8 的编码规则有二:

    • 对于单字节的符号,字节的第一位设为0,后面7位为这个符号的 Unicode 码。因此对于ASCII码字符,UTF-8 编码和 ASCII 码是相同的。
    • 对于 n 字节的符号(n > 1,2到4),第一个字节的前n位都设为1,第n + 1 位设为 0,后面字节的前两位一律设为10。剩下的没有提及的二进制位,全部为这个符号的 Unicode 码。

    以下是编码规则:

    Unicode    | UTF-8
    --------------------------------------------------------- 
    0000 0000-0000 007F | 0xxxxxxx
    0000 0080-0000 07FF | 110xxxxx 10xxxxxx
    0000 0800-0000 FFFF | 1110xxxx 10xxxxxx 10xxxxxx
    0001 0000-0010 FFFF | 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx
    ---------------------------------------------------------

    Go语言中,对于 Unicode 和 UTF-8 使用了 unicode 和 unicode/utf8 包来实现,下面是阅读 API 的总结和说明。

    3 Unicode 包

    Go语言中,提供了 Unicode 包,处理与 Unicode 相关的操作,整理如下:

    Is(rangeTab *RangeTable, r rune) bool

    检测 rune r 是否在 rangeTable 指定的字符范围内。

    rangeTable 一个 Unicode 码值集合,通常使用 unicode 包中定义的集合。

    判断字符是否出现在汉字集合中:

    unicode.Is(unicode.Scripts["Han"], 'k')
    // 返回 false
    unicode.Is(unicode.Scripts["Han"], '康')
    // 返回 true

    In(r rune, ranges …*RangeTable) bool

    检测 rune r 是否在多个 rangeTable 指定的字符范围内。

    rangeTable 一个 Unicode 码值集合,通常使用 unicode 包中定义的集合。

    unicode.In('康', unicode.Scripts["Han"], unicode.Scripts["Latin"])
    // 返回 true
    unicode.In('k', unicode.Scripts["Han"], unicode.Scripts["Latin"])
    // 返回 true

    IsOneOf(ranges []*RangeTable, r rune) bool

    检测 rune r 是否在 rangeTable ranges 指定的字符范围内。与 In 功能类似,推荐使用 In。

    IsSpace(r rune) bool

    检测字符 rune r 是否是空白字符。在Latin-1字符空间中,空白字符为:

    '\t', '\n', '\v', '\f', '\r', ' ', U+0085 (NEL), U+00A0 (NBSP)

    其它的空白字符请参见策略Z和属性Pattern_White_Space。

    IsDigit(r rune) bool

    检测字符 rune r 是否是十进制数字字符。

    unicode.IsDigit('9')
    // 返回 true
    unicode.IsDigit('k')
    // 返回 false

    IsNumber(r rune) bool

    检测字符 rune r 是否是 Unicode 数字字符。

    IsLetter(r rune) bool

    检测一个字符 rune r 是否是字母

    unicode.IsLetter('9')
    // 返回 false
    unicode.IsLetter('k')
    // 返回 true

    IsGraphic(r rune) bool

    一个字符 rune r 是否是 unicode 图形字符。图形字符包括字母、标记、数字、符号、标点、空白。

    unicode.IsGraphic('9')
    // 返回 true
    unicode.IsGraphic(',')
    // 返回 true

    IsControl(r rune) bool

    检测一个字符 rune r 是否是 unicode 控制字符。

    IsMark(r rune) bool

    检测一个字符 rune r 是否是标记字符。

    IsPrint(r rune) bool

    检测一个字符 rune r 是否是的可打印字符,基本与图形字符一致,除ASCII空白字符U+0020。

    IsPunct(r rune) bool

    检测一个字符 rune r 是否是 unicode标点字符。

    unicode.IsPunct('9')
    // 返回 false
    unicode.IsPunct(',')
    // 返回 true

    IsSymbol(r rune) bool

    检测一个字符 rune r 是否是 unicode 符号字符。

    IsLower(r rune) bool

    检测一个字符 rune r 是否是小写字母。

    unicode.IsLower('h')
    // 返回 true
    unicode.IsLower('H')
    // 返回 false

    IsUpper(r rune) bool

    检测一个字符 rune r 是否是大写字母。

    unicode.IsUpper('h')
    // 返回 false
    unicode.IsUpper('H')
    // 返回 true

    IsTitle(r rune) bool

    检测一个字符 rune r 是否是Title字符。大部分字符的 Title 格式就是其大写格式,少数字符的 Title 格式是特殊字符,例如 ᾏᾟᾯ。

    unicode.IsTitle('ᾯ')
    // 返回 true
    unicode.IsTitle('h')
    // 返回 false
    unicode.IsTitle('H')
    // 返回 true

    To(_case int, r rune) rune

    将字符 rune r 转换为指定的格式,格式_case支持:unicode.UpperCase、unicode.LowerCase、unicode.TitleCase

    unicode.To(unicode.UpperCase, 'h')
    // 返回 H

    ToLower(r rune) rune

    将字符 rune r 转换为小写。

    unicode.ToLower('H')
    // 返回 h

    func (SpecialCase) ToLower

    将字符 rune r 转换为小写。优先使用映射表 SpecialCase。

    映射表 SpecialCase 是特定语言环境下大小写的映射表。主要应用于一些欧洲字符,例如土耳其 TurkishCase。

    unicode.TurkishCase.ToLower('İ')
    // 返回 i

    ToUpper(r rune) rune

    将字符 rune r 转换为大写。

    unicode.ToUpper('h')
    // 返回 H

    func (SpecialCase) ToUpper

    将字符 rune r 转换为大写。优先使用映射表 SpecialCase。

    映射表 SpecialCase 是特定语言环境下大小写的映射表。主要应用于一些欧洲字符,例如土耳其 TurkishCase。

    unicode.TurkishCase.ToUpper('i')
    // 返回 İ

    ToTitle(r rune) rune

    将字符 rune r 转换为 Title 字符。

    unicode.ToTitle('h')
    // 返回 H

    func (SpecialCase) ToTitle

    将字符 rune r 转换为 Title 字符。优先使用映射表 SpecialCase。

    映射表 SpecialCase 是特定语言环境下大小写的映射表。主要应用于一些欧洲字符,例如土耳其 TurkishCase。

    unicode.TurkishCase.ToTitle('i')
    // 返回 İ

    SimpleFold(r rune) rune

    在 unicode 标准字符映射中查找与 rune r 互相对应的 unicode 码值。向码值大的方向循环查找。互相对应指的是同一个字符可能出现的多种写法。

    unicode.SimpleFold('H')
    // 返回 h
    unicode.SimpleFold('Φ')) 
    // 返回 φ

    4 unicode/utf8 包

    DecodeLastRune(p []byte) (r rune, size int)

    解码 []byte p 中最后一个 UTF-8 编码序列,返回该码值和长度。

    utf8.DecodeLastRune([]byte("小韩说课"))
    // 返回 35838 3
    // 35838 就是课的 unicode 码值

    DecodeLastRuneInString(s string) (r rune, size int)

    解码 string s 中最后一个 UTF-8 编码序列,返回该码值和长度。

    utf8.DecodeLastRuneInString("小韩说课")
    // 返回 35838 3
    // 35838 就是课的 unicode 码值

    DecodeRune(p []byte) (r rune, size int)

    解码 []byte p 中第一个 UTF-8 编码序列,返回该码值和长度。

    utf8.DecodeRune([]byte("小韩说课"))
    // 返回 23567 3
    // 23567 就是 小 的 unicode 码值

    DecodeRuneInString(s string) (r rune, size int)

    解码 string s 中第一个 UTF-8 编码序列,返回该码值和长度。

    utf8.DecodeRuneInString("小韩说课")
    // 返回 23567 3
    // 23567 就是 小 的 unicode 码值

    EncodeRune(p []byte, r rune) int

    将 rune r 的 UTF-8 编码序列写入 []byte p,并返回写入的字节数。p 满足足够的长度。

    buf := make([]byte, 3)
    n := utf8.EncodeRune(buf, '康')
    fmt.Println(buf, n)
    // 输出 [229 186 183] 3

    FullRune(p []byte) bool

    检测 []byte p 是否包含一个完整 UTF-8 编码。

    buf := []byte{229, 186, 183} // 康
    utf8.FullRune(buf)
    // 返回 true
    utf8.FullRune(buf[:2])
    // 返回 false

    FullRuneInString(s string) bool

    检测 string s 是否包含一个完整 UTF-8 编码。

    buf := "康" // 康
    utf8.FullRuneInString(buf)
    // 返回 true
    utf8.FullRuneInString(buf[:2])
    // 返回 false

    RuneCount(p []byte) int

    返回 []byte p 中的 UTF-8 编码的码值的个数。

    buf := []byte("小韩说课")
    len(buf)
    // 返回 12
    utf8.RuneCount(buf)
    // 返回 4

    RuneCountInString(s string) (n int)

    返回 string s 中的 UTF-8 编码的码值的个数。

    buf := "小韩说课"
    len(buf)
    // 返回 12
    utf8.RuneCountInString(buf)
    // 返回 4

    RuneLen(r rune) int

    返回 rune r 编码后的字节数。

    utf8.RuneLen('康')
    // 返回 3
    utf8.RuneLen('H')
    // 返回 1

    RuneStart(b byte) bool

    检测字节 byte b 是否可以作为某个 rune 编码的第一个字节。

    buf := "小韩说课"
    utf8.RuneStart(buf[0])
    // 返回 true
    utf8.RuneStart(buf[1])
    // 返回 false
    utf8.RuneStart(buf[3])
    // 返回 true

    Valid(p []byte) bool

    检测切片 []byte p 是否包含完整且合法的 UTF-8 编码序列。

    valid := []byte("小韩说课")
    invalid := []byte{0xff, 0xfe, 0xfd}
    utf8.Valid(valid)
    // 返回 true
    utf8.Valid(invalid)
    // 返回 false

    ValidRune(r rune) bool

    检测字符 rune r 是否包含完整且合法的 UTF-8 编码序列。

    valid := 'a'
    invalid := rune(0xfffffff)
    fmt.Println(utf8.ValidRune(valid))
    // 返回 true
    fmt.Println(utf8.ValidRune(invalid))
    // 返回 false

    ValidString(s string) bool

    检测字符串 string s 是否包含完整且合法的 UTF-8 编码序列。

    valid := "小韩说课"
    invalid := string([]byte{0xff, 0xfe, 0xfd})
    fmt.Println(utf8.ValidString(valid))
    // 返回 true
    fmt.Println(utf8.ValidString(invalid))
    // 返回 false

    完!

    总结

    jsjbwy