unicode/utf8 包详解
概述
unicode/utf8 包实现了支持 UTF-8 编码文本的函数和常量。它包括在 rune 和 UTF-8 字节序列之间转换的函数。
主要用途:
- UTF-8 编码和解码
- rune 与字节序列转换
- UTF-8 有效性验证
- 计算 rune 数量和长度
- 处理 UTF-8 字符串
核心概念:
- rune:Go 中的 Unicode 码点类型(int32 的别名)
- UTF-8:变长字符编码,每个 rune 占用 1-4 个字节
- 编码:将 rune 转换为 UTF-8 字节序列
- 解码:将 UTF-8 字节序列转换为 rune
参考:https://en.wikipedia.org/wiki/UTF-8
包导入
import "unicode/utf8"
常量详解
基本编码常量
const (
RuneError = '\uFFFD' // 替换字符(用于无效 UTF-8)
RuneSelf = 0x80 // 自编码的 rune 范围上限
MaxRune = '\U0010FFFF' // 最大 Unicode 码点
UTFMax = 4 // UTF-8 编码的最大字节数
)
说明:
RuneError:替换字符,用于表示无效的 UTF-8 编码()RuneSelf:小于此值的 rune 使用单字节编码(ASCII 兼容)MaxRune:Unicode 标准定义的最大码点值UTFMax:UTF-8 编码一个 rune 所需的最大字节数
示例:
fmt.Printf("RuneError: %c (U+%04X)\n", unicode/utf8.RuneError, unicode/utf8.RuneError)
fmt.Printf("RuneSelf: 0x%X\n", unicode/utf8.RuneSelf)
fmt.Printf("MaxRune: U+%04X\n", unicode/utf8.MaxRune)
fmt.Printf("UTFMax: %d bytes\n", unicode/utf8.UTFMax)
// 输出:
// RuneError: (U+FFFD)
// RuneSelf: 0x80
// MaxRune: U+10FFFF
// UTFMax: 4 bytes
函数详解(按 A-Z 分层归类)
A
AppendRune
func AppendRune(p []byte, r rune) []byte
作用:将 r 的 UTF-8 编码追加到 p 的末尾并返回扩展后的缓冲区
参数说明:
p:目标字节切片r:要编码的 rune
返回值:
- 扩展后的字节切片
说明:
- 如果 rune 超出范围,会追加 RuneError 的编码
- p 不需要预先分配空间,会自动扩展
示例:
// 基本用法
p := []byte("init")
p = utf8.AppendRune(p, '𐀀') // U+10000
fmt.Println(string(p))
// 输出:init𐀀
// 追加多个 rune
var buf []byte
buf = utf8.AppendRune(buf, 'H')
buf = utf8.AppendRune(buf, 'i')
buf = utf8.AppendRune(buf, '!')
fmt.Println(string(buf))
// 输出:Hi!
// 处理无效 rune
buf = utf8.AppendRune(buf, unicode/utf8.MaxRune+1)
fmt.Println(string(buf))
// 输出:Hi! (追加了 RuneError)
D
DecodeLastRune
func DecodeLastRune(p []byte) (r rune, size int)
作用:解包 p 中最后一个 UTF-8 编码并返回 rune 及其字节宽度
参数说明:
p:UTF-8 编码的字节切片
返回值:
r:解码后的 runesize:rune 的字节宽度
说明:
- 如果 p 为空,返回
(RuneError, 0) - 如果编码无效,返回
(RuneError, 1) - 无效编码包括:不正确的 UTF-8、超出范围的 rune、非最短编码
示例:
// 解码字符串的最后一个字符
s := []byte("Hello, 世界")
for len(s) > 0 {
r, size := utf8.DecodeLastRune(s)
fmt.Printf("%c %d\n", r, size)
s = s[:len(s)-size]
}
// 输出(反向):
// 界 3
// 世 3
// 1
// , 1
// o 1
// l 1
// l 1
// e 1
// H 1
// 处理空切片
r, size := utf8.DecodeLastRune([]byte{})
fmt.Printf("r=%c, size=%d\n", r, size)
// 输出:r=, size=0
// 处理无效 UTF-8
invalid := []byte{0xFF, 0xFE}
r, size = utf8.DecodeLastRune(invalid)
fmt.Printf("r=%c, size=%d\n", r, size)
// 输出:r=, size=1
DecodeLastRuneInString
func DecodeLastRuneInString(s string) (r rune, size int)
作用:与 DecodeLastRune 类似,但输入是字符串
参数说明:
s:UTF-8 编码的字符串
返回值:
r:解码后的 runesize:rune 的字节宽度
说明:
- 如果 s 为空,返回
(RuneError, 0) - 如果编码无效,返回
(RuneError, 1)
示例:
// 反向遍历字符串
str := "Hello, 世界"
for len(str) > 0 {
r, size := utf8.DecodeLastRuneInString(str)
fmt.Printf("%c %d\n", r, size)
str = str[:len(str)-size]
}
// 输出(反向):
// 界 3
// 世 3
// 1
// , 1
// o 1
// l 1
// l 1
// e 1
// H 1
// 获取字符串的最后一个字符
last, size := utf8.DecodeLastRuneInString("你好")
fmt.Printf("Last char: %c, size: %d\n", last, size)
// 输出:Last char: 好,size: 3
DecodeRune
func DecodeRune(p []byte) (r rune, size int)
作用:解包 p 中第一个 UTF-8 编码并返回 rune 及其字节宽度
参数说明:
p:UTF-8 编码的字节切片
返回值:
r:解码后的 runesize:rune 的字节宽度
说明:
- 如果 p 为空,返回
(RuneError, 0) - 如果编码无效,返回
(RuneError, 1) - 无效编码包括:不正确的 UTF-8、超出范围的 rune、非最短编码
示例:
// 正向遍历字节切片
p := []byte("Hello, 世界")
for len(p) > 0 {
r, size := utf8.DecodeRune(p)
fmt.Printf("%c %d\n", r, size)
p = p[size:]
}
// 输出:
// H 1
// e 1
// l 1
// l 1
// o 1
// , 1
// 1
// 世 3
// 界 3
// 处理空切片
r, size := utf8.DecodeRune([]byte{})
fmt.Printf("r=%c, size=%d\n", r, size)
// 输出:r=, size=0
// 处理无效 UTF-8
invalid := []byte{0x80, 0x81}
r, size = utf8.DecodeRune(invalid)
fmt.Printf("r=%c, size=%d\n", r, size)
// 输出:r=, size=1
DecodeRuneInString
func DecodeRuneInString(s string) (r rune, size int)
作用:与 DecodeRune 类似,但输入是字符串
参数说明:
s:UTF-8 编码的字符串
返回值:
r:解码后的 runesize:rune 的字节宽度
说明:
- 如果 s 为空,返回
(RuneError, 0) - 如果编码无效,返回
(RuneError, 1)
示例:
// 正向遍历字符串
str := "Hello, 世界"
for i := 0; i < len(str); {
r, size := utf8.DecodeRuneInString(str[i:])
fmt.Printf("%c %d\n", r, size)
i += size
}
// 输出:
// H 1
// e 1
// l 1
// l 1
// o 1
// , 1
// 1
// 世 3
// 界 3
// 获取字符串的第一个字符
first, size := utf8.DecodeRuneInString("你好")
fmt.Printf("First char: %c, size: %d\n", first, size)
// 输出:First char: 你,size: 3
E
EncodeRune
func EncodeRune(p []byte, r rune) int
作用:将 r 的 UTF-8 编码写入 p(p 必须足够大)
参数说明:
p:目标字节切片(必须至少为 UTFMax=4 字节)r:要编码的 rune
返回值:
- 写入的字节数
说明:
- 如果 rune 超出范围,会写入 RuneError 的编码
- p 必须足够大(至少 4 字节)
示例:
// 编码单个 rune
p := make([]byte, utf8.UTFMax)
n := utf8.EncodeRune(p, '世')
fmt.Printf("%v\n", p[:n])
fmt.Printf("%d\n", n)
// 输出:
// [228 184 150]
// 3
// 编码多个 rune
var buf [utf8.UTFMax * 3]byte
offset := 0
offset += utf8.EncodeRune(buf[offset:], 'H')
offset += utf8.EncodeRune(buf[offset:], 'i')
offset += utf8.EncodeRune(buf[offset:], '!')
fmt.Println(string(buf[:offset]))
// 输出:Hi!
// 处理无效 rune(超出范围)
p = make([]byte, utf8.UTFMax)
n = utf8.EncodeRune(p, unicode/utf8.MaxRune+1)
fmt.Printf("%d: %v %c %d\n", 0, p[:n], p[:n], n)
// 输出:0: [239 191 189] 3
F
FullRune
func FullRune(p []byte) bool
作用:报告 p 中的字节是否以完整的 UTF-8 rune 编码开始
参数说明:
p:UTF-8 编码的字节切片
返回值:
- 如果是完整的 rune 编码返回 true,否则返回 false
说明:
- 无效编码也被视为完整的 rune(因为会转换为宽度为 1 的错误 rune)
示例:
// 完整的多字节 rune
p := []byte("世") // 3 字节
fmt.Println(utf8.FullRune(p))
// 输出:true
// 不完整的 UTF-8 序列
p = []byte{0xE4, 0xB8} // 只有 2 字节,需要 3 字节
fmt.Println(utf8.FullRune(p))
// 输出:false
// 完整的单字节 ASCII
p = []byte("A")
fmt.Println(utf8.FullRune(p))
// 输出:true
// 空切片
p = []byte{}
fmt.Println(utf8.FullRune(p))
// 输出:false
FullRuneInString
func FullRuneInString(s string) bool
作用:与 FullRune 类似,但输入是字符串
参数说明:
s:UTF-8 编码的字符串
返回值:
- 如果是完整的 rune 编码返回 true,否则返回 false
示例:
// 完整的字符串
fmt.Println(utf8.FullRuneInString("世"))
// 输出:true
// 不完整的 UTF-8 序列(通过字节转换)
s := string([]byte{0xE4, 0xB8}) // 不完整的 3 字节序列
fmt.Println(utf8.FullRuneInString(s))
// 输出:false
// 空字符串
fmt.Println(utf8.FullRuneInString(""))
// 输出:false
R
RuneCount
func RuneCount(p []byte) int
作用:返回 p 中的 rune 数量
参数说明:
p:UTF-8 编码的字节切片
返回值:
- rune 数量
说明:
- 错误和短编码被视为单个 rune(宽度为 1 字节)
示例:
// 计算 rune 数量
p := []byte("Hello, 世界")
fmt.Printf("bytes = %d\n", len(p))
fmt.Printf("runes = %d\n", utf8.RuneCount(p))
// 输出:
// bytes = 13
// runes = 9
// 空切片
fmt.Println(utf8.RuneCount([]byte{}))
// 输出:0
// 包含无效 UTF-8
invalid := []byte{0x80, 0x81, 'A'}
fmt.Println(utf8.RuneCount(invalid))
// 输出:3 (每个无效字节算作 1 个 rune)
RuneCountInString
func RuneCountInString(s string) int
作用:与 RuneCount 类似,但输入是字符串
参数说明:
s:UTF-8 编码的字符串
返回值:
- rune 数量
示例:
// 计算字符串的 rune 数量
s := "Hello, 世界"
fmt.Printf("bytes = %d\n", len(s))
fmt.Printf("runes = %d\n", utf8.RuneCountInString(s))
// 输出:
// bytes = 13
// runes = 9
// 空字符串
fmt.Println(utf8.RuneCountInString(""))
// 输出:0
// 只有 emoji
s = "😀😃😄"
fmt.Printf("bytes = %d\n", len(s))
fmt.Printf("runes = %d\n", utf8.RuneCountInString(s))
// 输出:bytes = 12, runes = 3
RuneLen
func RuneLen(r rune) int
作用:返回 rune 的 UTF-8 编码的字节数
参数说明:
r:要计算的 rune
返回值:
- 字节数
- 如果 rune 不是有效的 UTF-8 编码值,返回 -1
示例:
// ASCII 字符
fmt.Println(utf8.RuneLen('A'))
// 输出:1
// 中文字符(通常在 U+0800 到 U+FFFF 之间)
fmt.Println(utf8.RuneLen('中'))
// 输出:3
// emoji(通常在 U+10000 以上)
fmt.Println(utf8.RuneLen('😀'))
// 输出:4
// 无效 rune(超出范围)
fmt.Println(utf8.RuneLen(unicode/utf8.MaxRune + 1))
// 输出:-1
// 代理对(无效)
fmt.Println(utf8.RuneLen(0xD800))
// 输出:-1
RuneStart
func RuneStart(b byte) bool
作用:报告字节是否可能是编码的(可能无效的)rune 的第一个字节
参数说明:
b:要检查的字节
返回值:
- 如果可能是 rune 的第一个字节返回 true,否则返回 false
说明:
- 第二个及后续字节的前两位总是设置为 10
- 第一个字节的前两位不是 10
示例:
// ASCII 字符(0x00-0x7F)
fmt.Println(utf8.RuneStart('A')) // 0x41
// 输出:true
// 多字节序列的第一个字节
fmt.Println(utf8.RuneStart(0xC0)) // 110xxxxx
// 输出:true
fmt.Println(utf8.RuneStart(0xE0)) // 1110xxxx
// 输出:true
fmt.Println(utf8.RuneStart(0xF0)) // 11110xxx
// 输出:true
// continuation 字节(10xxxxxx)
fmt.Println(utf8.RuneStart(0x80))
// 输出:false
fmt.Println(utf8.RuneStart(0xBF))
// 输出:false
V
Valid
func Valid(p []byte) bool
作用:报告 p 是否完全由有效的 UTF-8 编码的 rune 组成
参数说明:
p:要验证的字节切片
返回值:
- 如果完全有效返回 true,否则返回 false
示例:
// 有效的 UTF-8
p := []byte("Hello, 世界")
fmt.Println(utf8.Valid(p))
// 输出:true
// 无效的 UTF-8
invalid := []byte{0xFF, 0xFE}
fmt.Println(utf8.Valid(invalid))
// 输出:false
// 不完整的序列
incomplete := []byte{0xE4, 0xB8} // 缺少最后一个字节
fmt.Println(utf8.Valid(incomplete))
// 输出:false
// 空切片是有效的
fmt.Println(utf8.Valid([]byte{}))
// 输出:true
ValidRune
func ValidRune(r rune) bool
作用:报告 r 是否可以合法地编码为 UTF-8
参数说明:
r:要检查的 rune
返回值:
- 如果可以合法编码返回 true,否则返回 false
说明:
- 超出范围的码点是非法的
- 代理对的一半是非法的(U+D800 到 U+DFFF)
示例:
// 有效的 rune
fmt.Println(utf8.ValidRune('A'))
// 输出:true
fmt.Println(utf8.ValidRune('中'))
// 输出:true
fmt.Println(utf8.ValidRune(unicode/utf8.MaxRune))
// 输出:true
// 无效的 rune(超出范围)
fmt.Println(utf8.ValidRune(unicode/utf8.MaxRune + 1))
// 输出:false
// 无效的 rune(代理对)
fmt.Println(utf8.ValidRune(0xD800)) // 高代理
// 输出:false
fmt.Println(utf8.ValidRune(0xDFFF)) // 低代理
// 输出:false
ValidString
func ValidString(s string) bool
作用:报告 s 是否完全由有效的 UTF-8 编码的 rune 组成
参数说明:
s:要验证的字符串
返回值:
- 如果完全有效返回 true,否则返回 false
示例:
// 有效的 UTF-8 字符串
fmt.Println(utf8.ValidString("Hello, 世界"))
// 输出:true
// 通过无效字节构造的字符串
invalid := string([]byte{0xFF, 0xFE})
fmt.Println(utf8.ValidString(invalid))
// 输出:false
// 空字符串是有效的
fmt.Println(utf8.ValidString(""))
// 输出:true
类型详解
unicode/utf8 包不导出任何类型,所有功能通过函数提供。
典型示例
1. 遍历 UTF-8 字符串
package main
import (
"fmt"
"unicode/utf8"
)
func main() {
str := "Hello, 世界!"
// 方法 1:使用 range(推荐)
fmt.Println("Using range:")
for i, r := range str {
fmt.Printf("Index %d: %c (U+%04X)\n", i, r, r)
}
// 方法 2:使用 DecodeRuneInString
fmt.Println("\nUsing DecodeRuneInString:")
for i := 0; i < len(str); {
r, size := utf8.DecodeRuneInString(str[i:])
fmt.Printf("Index %d: %c (size=%d)\n", i, r, size)
i += size
}
}
2. 反向遍历字符串
package main
import (
"fmt"
"unicode/utf8"
)
func reverseString(s string) string {
runes := make([]rune, 0, utf8.RuneCountInString(s))
for len(s) > 0 {
r, size := utf8.DecodeLastRuneInString(s)
runes = append(runes, r)
s = s[:len(s)-size]
}
return string(runes)
}
func main() {
str := "Hello, 世界!"
reversed := reverseString(str)
fmt.Printf("Original: %s\n", str)
fmt.Printf("Reversed: %s\n", reversed)
// 输出:Original: Hello, 世界!
// Reversed: !界世,olleH
}
3. 计算字符串的字节数和 rune 数
package main
import (
"fmt"
"unicode/utf8"
)
func main() {
strings := []string{
"Hello",
"世界",
"😀😃😄",
"Hello, 世界!",
}
for _, s := range strings {
fmt.Printf("%q:\n", s)
fmt.Printf(" Bytes: %d\n", len(s))
fmt.Printf(" Runes: %d\n", utf8.RuneCountInString(s))
fmt.Printf(" Avg bytes per rune: %.2f\n",
float64(len(s))/float64(utf8.RuneCountInString(s)))
fmt.Println()
}
}
4. 验证 UTF-8 编码
package main
import (
"fmt"
"unicode/utf8"
)
func validateUTF8(data []byte) error {
if !utf8.Valid(data) {
return fmt.Errorf("invalid UTF-8 encoding")
}
return nil
}
func main() {
// 有效的 UTF-8
valid := []byte("Hello, 世界")
if err := validateUTF8(valid); err != nil {
fmt.Printf("Error: %v\n", err)
} else {
fmt.Println("Valid UTF-8")
}
// 无效的 UTF-8
invalid := []byte{0xFF, 0xFE, 0xFD}
if err := validateUTF8(invalid); err != nil {
fmt.Printf("Error: %v\n", err)
} else {
fmt.Println("Valid UTF-8")
}
}
5. 安全地截取字符串
package main
import (
"fmt"
"unicode/utf8"
)
// 安全地截取字符串,确保不截断多字节字符
func safeSubstring(s string, start, length int) string {
if start >= len(s) {
return ""
}
end := start + length
if end > len(s) {
end = len(s)
}
// 确保不在多字节字符中间截断
for end < len(s) && !utf8.RuneStart(s[end]) {
end++
}
return s[start:end]
}
func main() {
s := "Hello, 世界!"
// 正常截取
fmt.Println(safeSubstring(s, 0, 5)) // Hello
// 可能截断多字节字符
fmt.Println(safeSubstring(s, 7, 2)) // 世界(自动调整)
// 从中间开始
fmt.Println(safeSubstring(s, 7, 10)) // 世界!
}
6. 编码和解码 rune
package main
import (
"fmt"
"unicode/utf8"
)
func main() {
runes := []rune{'A', '中', '😀', '𐀀'}
for _, r := range runes {
// 编码
buf := make([]byte, utf8.UTFMax)
n := utf8.EncodeRune(buf, r)
// 解码
decoded, _ := utf8.DecodeRune(buf[:n])
fmt.Printf("Rune: %c (U+%04X)\n", r, r)
fmt.Printf(" Encoded: %v (%d bytes)\n", buf[:n], n)
fmt.Printf(" Decoded: %c\n", decoded)
fmt.Printf(" Match: %v\n\n", r == decoded)
}
}
7. 使用 AppendRune 构建字符串
package main
import (
"fmt"
"unicode/utf8"
)
func main() {
// 高效地构建 UTF-8 字符串
var buf []byte
runes := []rune{'H', 'e', 'l', 'l', 'o', ',', ' ', '世', '界', '!'}
for _, r := range runes {
buf = utf8.AppendRune(buf, r)
}
fmt.Println(string(buf))
// 输出:Hello, 世界!
// 与 strings.Builder 比较
fmt.Printf("Bytes: %d\n", len(buf))
fmt.Printf("Runes: %d\n", utf8.RuneCount(buf))
}
8. 检查 rune 长度
package main
import (
"fmt"
"unicode/utf8"
)
func main() {
testRunes := []rune{
'A', // ASCII
'é', // Latin-1
'中', // CJK
'😀', // Emoji
'𐀀', // Supplementary
0xD800, // Invalid (surrogate)
utf8.MaxRune + 1, // Invalid (out of range)
}
for _, r := range testRunes {
length := utf8.RuneLen(r)
fmt.Printf("%c (U+%04X): %d bytes\n", r, r, length)
}
}
9. 处理不完整的 UTF-8 序列
package main
import (
"fmt"
"unicode/utf8"
)
func main() {
// 完整的 UTF-8 序列
complete := []byte("中") // 3 字节
fmt.Printf("Complete: %v\n", utf8.FullRune(complete))
// 不完整的 UTF-8 序列
incomplete1 := []byte{0xE4} // 只有 1 字节
incomplete2 := []byte{0xE4, 0xB8} // 只有 2 字节
fmt.Printf("Incomplete 1 byte: %v\n", utf8.FullRune(incomplete1))
fmt.Printf("Incomplete 2 bytes: %v\n", utf8.FullRune(incomplete2))
// 处理流式数据
data := []byte{0xE4, 0xB8} // 不完整的"中"
fmt.Printf("Has full rune: %v\n", utf8.FullRune(data))
// 添加缺失的字节
data = append(data, 0xAD)
fmt.Printf("After adding byte: %v\n", utf8.FullRune(data))
// 解码
r, size := utf8.DecodeRune(data)
fmt.Printf("Decoded: %c (size=%d)\n", r, size)
}
10. 统计不同类型字符的数量
package main
import (
"fmt"
"unicode"
"unicode/utf8"
)
func main() {
str := "Hello, 世界!123"
var letters, digits, spaces, punctuation, others int
for i := 0; i < len(str); {
r, size := utf8.DecodeRuneInString(str[i:])
i += size
switch {
case unicode.IsLetter(r):
letters++
case unicode.IsDigit(r):
digits++
case unicode.IsSpace(r):
spaces++
case unicode.IsPunct(r):
punctuation++
default:
others++
}
}
fmt.Printf("String: %q\n", str)
fmt.Printf("Bytes: %d, Runes: %d\n", len(str), utf8.RuneCountInString(str))
fmt.Printf("Letters: %d\n", letters)
fmt.Printf("Digits: %d\n", digits)
fmt.Printf("Spaces: %d\n", spaces)
fmt.Printf("Punctuation: %d\n", punctuation)
fmt.Printf("Others: %d\n", others)
}
11. 转换大小写
package main
import (
"fmt"
"unicode"
"unicode/utf8"
)
func toUpper(s string) string {
buf := make([]byte, 0, len(s))
for i := 0; i < len(s); {
r, size := utf8.DecodeRuneInString(s[i:])
i += size
upper := unicode.ToUpper(r)
buf = utf8.AppendRune(buf, upper)
}
return string(buf)
}
func main() {
str := "hello, 世界!"
upper := toUpper(str)
fmt.Printf("Original: %s\n", str)
fmt.Printf("Uppercase: %s\n", upper)
// 输出:Original: hello, 世界!
// Uppercase: HELLO, 世界!
}
12. 查找 rune 在字符串中的位置
package main
import (
"fmt"
"unicode/utf8"
)
func indexRune(s string, target rune) int {
for i := 0; i < len(s); {
r, size := utf8.DecodeRuneInString(s[i:])
if r == target {
return i
}
i += size
}
return -1
}
func main() {
str := "Hello, 世界!"
pos1 := indexRune(str, 'H')
pos2 := indexRune(str, '世')
pos3 := indexRune(str, '!')
pos4 := indexRune(str, 'X')
fmt.Printf("Position of 'H': %d\n", pos1)
fmt.Printf("Position of '世': %d\n", pos2)
fmt.Printf("Position of '!': %d\n", pos3)
fmt.Printf("Position of 'X': %d\n", pos4)
}
最佳实践
1. 使用 range 遍历字符串
// 推荐:使用 range
for i, r := range str {
// i 是字节索引,r 是 rune
}
// 不推荐:直接索引
for i := 0; i < len(str); i++ {
ch := str[i] // 获取的是字节,不是 rune
}
2. 使用 RuneCountInString 获取 rune 数量
// 推荐
count := utf8.RuneCountInString(s)
// 不推荐(效率低)
count := len([]rune(s))
3. 验证 UTF-8 编码
// 在处理外部数据时验证
if !utf8.Valid(data) {
return fmt.Errorf("invalid UTF-8")
}
// 或使用 ValidString
if !utf8.ValidString(s) {
return fmt.Errorf("invalid UTF-8 string")
}
4. 使用 AppendRune 高效构建
// 推荐:使用 AppendRune
var buf []byte
for _, r := range runes {
buf = utf8.AppendRune(buf, r)
}
// 或使用 strings.Builder
var builder strings.Builder
for _, r := range runes {
builder.WriteRune(r)
}
5. 检查 rune 起始字节
// 在流式处理中检查完整 rune
if utf8.RuneStart(b) {
// 是新 rune 的开始
} else {
// 是 continuation 字节
}
6. 处理不完整的 UTF-8
// 在读取流式数据时
for len(data) > 0 {
if !utf8.FullRune(data) {
// 等待更多数据
break
}
r, size := utf8.DecodeRune(data)
// 处理 r
data = data[size:]
}
与其他包配合
unicode 包
import (
"unicode"
"unicode/utf8"
)
// 结合使用进行字符处理
for i := 0; i < len(s); {
r, size := utf8.DecodeRuneInString(s[i:])
i += size
if unicode.IsLetter(r) {
// 处理字母
}
}
strings 包
import (
"strings"
"unicode/utf8"
)
// 使用 strings.Builder 高效构建
var builder strings.Builder
builder.Grow(utf8.UTFMax * len(runes))
for _, r := range runes {
builder.WriteRune(r)
}
result := builder.String()
bytes 包
import (
"bytes"
"unicode/utf8"
)
// 使用 bytes.Buffer
var buf bytes.Buffer
buf.Grow(utf8.UTFMax * 10)
utf8.EncodeRune(buf.Bytes(), 'A')
bufio 包
import (
"bufio"
"unicode/utf8"
)
// 读取 UTF-8 文本
reader := bufio.NewReader(file)
for {
r, _, err := reader.ReadRune()
if err != nil {
break
}
// 处理 r
}
注意事项
1. len() 返回字节数,不是 rune 数
s := "你好"
fmt.Println(len(s)) // 6 (字节数)
fmt.Println(utf8.RuneCountInString(s)) // 2 (rune 数)
2. 字符串索引访问的是字节
s := "你好"
fmt.Println(s[0]) // 228 (字节值,不是 '你')
fmt.Println(s[1]) // 189
// 正确访问第一个字符
r, _ := utf8.DecodeRuneInString(s)
fmt.Println(r) // 你
3. 无效 UTF-8 的处理
// DecodeRune 返回 (RuneError, 1) 表示无效
invalid := []byte{0xFF, 0xFE}
r, size := utf8.DecodeRune(invalid)
fmt.Printf("r=%c, size=%d\n", r, size)
// 输出:r=, size=1
4. 代理对是无效的
// UTF-8 不允许代理对
fmt.Println(utf8.ValidRune(0xD800)) // false
fmt.Println(utf8.ValidRune(0xDFFF)) // false
5. 非最短编码是无效的
// 使用非最短编码会被拒绝
// 例如:用 2 字节编码 ASCII 字符
invalid := []byte{0xC0, 0x80} // 试图编码 NUL
r, size := utf8.DecodeRune(invalid)
fmt.Printf("r=%c, size=%d\n", r, size)
// 输出:r=, size=1
6. 空输入的处理
// 空输入返回特殊值
r, size := utf8.DecodeRune([]byte{})
fmt.Printf("r=%c, size=%d\n", r, size)
// 输出:r=, size=0
fmt.Println(utf8.FullRune([]byte{})) // false
fmt.Println(utf8.Valid([]byte{})) // true
7. EncodeRune 需要足够的空间
// p 必须至少为 UTFMax (4) 字节
p := make([]byte, utf8.UTFMax)
n := utf8.EncodeRune(p, 'A')
// 如果空间不足会 panic
// p := make([]byte, 1)
// utf8.EncodeRune(p, '中') // panic!
快速参考
常量速查表
| 常量 | 值 | 说明 |
|---|---|---|
RuneError | U+FFFD | 替换字符 |
RuneSelf | 0x80 | 自编码范围上限 |
MaxRune | U+10FFFF | 最大 Unicode 码点 |
UTFMax | 4 | 最大字节数 |
函数速查表
| 函数 | 说明 |
|---|---|
AppendRune | 追加 rune 编码到字节切片 |
DecodeLastRune | 解码最后一个 rune |
DecodeLastRuneInString | 解码字符串的最后一个 rune |
DecodeRune | 解码第一个 rune |
DecodeRuneInString | 解码字符串的第一个 rune |
EncodeRune | 编码 rune 到字节切片 |
FullRune | 检查是否有完整 rune |
FullRuneInString | 检查字符串是否有完整 rune |
RuneCount | 计算 rune 数量 |
RuneCountInString | 计算字符串的 rune 数量 |
RuneLen | 获取 rune 的编码长度 |
RuneStart | 检查是否是 rune 起始字节 |
Valid | 验证 UTF-8 编码 |
ValidRune | 验证 rune 是否可编码 |
ValidString | 验证 UTF-8 字符串 |
UTF-8 编码规则
| Unicode 范围 | UTF-8 编码 | 字节数 |
|---|---|---|
| U+0000 - U+007F | 0xxxxxxx | 1 |
| U+0080 - U+07FF | 110xxxxx 10xxxxxx | 2 |
| U+0800 - U+FFFF | 1110xxxx 10xxxxxx 10xxxxxx | 3 |
| U+10000 - U+10FFFF | 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx | 4 |
常见模式
// 遍历字符串
for i, r := range str {
// i 是字节索引,r 是 rune
}
// 计算 rune 数量
count := utf8.RuneCountInString(s)
// 获取第一个字符
r, size := utf8.DecodeRuneInString(s)
// 获取最后一个字符
r, size := utf8.DecodeLastRuneInString(s)
// 验证 UTF-8
if !utf8.Valid(data) {
// 处理错误
}
// 编码 rune
buf := make([]byte, utf8.UTFMax)
n := utf8.EncodeRune(buf, r)
// 追加 rune
buf = utf8.AppendRune(buf, r)
// 检查完整 rune
if utf8.FullRune(data) {
// 有完整的 rune
}
总结
unicode/utf8 包提供了完整的 UTF-8 编码支持功能:
核心功能:
- rune 与 UTF-8 字节序列的相互转换
- UTF-8 编码验证
- rune 数量和长度计算
- 完整的和不完整的 UTF-8 序列处理
主要函数:
- 编码:
EncodeRune、AppendRune - 解码:
DecodeRune、DecodeLastRune、DecodeRuneInString、DecodeLastRuneInString - 验证:
Valid、ValidString、ValidRune - 计算:
RuneCount、RuneCountInString、RuneLen - 检查:
FullRune、FullRuneInString、RuneStart
常量:
RuneError:替换字符RuneSelf:自编码范围上限MaxRune:最大 Unicode 码点UTFMax:最大字节数(4)
使用建议:
- 使用 range 遍历字符串
- 使用 RuneCountInString 获取 rune 数量
- 验证外部数据的 UTF-8 编码
- 使用 AppendRune 高效构建字符串
- 注意 len() 返回的是字节数
- 处理流式数据时检查完整 rune
典型用法:
// 遍历字符串
for i, r := range str {
fmt.Printf("%c (U+%04X) at byte %d\n", r, r, i)
}
// 编码和解码
buf := make([]byte, utf8.UTFMax)
n := utf8.EncodeRune(buf, '中')
r, _ := utf8.DecodeRune(buf[:n])
// 验证
if !utf8.ValidString(s) {
// 处理无效 UTF-8
}
通过 unicode/utf8 包,可以方便地处理 UTF-8 编码的文本,支持国际化应用程序的开发。