我有一个 io.ReadCloser
对象(来自一个 http.Response
对象)。
将整个流转换为 string
对象的最有效方法是什么?
编辑:
从 1.10 开始,strings.Builder 就存在了。例子:
buf := new(strings.Builder)
n, err := io.Copy(buf, r)
// check errors
fmt.Println(buf.String())
下面的过时信息
简短的回答是它效率不高,因为转换为字符串需要完整复制字节数组。这是执行您想要的操作的正确(非高效)方法:
buf := new(bytes.Buffer)
buf.ReadFrom(yourReader)
s := buf.String() // Does a complete copy of the bytes in the buffer.
此副本是作为保护机制完成的。字符串是不可变的。如果可以将 []byte 转换为字符串,则可以更改字符串的内容。但是,go 允许您使用 unsafe 包禁用类型安全机制。使用不安全的包需要您自担风险。希望这个名字本身就是一个足够好的警告。这是我使用不安全的方法:
buf := new(bytes.Buffer)
buf.ReadFrom(yourReader)
b := buf.Bytes()
s := *(*string)(unsafe.Pointer(&b))
我们开始了,您现在已经有效地将字节数组转换为字符串。真的,所有这些都是欺骗类型系统将其称为字符串。这种方法有几个注意事项:
不能保证这将适用于所有 go 编译器。虽然这适用于 plan-9 gc 编译器,但它依赖于官方规范中未提及的“实现细节”。您甚至不能保证这将适用于所有架构或不会在 gc 中更改。换句话说,这是一个坏主意。该字符串是可变的!如果您对该缓冲区进行任何调用,它将更改字符串。要非常小心。
我的建议是坚持官方方法。做一个副本并不昂贵,也不值得不安全的危害。如果字符串太大而无法进行复制,则不应将其制成字符串。
到目前为止,答案还没有解决问题的“整个流程”部分。我认为这样做的好方法是ioutil.ReadAll
。将您的 io.ReaderCloser
命名为 rc
,我会写,
去 >= v1.16
if b, err := io.ReadAll(rc); err == nil {
return string(b)
} ...
去 <= v1.15
if b, err := ioutil.ReadAll(rc); err == nil {
return string(b)
} ...
buf.ReadFrom()
还将整个流读取到 EOF。
ioutil.ReadAll()
的实现,它只是包装了 bytes.Buffer
的 ReadFrom
。缓冲区的 String()
方法是一个简单的环绕转换为 string
的方法——所以这两种方法实际上是相同的!
data, _ := ioutil.ReadAll(response.Body)
fmt.Println(string(data))
最有效的方法是始终使用 []byte
而不是 string
。
如果您需要打印从 io.ReadCloser
接收的数据,fmt
包可以处理 []byte
,但效率不高,因为 fmt
实现会在内部将 []byte
转换为 string
。为了避免这种转换,您可以为 type ByteSlice []byte
之类的类型实现 fmt.Formatter
接口。
[]byte
到 string
的转换相当快,但问题是询问“最有效的方式”。目前,Go 运行时在将 []byte
转换为 string
时总是会分配一个新的 string
。其原因是编译器不知道如何确定转换后 []byte
是否会被修改。这里有一些编译器优化的空间。
func copyToString(r io.Reader) (res string, err error) {
var sb strings.Builder
if _, err = io.Copy(&sb, r); err == nil {
res = sb.String()
}
return
}
var b bytes.Buffer
b.ReadFrom(r)
// b.String()
strings.Builder
通过确保底层[]byte
永不泄漏,并以未来支持的方式转换为没有副本的string
来有效地做到这一点。这在 2012 年不存在。@dimchansky 下面的解决方案自 Go 1.10 以来一直是正确的解决方案。请考虑编辑!