引入问题-为何打印s[0] 没有打印‘你’字符
package main
import "fmt"
func main() {
s := "你"
fmt.Println(s[0])
fmt.Printf("%s\n", s[0])
}
output
%!s(uint8=228)
首先需要知道go中编码格式和String 类型, Go内置的utf-8编码格式。
utf-8与Unicode
Unicode,全称为Unicode标准(The Unicode Standard),其官方机构Unicode联盟所用的中文名称为统一码,为各种语言中的每个字符定义一个唯一的编码,也描述为码点。
utf-8编码 是一种可变长编码方式,可以用一至四个字节对有效码点进行编码。
String
定义:在Go中,string是一个内置的不可变序列类型,用于表示UTF-8编码的字符串。
内存分配:字符串是不可变的,这意味着一旦创建,其内容就不能被更改。字符串在内存中是不可变的,这意味着每次修改字符串(例如连接或修改)时,都会创建一个新的字符串对象。
用途:主要用于表示文本数据,如用户输入、文件内容、网络消息等。
操作:提供了丰富的字符串操作函数,如len()获取长度、+进行字符串连接、strings包中的各种函数等。
在GO中String类型可以存储任意字节,在编写代码过程,当定义好一个字符串时,存储的是utf-8编码后的字节, 也就是说以下的s和s1字符串是等价的
package main
import "fmt"
func main() {
s := "你"
s1 := "\xe4\xbd\xa0"
if s == s1 {
fmt.Println("s == s1")
}
}
可以看到 s字符串实际存储的是对‘你’字符utf-8编码后的字节,那么回到最开始的问题,当使用下标进行访问的时候,s[0]打印的应该是编码后的第一个字节
fmt.Printf("%x", s[0])
output:
e4 //确实如猜测一样打印第一个字节
那么是如何解决这个问题,按字符来进行访问,而不是字节?
rune类型
定义
在builtin/builtin.go文件中定义描述
// rune is an alias for int32 and is equivalent to int32 in all ways. It is
// used, by convention, to distinguish character values from integer values.
type rune = int32
所以 type和int32是等价的
主要作用是用来区分字符值和整数值
Go语言中的rune类型是int32的别名,用于表示单个Unicode字符(码点),支持多字节字符处理(如中文),是处理国际化文本的关键数据类型。
UTF-8 下:1个中文字符占 3个字节
var r rune = '你'
fmt.Println(r) // 输出 Unicode 编码:20320
fmt.Printf("%c\n", r) // 输出字符:你
应用
将字符串转化为rune类型
package main
import "fmt"
func main() {
s := "你好"
s1 := []rune(s)
fmt.Printf("字节个数: %d\n", len(s))
fmt.Printf("字符个数: %d\n", len(s1))
}
output
字节个数: 6
字符个数: 2
range
因为在GO中utf-8是唯一的编码方式,因此在对字符串使用range遍历时,会对字符串使用utf-8进行解码,转化为rune类型输出
package main
import "fmt"
func main() {
s := "你好"
for _, c := range s {
fmt.Printf("%s\n", string(c))
}
}
output
你
好
✅ for...range
会自动把 string 拆成 rune 单位,而不是 byte!
其他
var r rune = '你'
fmt.Println(r) // 输出 Unicode 编码:20320
fmt.Printf("%c\n", r) // 输出字符:你
字符串转换为 rune 切片
str := "你好Go"
runes := []rune(str)
fmt.Println(len(str)) // 字节数(UTF-8):6
fmt.Println(len(runes)) // 字符数(rune):3
fmt.Println(string(runes)) // 还能转回字符串
rune 的实战应用场景
✅ 字符统计(处理中文、emoji 不出错)
✅ 字符串反转(不能直接对 byte 做反转)
✅ 正确截取字符串中的“字符”
✅ 自定义解码器或语法分析器(处理 code point)
func reverse(s string) string {
runes := []rune(s)
for i, j := 0, len(runes)-1; i < j; i, j = i+1, j-1 {
runes[i], runes[j] = runes[j], runes[i]
}
return string(runes)
}
fmt.Println(reverse("你好Go!")) // !oG好你
byte
rune可表示1~4字节的字符(如中文、日文等),而bytes仅能处理单字节ASCII字符
rune占用4字节,若仅处理英文文本,byte更高效。
特性 | byte | rune |
---|---|---|
本质类型 | uint8(1 字节) | int32(4 字节) |
用于表示 | ASCII/单字节字符 | Unicode 字符(多字节) |
示例字符 | ‘A’(一个字节) | ‘你’(三个字节) |
用途 | 处理英文、底层IO更快、提供一系列处理字符api | 处理中文(比如字符数)、emoji安全 |
Byte 切片([]byte)
定义:[]byte是一个字节切片,是[]uint8的别名,用于表示一个字节序列。
byte的优点——主要是相比string
内存分配:字节切片是可变的,这意味着你可以修改切片中的元素。如果你需要频繁修改字符串内容,使用字节切片会更高效,因为它不需要每次都创建一个新的字符串对象。
用途:主要用于需要频繁修改文本内容的场景,如处理二进制数据、大文件的读写、需要频繁修改字符串内容的场景等。
操作:提供了基本的切片操作,如len()获取长度、append()添加元素等。对于复杂的文本处理,通常会结合使用bytes包中的函数。
相比rune更适合处理英文数据
相比rune,byte的缺点
- 字符串通过rune转换后可准确统计字符数(非字节数)
- 需使用rune切片而非byte切片操作含非ASCII字符的字符串。
- 通过
string(r)
可将rune转为字符串,反之通过rune(s)