无意义的字符串转换
当编码操作的类型既可以是 []byte 又可以是 string时,大多数程序员倾向使用string类型,因为这样可能更方便。但是大多数的 I/O 操作采用的类型是 []byte。例如 io.Reader
、io.Writer
和 io.ReadAll
. 如果拿到的类型是string,但又要使用这些接口意味着需要进行类型转换,strings包提供了相关的转换函数。
下面看一个没有必要进行转换的例子。getBytes
函数从入参io.Reader中读取字节流,然后调用sanitize
函数,去掉首尾的空白符。
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处理。
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类型,这样就不用进行额外转换。
func sanitize(b []byte) []byte {
return bytes.TrimSpace(b)
}
bytes
包也有去除空白符函数TrimSpace
,直接调用bytes.TrimSpace,不需要额外转换。
return sanitize(b), nil
总结,大多数I/O操作处理的是[]byte,而不是string. 当我们不是很明确到底是采用[]byte还是string时候,应想到本文举的例子,使用[]byte不一定没有string方便,像strings
包提供的常用函数:Split、Count、Contains、Index 在bytes
包中也有一样的函数。因此,在处理I/O操作时,我们应该首先想到使用[]byte而不是string,避免额外的转换开销。