首页 / Go 语言入门教程 / rune 与 byte

Go 语言入门教程

rune 与 byte

本教程共 80 篇 · 第 13 篇 · 更新于 2026-07-27 · 约 7 分钟阅读

GoGo 入门教程runebyteUnicodeUTF-8字符编码

13. rune 与 byte

本节目标:彻底搞懂 rune 和 byte 的区别,理解 Unicode 码点,学会正确处理含中文的字符串。

为什么需要两个类型

先说结论:byterune 都是整数类型的别名,但用途不同。

type byte = uint8   // 字节,1个字节
type rune = int32   // 字符,4个字节
  • byte 用来表示一个字节(8 位)
  • rune 用来表示一个 Unicode 码点(Code Point)

在处理 ASCII 文本时,byterune 看起来没区别。但一旦涉及中文、表情符号等多字节字符,区别就大了。

Unicode 和 UTF-8

Unicode 码点

Unicode 给世界上每个字符分配了一个唯一的数字编号,叫码点

字符码点
AU+0041(十进制 65)
U+4E2D(十进制 20013)
😀U+1F600(十进制 128512)

rune 就是用来存这些码点的。它用 int32,能表示 0 到 0x10FFFF 的所有 Unicode 码点。

UTF-8 编码

UTF-8 是一种把 Unicode 码点编码成字节序列的方式:

码点范围编码字节数
0-1271 字节
128-20472 字节
2048-655353 字节
65536+4 字节

ASCII 字符(0-127)在 UTF-8 中只占 1 字节,和 ASCII 编码完全兼容。中文通常占 3 字节,表情符号占 4 字节。

Go 字符串用 UTF-8 编码存储。一个 byte 存一个字节,一个 rune 存一个完整的码点。

byte 的用法

byte 就是 uint8,表示一个字节。

var b byte = 65
fmt.Println(b)           // 65
fmt.Printf("%c\n", b)    // A(把 65 当 ASCII 字符输出)
fmt.Printf("%T\n", b)    // uint8

字符串索引 s[i] 返回的就是 byte

s := "Hello"
b := s[0]  // byte 类型,值是 72
fmt.Printf("%c\n", b)  // H

byte 适合处理纯 ASCII 文本,或者操作二进制数据。

rune 的用法

rune 就是 int32,表示一个 Unicode 码点。

用单引号写的字符字面量,默认类型是 rune

r := 'A'
fmt.Printf("%T\n", r)  // int32
fmt.Println(r)          // 65

r2 := ''
fmt.Println(r2)          // 20013
fmt.Printf("%c\n", r2)   // 中

字符串与 rune 的转换

string 转 []rune

s := "Hello,世界"
runes := []rune(s)
fmt.Println(len(runes))  // 8(8个字符,不是字节数)

for _, r := range runes {
    fmt.Printf("%c ", r)  // H e l l o , 世 界
}

转成 []rune 后,每个元素就是一个完整的字符,可以按字符索引访问。

[]rune 转 string

runes := []rune{72, 101, 108, 108, 111}
s := string(runes)
fmt.Println(s)  // Hello

string 转 []byte

s := "Hello"
bytes := []byte(s)
fmt.Println(bytes)  // [72 101 108 108 111]

[]byte 转 string

bytes := []byte{72, 101, 108, 108, 111}
s := string(bytes)
fmt.Println(s)  // Hello

两种遍历方式对比

按字节遍历(for + len)

s := "Hello,世界"
for i := 0; i < len(s); i++ {
    fmt.Printf("%c ", s[i])
}
// 输出:H e l l o , ä ¸  ç

中文被拆成了乱码,因为每个中文字符占 3 字节,按字节遍历会拆开。

按字符遍历(for-range)

s := "Hello,世界"
for _, r := range s {
    fmt.Printf("%c ", r)
}
// 输出:H e l l o , 世 界

for-range 自动按 UTF-8 解码,每次得到一个完整的 rune

Note

for-range 遍历字符串时,第二个变量是 rune 类型,不是 byte。这是 Go 语言设计上的贴心之处。

实际应用

统计字符数

s := "Hello,世界"
fmt.Println("字节数:", len(s))                        // 12
fmt.Println("字符数:", utf8.RuneCountInString(s))    // 8
fmt.Println("字符数:", len([]rune(s)))                // 8

按字符索引访问

s := "你好Go"
runes := []rune(s)
fmt.Printf("%c\n", runes[0])  // 你
fmt.Printf("%c\n", runes[3])  // G

直接用 s[0] 只能拿到第一个字节,转成 []rune 后才能按字符索引。

修改字符串中的字符

s := "Hello"
runes := []rune(s)
runes[0] = 'h'
s = string(runes)
fmt.Println(s)  // hello

判断字符类型

unicode 包提供了判断字符类型的函数:

import "unicode"

func main() {
    r := ''
    fmt.Println(unicode.Is(unicode.Han, r))  // true,是汉字
    fmt.Println(unicode.IsLetter(r))          // true,是字母
    fmt.Println(unicode.IsDigit('5'))         // true,是数字
    fmt.Println(unicode.IsSpace(' '))         // true,是空白字符
}

转大小写

import "unicode"

func main() {
    r := 'a'
    fmt.Printf("%c\n", unicode.ToUpper(r))  // A
    fmt.Printf("%c\n", unicode.ToLower('B')) // b
}

byte 和 rune 对比

特性byterune
底层类型uint8int32
大小1 字节4 字节
表示一个字节一个 Unicode 码点
字符串索引返回
for-range 返回
适合场景二进制数据、ASCII多语言文本

字符串常量是 UTF-8

Go 源代码文件本身必须是 UTF-8 编码。你在字符串字面量里写的中文,编译后就是 UTF-8 字节序列:

s := "中文"
// s 的底层是 [228, 184, 173, 230, 150, 135]

这也是 Go 原生支持多语言的原因—从源码到运行时,全链路 UTF-8。

常见问题

单引号和双引号的区别

s := "A"   // 字符串,类型是 string
c := 'A'   // 字符,类型是 int32(rune)

双引号是字符串,单引号是字符(rune)。这个区别一定要记住。

string(65) 和 string([]byte{65})

fmt.Println(string(65))         // A,把码点 65 转成字符
fmt.Println(string([]byte{65})) // A,把字节序列转成字符串

结果一样,但含义不同。string(65) 是把整数当码点转字符,string([]byte{65}) 是把字节序列转字符串。

for-range 的索引不连续

s := "A中"
for i, r := range s {
    fmt.Printf("索引%d: %c\n", i, r)
}
// 索引0: A
// 索引1: 中

“A” 占 1 字节,所以第二个字符的索引是 1。“中” 占 3 字节,如果后面还有字符,索引会跳到 4。

学完这节你能做什么

  • 分清 byte 和 rune 的用途
  • 理解 Unicode 码点和 UTF-8 编码的关系
  • 正确遍历包含中文的字符串
  • 按字符索引访问和修改字符串
  • 用 unicode 包判断字符类型

下一节讲类型转换和类型推断。