Dari io.Reader ke string di Go

129

Saya punya io.ReadCloser objek (dari http.Responseobjek).

Apa cara paling efisien untuk mengubah seluruh aliran menjadi stringobjek?

djd
sumber

Jawaban:

175

EDIT:

Sejak 1,10, strings.Builder ada. Contoh:

buf := new(strings.Builder)
n, err := io.Copy(buf, r)
// check errors
fmt.Println(buf.String())

INFORMASI KEDUA DI BAWAH

Jawaban singkatnya adalah bahwa itu tidak akan efisien karena mengkonversi ke string memerlukan melakukan salinan array byte lengkap. Inilah cara yang tepat (tidak efisien) untuk melakukan apa yang Anda inginkan:

buf := new(bytes.Buffer)
buf.ReadFrom(yourReader)
s := buf.String() // Does a complete copy of the bytes in the buffer.

Salinan ini dilakukan sebagai mekanisme perlindungan. String tidak berubah. Jika Anda bisa mengonversi byte [] menjadi string, Anda bisa mengubah konten string. Namun, go memungkinkan Anda untuk menonaktifkan jenis mekanisme keamanan menggunakan paket yang tidak aman. Gunakan paket yang tidak aman dengan risiko Anda sendiri. Semoga saja nama itu peringatan yang cukup bagus. Inilah cara saya melakukannya menggunakan tidak aman:

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

Ini dia, Anda sekarang telah secara efisien mengkonversi byte array Anda menjadi sebuah string. Sungguh, semua ini dilakukan adalah mengelabui sistem tipe agar menyebutnya string. Ada beberapa peringatan untuk metode ini:

  1. Tidak ada jaminan ini akan bekerja di semua kompiler go. Meskipun ini bekerja dengan kompiler plan-9 gc, ia bergantung pada "detail implementasi" yang tidak disebutkan dalam spesifikasi resmi. Anda bahkan tidak dapat menjamin bahwa ini akan berfungsi pada semua arsitektur atau tidak diubah di gc. Dengan kata lain, ini adalah ide yang buruk.
  2. String itu bisa berubah! Jika Anda membuat panggilan pada buffer itu, ia akan mengubah string. Berhati-hatilah.

Saran saya adalah tetap berpegang pada metode resmi. Melakukan salinan tidak yang mahal dan tidak sebanding dengan kejahatan yang tidak aman. Jika string terlalu besar untuk dikopi, Anda tidak boleh membuatnya menjadi string.

Stephen Weinberg
sumber
Terima kasih, itu jawaban yang sangat terperinci. Cara "baik" tampaknya kira-kira sama dengan jawaban @ Sonia juga (karena buf.String hanya melakukan pemeran internal).
djd
1
Dan itu bahkan tidak berfungsi dengan versi saya, sepertinya tidak bisa mendapatkan Pointer dari & but.Bytes (). Menggunakan Go1.
sinni800
@ sinni800 Terima kasih atas tipnya. Saya lupa pengembalian fungsi tidak dialamatkan. Sekarang sudah diperbaiki.
Stephen Weinberg
3
Yah komputer sangat cepat menyalin blok byte. Dan mengingat ini adalah permintaan http, saya tidak bisa membayangkan skenario di mana latensi transmisi tidak akan menjadi satu triliun kali lebih besar dari waktu sepele yang diperlukan untuk menyalin array byte. Setiap bahasa fungsional menyalin jenis hal yang tidak dapat diubah ini di semua tempat, dan masih berjalan sangat cepat.
Lihat lebih tajam
Jawaban ini kedaluwarsa. strings.Buildermelakukan ini secara efisien dengan memastikan bahwa yang mendasarinya []bytetidak pernah bocor, dan mengubahnya menjadi stringtanpa salinan dengan cara yang akan didukung di masa mendatang. Ini tidak ada pada 2012. Solusi @ dimchansky di bawah ini adalah yang benar sejak Go 1.10. Silakan pertimbangkan hasil edit!
Nuno Cruces
102

Jawaban sejauh ini belum membahas bagian "seluruh aliran" dari pertanyaan. Saya pikir cara terbaik untuk melakukan ini adalah ioutil.ReadAll. Dengan io.ReaderClosernama Anda rc, saya akan menulis,

if b, err := ioutil.ReadAll(rc); err == nil {
    return string(b)
} ...
Sonia
sumber
2
Terima kasih, jawaban yang bagus. Sepertinya buf.ReadFrom()juga membaca seluruh aliran hingga EOF.
djd
8
Bagaimana lucu: Saya baru saja membaca pelaksanaan ioutil.ReadAll()dan itu hanya membungkus bytes.Buffer's ReadFrom. Dan metode buffer String()adalah membungkus casting yang sederhana string- sehingga kedua pendekatannya praktis sama!
djd
1
Ini adalah solusi terbaik, paling ringkas.
mk12
1
Saya melakukan ini dan berhasil ... pertama kali. Untuk beberapa alasan setelah membaca string, urutan membaca mengembalikan string kosong. Belum yakin kenapa.
Aldo 'xoen' Giambelluca
1
@ Aldo'xoen'Giambelluca ReadAll mengkonsumsi pembaca, jadi pada panggilan berikutnya tidak ada yang tersisa untuk dibaca.
DanneJ
9
data, _ := ioutil.ReadAll(response.Body)
fmt.Println(string(data))
yakob abada
sumber
5

Cara yang paling efisien akan selalu menggunakan []bytebukannyastring .

Dalam kasus Anda perlu untuk mencetak data yang diterima dari io.ReadCloser, yang fmtpaket dapat menangani []byte, tetapi tidak efisien karena fmtpelaksanaan internal akan mengkonversi []byteke string. Untuk menghindari konversi ini, Anda dapat mengimplementasikan fmt.Formatterantarmuka untuk jenis suka type ByteSlice []byte.


sumber
Apakah konversi dari [] byte ke string mahal? Saya berasumsi string ([] byte) tidak benar-benar menyalin byte [], tetapi hanya menafsirkan elemen slice sebagai serangkaian rune. Itulah sebabnya saya menyarankan Buffer.String () Weekly.golang.org/src/pkg/bytes/buffer.go?s=1787:1819#L37 . Saya kira akan baik untuk mengetahui apa yang terjadi ketika string ([] byte) dipanggil.
Nate
4
Konversi dari []byteke stringcukup cepat, tetapi pertanyaannya adalah tentang "cara paling efisien". Saat ini, Go run-time akan selalu mengalokasikan yang baru stringsaat dikonversi []byteke string. Alasan untuk ini adalah bahwa kompiler tidak tahu bagaimana menentukan apakah []byteakan dimodifikasi setelah konversi. Ada beberapa ruang untuk optimisasi kompiler di sini.
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
}
Dimchansky
sumber
1
var b bytes.Buffer
b.ReadFrom(r)

// b.String()
Vojtech Vitek
sumber
0

Saya suka struct bytes.Buffer . Saya melihatnya memiliki metode ReadFrom dan String . Saya telah menggunakannya dengan byte [] tetapi bukan io.Reader.

Nate
sumber