Из io.Reader в строку в Go


129

У меня есть io.ReadCloserобъект (из http.Responseобъекта).

Какой самый эффективный способ преобразовать весь поток в stringобъект?

Ответы:


175

РЕДАКТИРОВАТЬ:

Начиная с версии 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.

Эта копия сделана как механизм защиты. Строки неизменны. Если бы вы могли преобразовать байт [] в строку, вы могли бы изменить содержимое строки. Однако go позволяет отключить механизмы безопасности типов с помощью пакета unsafe. Используйте небезопасную упаковку на свой страх и риск. Надеюсь, само по себе название - достаточно хорошее предупреждение. Вот как я бы сделал это, используя unsafe:

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

Итак, теперь вы эффективно преобразовали свой байтовый массив в строку. На самом деле все это обманом заставляет систему типов называть ее строкой. У этого метода есть несколько предостережений:

  1. Нет никаких гарантий, что это будет работать во всех компиляторах go. Хотя это работает с компилятором gc plan-9, он полагается на «детали реализации», не упомянутые в официальной спецификации. Вы даже не можете гарантировать, что это будет работать на всех архитектурах или не будет изменено в gc. Другими словами, это плохая идея.
  2. Эта строка изменяема! Если вы сделаете какие - либо вызовы на этот буфер он будет изменить строку. Будь очень осторожен.

Мой совет - придерживаться официального метода. Делать копию не что дорого , и это не стоит пороков небезопасных. Если строка слишком велика для копирования, вы не должны превращать ее в строку.


Спасибо, это действительно подробный ответ. «Хороший» способ также примерно эквивалентен ответу @Sonia (поскольку buf.String просто выполняет внутреннее приведение).
djd

1
И это даже не работает с моей версией, вроде не получается получить указатель от & but.Bytes (). Используя Go1.
sinni800

@ sinni800 Спасибо за подсказку. Я забыл, что возвращаемые функции не адресуются. Теперь это исправлено.
Стивен Вайнберг

3
Что ж, компьютеры чертовски быстро копируют блоки байтов. И учитывая, что это HTTP-запрос, я не могу представить сценарий, при котором задержка передачи не будет в миллионы раз больше, чем тривиальное время, необходимое для копирования массива байтов. Любой функциональный язык копирует этот тип неизменяемого материала повсюду и по-прежнему работает достаточно быстро.
см. Резче

Этот ответ устарел. strings.Builderделает это эффективно, гарантируя отсутствие []byteутечек в базовом коде и преобразование в stringбез копии таким образом, который будет поддерживаться в будущем. Этого не существовало в 2012 году. Приведенное ниже решение @dimchansky было правильным со времен Go 1.10. Пожалуйста, рассмотрите возможность редактирования!
Нуно Крусес,

102

Ответы пока не касаются части вопроса "весь поток". Я думаю, что это хороший способ сделать это ioutil.ReadAll. С твоим io.ReaderCloserименем rcя бы написал,

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

2
Спасибо, хороший ответ. Похоже, buf.ReadFrom()также читает весь поток до EOF.
djd

8
Как смешно: я только что прочитал реализацию ioutil.ReadAll()и просто обертывания bytes.Buffer«S ReadFrom. А метод буфера String()- это простой переход на приведение к string- так что два подхода практически одинаковы!
djd

1
Это лучшее и максимально лаконичное решение.
mk12

1
Я сделал это, и это работает ... в первый раз. По какой-то причине после чтения строки последующие чтения возвращают пустую строку. Пока не знаю почему.
Aldo 'xoen' Giambelluca,

1
@ Aldo'xoen'Giambelluca ReadAll потребляет читателя, поэтому при следующем вызове читать нечего.
DanneJ


5

Самый эффективный способ - всегда использовать []byteвместоstring .

Если вам нужно распечатать данные, полученные от io.ReadCloser, fmtпакет может обрабатывать []byte, но это неэффективно, потому что fmtреализация будет внутренне преобразована []byteв string. Чтобы избежать этого преобразования, вы можете реализовать fmt.Formatterинтерфейс для такого типа, как type ByteSlice []byte.


Дорого ли преобразование из байта [] в строку? Я предположил, что строка ([] byte) на самом деле не копирует байт [], а просто интерпретирует элементы среза как серию рун. Вот почему я предложил Buffer.String () weekly.golang.org/src/pkg/bytes/buffer.go?s=1787:1819#L37 . Думаю, было бы неплохо узнать, что происходит при вызове строки ([] byte).
Nate

4
Преобразование из []byteв stringпроисходит достаточно быстро, но вопрос был о «наиболее эффективном способе». В настоящее время среда выполнения Go всегда выделяет новую stringпри преобразовании []byteв string. Причина этого в том, что компилятор не знает, как определить, []byteбудет ли изменен после преобразования. Здесь есть место для оптимизации компилятора.

3
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
}


Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.