Files
leetcode-go/note/2026-05-15/03-StringByteRuneConversion.md
T

136 lines
4.0 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
---
tags: ["Go", "类型系统", "算法技巧", "避坑"]
create time: 2026-05-15 16:00
---
# 03-StringByteRuneConversion - string 索引返回 byte 而非 rune
## 核心问题
在 Go 中,字符串的**两种访问方式**返回完全不同的类型:
```go
s := "ABC"
// ── 方式 1:range 迭代 → 返回 rune ──
for _, char := range s {
// char 的类型是 rune(int32)
_ = char // rune ✅
}
// ── 方式 2:索引访问 → 返回 byte ──
c := s[0]
// c 的类型是 byte(uint8)!不是 rune
_ = c // byte ✅ ← 这就是陷阱所在
```
## 为什么这个细节容易踩坑?
当你的哈希表用 `map[rune]int` 做 key 时:
```go
need := make(map[rune]int)
have := make(map[rune]int)
for right, char := range s { // char 是 rune,✅ 可以直接当 key
have[char]++
}
leftChar := s[left] // s[left] 是 byte ❌
have[leftChar]-- // 编译错误:byte vs rune
```
> [!danger] ⛔ 没有隐式转换
>
> Go 和 Python/JavaScript 不同,**byte 不能隐式转为 rune**。即使 byte 的值域完全包含在 rune 内,也必须显式转换:
```go
// ❌ 编译错误:cannot use leftChar (variable of type byte) as rune value in map index
have[s[left]]--
// ✅ 正确:显式转换
leftChar := rune(s[left])
have[leftChar]--
// ✅ 替代方案:统一用 byte 做 key(仅限 ASCII 场景)
have := make(map[byte]int)
have[s[left]]-- // byte ↔ byte,匹配 ✅
```
## 典型出错场景
### 滑动窗口中使用 `range` + `[index]` 混合访问
```go
func minWindow(s string, t string) string {
need := make(map[rune]int) // key 是 rune
have := make(map[rune]int) // key 是 rune
for right, char := range s { // range → rune,与 map key 一致
have[char]++ // ✅ rune 作为 key
}
// 但收缩阶段需要按索引访问左边界字符:
leftChar := rune(s[left]) // ← 必须加 rune() 转换!
have[leftChar]-- // ✅
}
```
**根源原因**:`range s` 给出的是完整 Unicode 码点(rune),而 `s[i]` 直接访问的是 Go 字符串的底层字节数组([]byte)。前者一次可能跳过多个字节(对于 UTF-8 多字节字符),后者永远只取一个字节。
## byte key vs rune key 如何选择?
| 场景 | 推荐 key 类型 | 原因 |
|------|-------------|------|
| **仅英文字母 / ASCII** | `map[byte]int` | 少一层转换,性能略优 |
| **含中文 / Emoji / 多字节 Unicode** | `map[rune]int` | 能正确处理多字节字符 |
| **通用安全写法** | `map[rune]int` | 无论输入什么都能工作 |
> [!tip] 🔑 选择建议
>
> LeetCode 题目几乎全部只涉及英文字母,所以两种都可以。但养成使用 `rune` 的习惯更安全——代码复用时不会因为遇到非 ASCII 字符而出错。
## 常见变体代码对比
```go
// ── 错误写法(漏了转换)──
func buggy(s string) {
m := make(map[rune]int)
m[s[0]]++ // ❌ compile error: byte used as rune key
}
// ── 修正写法 A:加转换 ──
func fixA(s string) {
m := make(map[rune]int)
m[rune(s[0])]++ // ✅ byte → rune 显式转换
}
// ── 修正写法 B:改用 byte key ──
func fixB(s string) {
m := make(map[byte]int)
m[s[0]]++ // ✅ byte 对 byte,天然匹配
}
// ── 修正写法 C:全用 range,不索引 ──
func fixC(s string) {
m := make(map[rune]int)
for _, c := range s {
m[c]++ // ✅ range 返回 rune,直接用
}
}
```
## 检查清单
写涉及 `string` 索引的算法时自问:
- [ ] 我的 map key 是 `rune` 还是 `byte`?
- [ ] `s[index]` 返回的是 `byte`
- [ ] 如果 key 是 `rune`,是否加了 `rune(s[index])` 转换?
- [ ] 如果使用 `range` 遍历,`char` 本身就是 `rune`,不需要额外转换
## 记忆口诀
> **`range s` 拿的是 rune,`s[i]` 抠出来是 byte。**
> **rune 地图塞不进 byte,要转就要明说。**