Go 中 len 对字符串和 slice 的返回含义不同

FreeGuideOnline 最新 2026-07-04

go package main

import "fmt"

func main() { s1 := "hello" s2 := "你好" s3 := "👋🌍"

fmt.Println(len(s1)) // 5  (5个英文字母,每个1字节)
fmt.Println(len(s2)) // 6  (2个中文字符,每个3字节)
fmt.Println(len(s3)) // 8  (2个emoji,每个4字节)

}


**核心认知**:一个 Unicode 字符(Go 中称为 `rune`)可能占用 1~4 个字节。`len` 只看字节,不看字符边界。  
如果你需要**字符数量**,`len` 会给出错误结果。

## `len` 作用于切片:返回的是**元素个数**

切片是一个动态数组的视图,由指针、长度(length)和容量(capacity)三部分组成。`len(slice)` 返回的是切片当前存储的**元素数量**,这与底层数组的容量无关。

```go
package main

import "fmt"

func main() {
    // 创建一个长度为3、容量为5的整数切片
    slice := make([]int, 3, 5)
    slice[0] = 10
    slice[1] = 20
    slice[2] = 30

    fmt.Println("len:", len(slice)) // 3
    fmt.Println("cap:", cap(slice)) // 5

    // 追加元素,长度变化,但容量可能不变(如果未超过容量)
    slice = append(slice, 40)
    fmt.Println("len after append:", len(slice)) // 4
    fmt.Println("cap after append:", cap(slice)) // 5(容量足够,未扩容)
}

对切片而言,len 返回的就是你存入的元素个数,与元素类型无关,也不关心每个元素占用多少内存。

对比表格:一图看懂差异

类型 len 的返回值 示例 输出 含义
字符串 "Go语言" 字节数(UTF-8 编码后的总字节数) len("Go语言") 8 G(1) + o(1) + (3) + (3)
字符串 "abc" 字节数(纯ASCII,每字符1字节) len("abc") 3 恰好等于字符数
整数切片 []int{1,2,3,4} 元素个数 len([]int{1,2,3,4}) 4 4个 int 元素
结构体切片 []struct{A int} 元素个数 len([]struct{A int}{{1},{2}}) 2 2个结构体元素

如何正确获取字符串的“字符”数量?

你需要的是 Unicode 码点数量,也就是 rune 的个数。常见方法有三种:

方法一:使用 utf8.RuneCountInString (推荐)

标准库 unicode/utf8 提供了专门函数,它遍历字符串并统计码点,无需额外分配内存。

import (
    "fmt"
    "unicode/utf8"
)

func main() {
    s := "你好,世界!"
    fmt.Println("字节数:", len(s))                        // 18
    fmt.Println("字符数:", utf8.RuneCountInString(s))    // 6
}

方法二:转换为 []rune 再取长度

len([]rune(s)) 会先分配一个新的 rune 切片,将字符串解码为一个个 Unicode 码点,再计算切片长度。

s := "Hello, 世界"
runeCount := len([]rune(s))
fmt.Println(runeCount) // 9

这种方法简单但会产生一次内存分配和拷贝,如果只是为了计数,不推荐在性能敏感代码中使用。

方法三:使用 range 遍历计数

for range 循环会按照 rune 遍历字符串,每迭代一次就是一个字符。

s := "🏆Go!"
count := 0
for range s {
    count++
}
fmt.Println(count) // 4 ('🏆' + 'G' + 'o' + '!')

一个容易踩坑的实际场景

假设你想截取字符串的前 N 个字符(不是前 N 个字节)。直接用切片表达式 s[:N]按字节索引的,可能截断多字节字符,造成乱码。

s := "Go语言"
fmt.Println(s[:4]) // "Go语"?实际输出 "Go��"(截断了“语”的第3个字节)

// 正确做法:先转换为 rune 切片再切片
rs := []rune(s)
fmt.Println(string(rs[:3])) // "Go语"