unicode 包


unicodeunicode/utf8unicode/utf16是用来判断、处理 Unicode 以及 UTF-8、UTF-16 编码的包,在使用这些包之前,要先知道的是,Go 认为「字符」的定义过于模糊,在 Go 中使用rune存储 Unicode 码(Code point),而 Go 中字符串是 UTF-8 编码的字节组成。

unicode包主要用来判断 Unicode 码的特性(properties),在 Unicode 规范中,每个码会被指定某些特性,具有相同特性的一组码构成一个集合,以便于理解、判断这组码。

例如,General Category特性有 Letter/L 代表字母、Number/N 代表数字等,在 Go 的unicode 包文件的 Variables一开头,列出的就是这类特性的变量:

var (
    ...
    Digit  = _Nd // 十进制数字的集合

    Letter = _L  // 字母集合
    L      = _L
    ...
    Number = _N  // 数字集合
    N      = _N
    ...
}

每个变量的类型都是*RangeTable,由码的范围等字段组成:

type RangeTable struct {
    R16         []Range16   // 用 uint16 记录码低位至高位
    R32         []Range32   // 记录 R16 无法表示的范围,用 uint32 记录码低位至高位
    LatinOffset int 
}

码范围表可以在tables.go找到。举例来说,字母集合的码范围:

var _L = &RangeTable{
    R16: []Range16{
        {0x0041, 0x005a, 1},
        {0x0061, 0x007a, 1},
        {0x00aa, 0x00b5, 11},
        很长的清单...

透过指定RangeTable,就可以简单地判断码是否有某特性,例如,²³¹¼½¾𝟏𝟐𝟑𝟜𝟝𝟞𝟩𝟪𝟫𝟬𝟭𝟮𝟯𝟺𝟻𝟼㉛㉜㉝ⅠⅡⅢⅣⅤⅥⅦⅧⅨⅩⅪⅫⅬⅭⅮⅯⅰⅱⅲⅳⅴⅵⅶⅷⅸⅹⅺⅻⅼⅽⅾⅿ都是数字:

package main

import (
    "fmt"
    "unicode"
)

func allNumbers(s string) bool {
    for _, r := range []rune(s) {
        if !unicode.Is(unicode.Number, r) {
            return false
        }
    }
    return true
}

func main() {
    // true
    fmt.Println(allNumbers("²³¹¼½¾𝟏𝟐𝟑𝟜𝟝𝟞𝟩𝟪𝟫𝟬𝟭𝟮𝟯𝟺𝟻𝟼㉛㉜㉝ⅠⅡⅢⅣⅤⅥⅦⅧⅨⅩⅪⅫⅬⅭⅮⅯⅰⅱⅲⅳⅴⅵⅶⅷⅸⅹⅺⅻⅼⅽⅾⅿ"))
}

Unicode 将希腊文、汉字等以文本(Script)特性标示,在 Go 的unicode 包文件的 Variables第二组列出的变量清单,就是对应的RangeTable,例如unicode.Han是正体中文、简体中文,以及日、韩、越南文的全部汉字范围。

另外还有一些其他特性,列在 Go 的unicode 包文件的 Variables第三组变量清单,例如unicode.White_Space代表被标示为空白特性的码,这包括了半形、全形、Tab 等。

如果想要使用多个 RangeTable,可以透过 IsOneOf

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

unicode也提供了一些常用的判断函数:

func IsControl(r rune) bool
func IsDigit(r rune) bool
func IsGraphic(r rune) bool
func IsLetter(r rune) bool
func IsLower(r rune) bool
func IsMark(r rune) bool
func IsNumber(r rune) bool
func IsPrint(r rune) bool
func IsPunct(r rune) bool
func IsSpace(r rune) bool
func IsSymbol(r rune) bool
func IsTitle(r rune) bool
func IsUpper(r rune) bool

在大小写或特定转换上,有以下的函数:

func To(_case int, r rune) rune
func ToLower(r rune) rune
func ToTitle(r rune) rune
func ToUpper(r rune) rune

基本上,这可以应付大多数语言的转换,像是全形字母的大小写或首字母大写等,To可使用的常数有:

const (
    UpperCase = iota
    LowerCase
    TitleCase
    MaxCase
)

例如,unicode.To(unicode.UpperCase, rune('a'))可以得到'A'

unicode/utf8、unicode/utf16 包

unicode/utf8包提供的函数,主要是进行rune与 UTF-8 编码之间的处理。例如验证是否为合法的 UTF-8[]byte或字符串:

func Valid(p []byte) bool
func ValidString(s string) bool

验证rune可否编码为 UTF-8:

func ValidRune(r rune) bool

rune与 UTF-8 编码之间转换:

func DecodeLastRune(p []byte) (r rune, size int)
func DecodeLastRuneInString(s string) (r rune, size int)
func DecodeRune(p []byte) (r rune, size int)
func DecodeRuneInString(s string) (r rune, size int)
func EncodeRune(p []byte, r rune) int

unicode/utf16主要是进行rune与 UTF-16 编码之间的处理,只不过目前函数只有几个:

func Decode(s []uint16) []rune
func DecodeRune(r1, r2 rune) rune
func Encode(s []rune) []uint16
func EncodeRune(r rune) (r1, r2 rune)
func IsSurrogate(r rune) bool

UTF-8 编码下,码元(code unit)是 8 个位,Go 中使用byte也就是uint8来存储,UTF-16 编码下,码元(code unit)是 16 个位,Go 中使用uint16来存储。

来看个简单的范例,使用unicode/utf8unicode/utf16包来显示「Hello, 世界」的 UTF-16 码元:

package main

import (
    "fmt"
    "unicode/utf8"
    "unicode/utf16"
)

func main() {    
    b := []byte("Hello, 世界")

    for len(b) > 0 {
        r, size := utf8.DecodeRune(b)
        u16 := utf16.Encode([]rune{r})
        fmt.Printf("%#U:\n  Code unit %04X\n", r, u16)
        b = b[size:]
    }
}

显示结果如下:

U+0048 'H':       
  Code unit [0048]
U+0065 'e':       
  Code unit [0065]
U+006C 'l':       
  Code unit [006C]
U+006C 'l':       
  Code unit [006C]
U+006F 'o':
  Code unit [006F]
U+002C ',':
  Code unit [002C]
U+0020 ' ':
  Code unit [0020]
U+4E16 '世':
  Code unit [4E16]
U+754C '界':
  Code unit [754C]

Unicode 码号码与码元显示刚好一样对吧?这就是为什么常有人会乱说「Unicode 使用 16 位存储」的原因之一吧!… XD


展开阅读全文