Golang中不同for循环遍历string存在的小坑

Golang中不同for循环遍历string存在的小坑


众所周知,go语言中string是一个不可变的类型,由字节组成,字符串的内部结构由只读的字节数组和长度组成,结构如下:

type stringStruct struct {
    str unsafe.Pointer // 指向底层字节数组
    len int            // 字符串长度(字节数)
}

可以"无缝地"与[]byte进行转换,此时byte数组中每个元素为字符串s 的一个字节。

s := "hello, world"

// string → []byte
b := []byte(s) 	
fmt.Println(b)	// 输出字符的ASCII编码:[104 101 108 108 111 44 32 119 111 114 108 100]

// []byte → string
s2 := string(b) 

每个字符在存储时采用的是UTF-8编码,而UTF-8是可变长度的编码方式,英文等ASCII字符占1个字节,而中文通常占3个字节。如字母A是单字节,而在编码时占三个字节。由于string存放的是字符,因此使用len()时返回的是string的字节数而不是字符数。

s := "Hello"        
fmt.Println(len(s)) // 输出 5(字节数)

s := "你好,世界"        	
fmt.Println(len(s)) // 输出 15("你"(3)+"好"(3)+","(3)+"世"(3)+"界"(3))

而我们想要遍历字符串时,有多种方式进行遍历,如常规的索引循环或者range循环。此时如果面对纯ASCII字符组成的字符串,两种遍历方式都能正确获取string中的字符:

s := "Hello"
for i := 0; i < len(s); i++ {
	fmt.Printf("字节索引 %d: %c\n", i, s[i])
}
// Output:
字节索引 0: H
字节索引 1: e
字节索引 2: l
字节索引 3: l
字节索引 4: o

for i, c := range s {
	fmt.Printf("字节索引 %d: %c\n", i, c)
}
// Output:
字节索引 0: H
字节索引 1: e
字节索引 2: l
字节索引 3: l
字节索引 4: o

但是如果面对存在非ASCII字符组成的字符串时,两种for循环遍历,就会存在不同的结果:

// range循环能正常输出字符
s := "你好,世界"
for i, c := range s {
	fmt.Printf("字节索引 %d: %c\n", i, c)
}
// Output:
字节索引 0: 你
字节索引 3: 好
字节索引 6: ,
字节索引 9: 世
字节索引 12:// 下标遍历则存在问题
s := "你好,世界"
for i := 0; i < len(s); i++ {
	fmt.Printf("字节索引 %d: %c\n", i, s[i])
}
// Output:
字节索引 0: ä
字节索引 1: ½
字节索引 2:  
字节索引 3: å
字节索引 4: ¥
字节索引 5: ½
字节索引 6: ï
字节索引 7: ¼
字节索引 8: Œ
字节索引 9: ä
字节索引 10: ¸
字节索引 11: –
字节索引 12: ç
字节索引 13: •
字节索引 14: 

究其原因,下标遍历时,每个下标索引的是字符串底层字符数组的每个字符位置,而range遍历时,是根据字符串编码的字符进行索引,我们通过输出每个元素的类型可以发现,s[i]是uint8类型的元素,而cint32(或rune类型,rune底层为int32)类型的元素:

s := "你好,世界"
for i := 0; i < len(s); i++ {
	fmt.Printf("%T\n", s[i])
	break
}
for _, c := range s {
	fmt.Printf("%T\n", c)
	break
}

// Output:
uint8
int32

当然,并不是说存在中文等字符的字符串不能通过下标索引遍历,可以通过将字符串转换为 []rune 后遍历,通过这种方式我们也可以实现随机访问字符串元素。

s := "Hello, 世界"
runes := []rune(s)
for i := 0; i < len(runes); i++ {
    fmt.Printf("字符 %d: %c\n", i, runes[i])
}

// Output:
字符 0: H
字符 1: e
...
字符 7: 世
字符 8:

在面对不同字符组成的字符串时,应根据实际场景选择遍历方式,正确获取元素的同时也提升代码性能。

  • 纯 ASCII 文本:优先索引循环(性能最佳)
  • 含多字节字符:使用 range 循环(平衡性能与正确性)
  • 需要随机访问字符:转换为 []rune(注意内存开销)
遍历方式处理单位多字节支持内存开销典型场景
索引循环字节最低纯ASCII处理
range 循环Unicode通用字符处理
转换为 []runeUnicode随机访问字符
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值