ChatGPT解决这个技术问题 Extra ChatGPT

从 io.Reader 到 Go 中的字符串

go

我有一个 io.ReadCloser 对象(来自一个 http.Response 对象)。

将整个流转换为 string 对象的最有效方法是什么?


S
Stephen Weinberg

编辑:

从 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 中更改。换句话说,这是一个坏主意。该字符串是可变的!如果您对该缓冲区进行任何调用,它将更改字符串。要非常小心。

我的建议是坚持官方方法。做一个副本并不昂贵,也不值得不安全的危害。如果字符串太大而无法进行复制,则不应将其制成字符串。


谢谢,这是一个非常详细的答案。 “好”的方式似乎也大致相当于@Sonia的答案(因为 buf.String 只是在内部进行转换)。
而且它甚至不适用于我的版本,似乎无法从 &but.Bytes() 获取指针。使用 Go1。
@sinni800 感谢您的提示。我忘记了函数返回不可寻址。现在已修复。
好吧,计算机在复制字节块方面非常快。鉴于这是一个 http 请求,我无法想象传输延迟不会比复制字节数组所需的微不足道的时间大 squillion 倍的场景。任何函数式语言都会到处复制这种不可变的东西,并且仍然运行得很快。
这个答案已经过时了。 strings.Builder 通过确保底层 []byte 永不泄漏,并以未来支持的方式转换为没有副本的 string 来有效地做到这一点。这在 2012 年不存在。@dimchansky 下面的解决方案自 Go 1.10 以来一直是正确的解决方案。请考虑编辑!
a
aymericbeaumet

到目前为止,答案还没有解决问题的“整个流程”部分。我认为这样做的好方法是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.BufferReadFrom。缓冲区的 String() 方法是一个简单的环绕转换为 string 的方法——所以这两种方法实际上是相同的!
我这样做了,它的工作原理......第一次。由于某种原因,在读取字符串后,后续读取返回一个空字符串。还不知道为什么。
@Aldo'xoen'Giambelluca ReadAll 会消耗阅读器,因此在下一次调用时,没有什么可阅读的了。
@DanneJ 我前段时间写过这个:medium.com/@xoen/…有什么理由不这样做吗?
X
Xavi
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 的转换是否昂贵?我假设 string([]byte) 实际上并没有复制 []byte,而只是将切片元素解释为一系列符文。这就是我建议使用 Buffer.String() weekly.golang.org/src/pkg/bytes/buffer.go?s=1787:1819#L37 的原因。我想知道调用 string([]byte) 时发生了什么会很好。
[]bytestring 的转换相当快,但问题是询问“最有效的方式”。目前,Go 运行时在将 []byte 转换为 string 时总是会分配一个新的 string。其原因是编译器不知道如何确定转换后 []byte 是否会被修改。这里有一些编译器优化的空间。
D
Dimchansky
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
}

V
Vojtech Vitek
var b bytes.Buffer
b.ReadFrom(r)

// b.String()

N
Nate

我喜欢 bytes.Buffer 结构。我看到它有 ReadFromString 方法。我已经将它与 []byte 一起使用,但不是 io.Reader。