引入
字符串里提到 UTF-8 编码和 range 遍历拿到的是字符而不是字节。那单个字符在 Go 里到底是什么类型?答案是 rune 和 byte。rune 代表一个 Unicode 码点,byte 代表一个字节。搞清楚这两个类型的区别,才能正确处理中文、emoji 等多字节字符。
正文
定义
定义
rune是int32的别名,代表一个 Unicode 码点(即一个字符)。byte是uint8的别名,代表一个字节。字符串在内存中以byte序列存储(UTF-8 编码),但逻辑上由rune序列组成。
语法
// rune:单个字符(Unicode 码点)
var r rune = 'A' // 65
var r2 rune = '你' // 20320
// byte:单个字节
var b byte = 'A' // 65
var b2 byte = 0xFF // 255例子
rune 是什么
var r1 rune = 'A'
var r2 rune = '你'
var r3 rune = '😀'
fmt.Printf("%c %U %d\n", r1, r1, r1) // A U+0041 65
fmt.Printf("%c %U %d\n", r2, r2, r2) // 你 U+4F60 20320
fmt.Printf("%c %U %d\n", r3, r3, r3) // 😀 U+1F600 128512rune 本质上就是 int32,每个 rune 值对应 Unicode 标准里的一个码点。用单引号 '…' 写出来的是 rune 字面量。
| 字符 | Unicode 码点 | rune 值 |
|---|---|---|
'A' | U+0041 | 65 |
'你' | U+4F60 | 20320 |
'😀' | U+1F600 | 128512 |
byte 是什么
var b1 byte = 'A' // 65
var b2 byte = 255 // byte 最大值
// var b3 byte = 256 // 编译报错:溢出
fmt.Printf("%c %d\n", b1, b1) // A 65byte 就是 uint8,范围 0 ~ 255。它代表一个字节的数据,是字符串在内存中的基本单位。
字符串的 byte 和 rune
s := "你好"
// 按 byte 遍历(拿到每个字节)
for i := 0; i < len(s); i++ {
fmt.Printf("byte[%d] = %d\n", i, s[i])
}
// byte[0] = 228
// byte[1] = 189
// byte[2] = 160
// byte[3] = 229
// byte[4] = 165
// byte[5] = 177
// 按 rune 遍历(拿到每个字符)
for i, r := range s {
fmt.Printf("rune[%d] = %c (%U)\n", i, r, r)
}
// rune[0] = 你 (U+4F60)
// rune[3] = 好 (U+597D)len(s) 返回字节数(6),range 遍历拿到的是 rune(2 个字符)。注意 range 返回的索引是字节偏移量,不是字符索引。
UTF-8 编码
s := "A你😀"
// 每个字符占几个字节?
fmt.Printf("字节数: %d\n", len(s)) // 8
fmt.Printf("字符数: %d\n", utf8.RuneCountInString(s)) // 3| 字符 | UTF-8 字节数 |
|---|---|
| ASCII(A-Z, 0-9 等) | 1 字节 |
| 中文、日文等 | 3 字节 |
| emoji 等补充平面字符 | 4 字节 |
UTF-8 是变长编码,ASCII 字符占 1 字节,和 ASCII 完全兼容。
字符串和 rune 切片互转
s := "你好"
// 字符串转 rune 切片
runes := []rune(s) // [20320 22909]
fmt.Println(len(runes)) // 2(字符数)
// rune 切片转字符串
s2 := string(runes) // "你好"
// 用 rune 切片修改字符串中的某个字符
runes[0] = '我'
s3 := string(runes) // "我好" → 不对,是"我好"因为字符串不可变,要修改某个字符就得先转成 []rune,改完再转回来。
unicode 包
import "unicode"
unicode.IsLetter('A') // true
unicode.IsDigit('9') // true
unicode.IsUpper('A') // true
unicode.IsLower('a') // true
unicode.IsSpace(' ') // true
unicode.Is(unicode.Han, '你') // true(判断是否为汉字)unicode 包提供了判断和操作 Unicode 字符的函数。
常见写法
| 写法 | 说明 |
|---|---|
'A' | rune 字面量 |
var r rune = '你' | 声明 rune 变量 |
var b byte = 65 | 声明 byte 变量 |
[]rune(s) | 字符串转 rune 切片 |
[]byte(s) | 字符串转字节切片 |
string(r) | rune 转字符串 |
utf8.RuneCountInString(s) | 获取字符数 |
range s | 按 rune 遍历字符串 |
特点
rune就是int32的别名,代表一个 Unicode 码点byte就是uint8的别名,代表一个字节- 单引号
'A'是rune,双引号"A"是string - 字符串按 UTF-8 编码存储,不同字符占 1~4 字节不等
len(s)返回字节数,utf8.RuneCountInString(s)返回字符数range遍历字符串时拿到的是rune,索引是字节偏移量- 要修改字符串中的字符,需要先转
[]rune再转回来
理解
rune 和 byte 的本质区别就是字符 vs 字节。byte 是存储层面的最小单位,rune 是逻辑层面的一个字符。Go 的字符串底层是 byte 序列,但 range 会自动按 UTF-8 解码成 rune。处理纯 ASCII 文本时 byte 就够了,但涉及中文、emoji 就得用 rune,否则 len() 和索引都会出错。
引出
rune 是 int32,byte 是 uint8,这些不同类型之间经常需要互相转换。接下来看 Go 类型转换,学习 Go 里不同类型之间怎么做显式转换、strconv 包的用法以及类型断言。