Від 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 дозволяє відключити механізми безпеки типу за допомогою небезпечного пакету. Використовуйте небезпечний пакет на свій страх і ризик. Сподіваємось, саме ім'я є досить хорошим попередженням. Ось як я це зробив за допомогою небезпечних:

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

Зараз ми вже зараз ефективно перетворили свій байтовий масив у рядок. Дійсно, все це полягає в хитрості типової системи називати її рядком. Існує пара застережень до цього методу:

  1. Немає гарантій, що це буде працювати у всіх компіляторах, що йдуть. Хоча це працює з компілятором plan-9 gc, він покладається на "деталі реалізації", не зазначені в офіційній специфікації. Ви навіть не можете гарантувати, що це буде працювати у всіх архітектурах або не буде змінено в gc. Іншими словами, це погана ідея.
  2. Ця струна є змінною! Якщо ви зробите які - або виклики на цей буфер він буде змінити рядок. Будьте дуже обережні.

Моя порада - дотримуватися офіційного методу. Робити копією не що дорого , і це не варто вад небезпечних. Якщо рядок занадто великий, щоб зробити копію, не слід перетворювати її на рядок.


Дякую, це дійсно детальна відповідь. "Добрий" спосіб виглядає приблизно еквівалентним відповіді @ Соні (оскільки buf.String просто виконує акторський склад всередині).
djd

1
І навіть не працює з моєю версією, схоже, не вдається отримати вказівник від & but.Bytes (). Використання Go1.
sinni800

@ sinni800 Дякую за пораду. Я забув, повернення функції не було адресованим. Зараз це виправлено.
Стівен Вайнберг

3
Добре, що комп'ютери досить прокляті при копіюванні блоків байтів. І зважаючи на те, що це http-запит, я не можу уявити сценарій, коли затримка передачі не буде в мільйон разів більшим, ніж тривіальний час, необхідний для копіювання байтового масиву. Будь-яка функціональна мова копіює цей тип непорушних матеріалів по всьому світу, і все ще працює досить швидко.
див. Гостріше

Ця відповідь застаріла. strings.Builderробить це ефективно, гарантуючи, що базові []byteніколи не протікають, і перетворюючись на stringбез копії таким чином, що буде підтримуватися вперед. Цього не існувало у 2012 році. Розв’язання димчанського нижче було правильним з Go 1.10. Будь ласка, розгляньте правку!
Nuno Cruces

102

Досі відповіді не стосувались частини питання "весь потік". Я думаю, що хороший спосіб зробити це ioutil.ReadAll. З вашим io.ReaderCloserназваним rcя б написав:

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

2
Дякую, хороша відповідь. Схоже, він buf.ReadFrom()також читає весь потік аж до EOF.
дед

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.


Чи дороге перетворення з [] байта в рядок? Я припускав, що рядок ([] байт) насправді не копіював байт [], а просто інтерпретував елементи зрізу як серію рун. Ось чому я запропонував Buffer.String () тиждень.golang.org/src/pkg/bytes/buffer.go ? s=1787:1819#L37 . Я думаю, було б добре знати, що відбувається, коли викликається рядок ([] байт).
Нейт

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.