【问题标题】:From io.Reader to string in Go从 io.Reader 到 Go 中的字符串
【发布时间】:2012-03-27 11:58:37
【问题描述】:

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

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

【问题讨论】:

    标签: go


    【解决方案1】:

    编辑:

    从 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 包禁用类型安全机制。使用不安全的包需要您自担风险。希望这个名字本身就是一个足够好的警告。以下是我使用 unsafe 的方法:

    buf := new(bytes.Buffer)
    buf.ReadFrom(yourReader)
    b := buf.Bytes()
    s := *(*string)(unsafe.Pointer(&b))
    

    我们开始了,您现在已经有效地将字节数组转换为字符串。真的,所有这些都是欺骗类型系统将其称为字符串。这种方法有几个注意事项:

    1. 不保证这将在所有 go 编译器中工作。虽然这适用于 plan-9 gc 编译器,但它依赖于官方规范中未提及的“实现细节”。您甚至不能保证这将适用于所有架构或不会在 gc 中更改。换句话说,这是个坏主意。
    2. 那个字符串是可变的!如果您对该缓冲区进行任何调用,它更改字符串。要非常小心。

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

    【讨论】:

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

    到目前为止,答案还没有解决问题的“整个流程”部分。我认为做到这一点的好方法是ioutil.ReadAll。用你的io.ReaderCloser 命名为rc,我会写,

    开始 >= v1.16

    if b, err := io.ReadAll(rc); err == nil {
        return string(b)
    } ...
    

    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/…有什么理由不这样做吗?
    【解决方案3】:
    data, _ := ioutil.ReadAll(response.Body)
    fmt.Println(string(data))
    

    【讨论】:

      【解决方案4】:

      最有效的方法是始终使用[]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是否会被修改。这里有一些编译器优化的空间。
      【解决方案5】:
      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
      }
      

      【讨论】:

        【解决方案6】:
        var b bytes.Buffer
        b.ReadFrom(r)
        
        // b.String()
        

        【讨论】:

          【解决方案7】:

          我喜欢bytes.Buffer 结构。我看到它有 ReadFromString 方法。我用 []byte 但不是 io.Reader。

          【讨论】:

            猜你喜欢
            • 1970-01-01
            • 2018-03-28
            • 2015-10-14
            • 2017-05-03
            • 2020-08-22
            • 1970-01-01
            • 2020-11-29
            • 1970-01-01
            • 2016-08-11
            相关资源
            最近更新 更多