Go语言中常见100问题-#40 Useless string conversions

2023-08-17 08:33:53 浏览数 (2)

无意义的字符串转换

当编码操作的类型既可以是 []byte 又可以是 string时,大多数程序员倾向使用string类型,因为这样可能更方便。但是大多数的 I/O 操作采用的类型是 []byte。例如 io.Readerio.Writerio.ReadAll. 如果拿到的类型是string,但又要使用这些接口意味着需要进行类型转换,strings包提供了相关的转换函数。

下面看一个没有必要进行转换的例子。getBytes函数从入参io.Reader中读取字节流,然后调用sanitize函数,去掉首尾的空白符。

代码语言:javascript复制
func getBytes(reader io.Reader) ([]byte, error) {
 b, err := io.ReadAll(reader)
 if err != nil {
  return nil, err
 }
 // call sanitize
}

io.ReadAll返回的是字节切片(赋值给b),sanitize函数如何实现呢?一种思路是接收一个string参数返回值也是string,内部调用strings包的TrimSpace处理。

代码语言:javascript复制
func sanitize(s string) string {
 return strings.TrimSpace(s)
}

回到getBytes函数,拿到的b是[]byte,需要将其转换为string才能调用sanitize,对于返回值,由于sanitize返回的是字符串,但是getBytes返回的是[]byte,所以也需要进行逆向转换。

代码语言:javascript复制
return []byte(sanitize(string(b))),nil

看到这里实现存在的问题了吗?我们需要进行额外的转换,将[]byte转为string,返回的时候又要将string转回为[]byte. 每次转换需要分配额外的内存,尽管string的内部也有一个[]byte,但是转换为[]byte需要将string内部[]byte中的内容拷贝新分配的[]byte中,因为字符串string不可变特性。

字符串不可变特性

可以通过一段代码验证字符串的不可变特性,如下所示。字节切片b由字符a、b、c构成,然后通过b创建字符串s, 现在修改切片b中第二位置元素,即从b换成x,然后打印字符串s,观察s是否为axc.

代码语言:javascript复制
b := []byte{'a', 'b', 'c'}
s := string(b)
b[1] = 'x'
fmt.Println(s)

实际运行输出的是abc而不是axc,可以验证s和b不是共享同一个切片。

那如何实现 sanitize函数呢?将其入参和返回值类型都改为[]byte类型,这样就不用进行额外转换。

代码语言:javascript复制
func sanitize(b []byte) []byte {
 return bytes.TrimSpace(b)
}

bytes包也有去除空白符函数TrimSpace,直接调用bytes.TrimSpace,不需要额外转换。

代码语言:javascript复制
return sanitize(b), nil

总结,大多数I/O操作处理的是[]byte,而不是string. 当我们不是很明确到底是采用[]byte还是string时候,应想到本文举的例子,使用[]byte不一定没有string方便,像strings包提供的常用函数:Split、Count、Contains、Index 在bytes包中也有一样的函数。因此,在处理I/O操作时,我们应该首先想到使用[]byte而不是string,避免额外的转换开销。

0 人点赞