面试官:
str := "Go 测试",如果直接str[:3]会发生什么?
候选人:得到"Go 测"?
面试官:❌ 错,是乱码。
这道题考察的是 Go 字符串底层原理 与 UTF-8 编码,属于必知必会的基础坑点。
1️⃣ 错误示范:直接按索引截取
package main
import "fmt"
func main() {
str := "Go 测试"
// ❌ 错误:直接按字节截取
sub := str[:3]
fmt.Println(sub)
}
输出结果:
Go // 或者乱码,因为"测"字被截断了
2️⃣ 为什么会出现乱码?
🔍 核心原因:Go 字符串是 字节数组
在 Go 中,string 底层是 []byte,索引操作 str[i] 或切片 str[:n] 按字节(byte)计算,而不是按字符(character)计算。
📊 内存布局图解
字符串:"G" "o" "测" "试"
字节数: 1 1 3 3
索引: [0] [1] [2][3][4] [5][6][7]
↑
str[:3] 截到这里
- G:ASCII 码,占 1 字节
- o:ASCII 码,占 1 字节
- 测:UTF-8 中文,占 3 字节
- 试:UTF-8 中文,占 3 字节
str[:3] 取的是前 3 个字节:
G(完整)o(完整)测的第 1 个字节 (不完整)
结果:UTF-8 解码器遇到不完整的中文编码,显示为乱码()。
3️⃣ 正确做法:转 []rune 再截取
Rune 是 Go 中的 Unicode 码点类型,一个 rune 代表一个完整的字符。
package main
import "fmt"
func main() {
str := "Go 测试"
// ✅ 正确:先转 rune 切片,再截取
runes := []rune(str)
sub := string(runes[:3]) // 取前 3 个字符
fmt.Println(sub) // 输出:Go 测
}
4️⃣ 面试加分项:性能优化
如果字符串很长,频繁转 []rune 会有内存开销。面试时可以补充:
- 短字符串:直接
[]rune转换,代码清晰。 - 长字符串/高性能场景:使用
utf8.RuneCountInString先判断长度,或遍历range截取。
// 遍历 range 自动按 rune 解码
for i, r := range str {
// i 是字节索引,r 是 rune
if i == 3 { break } // 需配合逻辑控制
}
5️⃣ 一张表总结
| 操作 | 单位 | 适用场景 | 风险 |
|---|---|---|---|
str[:n] | 字节 (byte) | 纯英文/ASCII | 中文乱码 |
[]rune(str)[:n] | 字符 (rune) | 含中文/多语言 | 内存开销 |
range str | 字符 (rune) | 遍历处理 | 性能较好 |
💡 记忆口诀
Go 串底层是字节,中文三字节别切。
要想截取不乱码,转成 rune 再操作。
✅ 避坑指南:涉及用户输入、国际化场景,永远不要假设 1 字符 = 1 字节!