unicode
、unicode/utf8
、unicode/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/utf8
与unicode/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