İo.Reader'dan Go'daki dizelere


129

Bir io.ReadClosernesnem var (bir http.Responsenesneden).

Akışın tamamını bir stringnesneye dönüştürmenin en etkili yolu nedir ?

Yanıtlar:


175

DÜZENLE:

1.10'dan beri string.Builder var. Misal:

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

AŞAĞIDAKİ GEÇMİŞ BİLGİLER

Kısa cevap, bunun verimli olmayacağıdır çünkü bir dizgeye dönüştürmek, bayt dizisinin tam bir kopyasını yapmayı gerektirir. İstediğinizi yapmanın doğru (verimli olmayan) yolu:

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

Bu kopya bir koruma mekanizması olarak yapılır. Dizeler değişmezdir. Bir [] baytı bir dizgeye dönüştürebilseydiniz, dizenin içeriğini değiştirebilirsiniz. Bununla birlikte, go, güvenli olmayan paketi kullanarak tür güvenlik mekanizmalarını devre dışı bırakmanıza izin verir. Güvenli olmayan paketi kullanma riski size aittir. Umarım tek başına isim yeterince iyi bir uyarıdır. Güvensiz kullanarak bunu şu şekilde yapardım:

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

İşte başlıyoruz, şimdi bayt dizinizi verimli bir şekilde bir dizeye dönüştürdünüz. Gerçekte, tüm bunlar tip sistemini kandırarak onu bir dizge olarak adlandırmaktır. Bu yönteme ilişkin birkaç uyarı vardır:

  1. Bunun all go derleyicilerinde çalışacağına dair hiçbir garanti yoktur. Bu plan-9 gc derleyicisi ile çalışırken, resmi spesifikasyonda belirtilmeyen "uygulama ayrıntılarına" dayanır. Bunun tüm mimarilerde çalışacağını veya gc'de değiştirilmeyeceğini garanti edemezsiniz bile. Başka bir deyişle, bu kötü bir fikir.
  2. Bu dize değiştirilebilir! O tampon üzerinde hiçbir arama yaparsanız o olacak dizesini değiştirin. Çok dikkatli ol.

Benim tavsiyem resmi yönteme bağlı kalmaktır. Bir kopya yapmak o kadar pahalı değil ve güvensizliğin kötülüklerine değmez. Dize bir kopya yapmak için çok büyükse, onu bir dizeye dönüştürmemelisiniz.


Teşekkürler, bu gerçekten ayrıntılı bir cevap. "İyi" yol kabaca @ Sonia'nın cevabına denk görünüyor (çünkü buf.String sadece kadroyu dahili olarak yapıyor).
djd

1
Ve benim sürümümle bile çalışmıyor, görünen o ki & but.Bytes () 'den bir İşaretçi alamıyor. Go1'i kullanma.
sinni800

@ sinni800 Bahşiş için teşekkürler. İşlev dönüşlerinin adreslenebilir olmadığını unuttum. Artık düzeltildi.
Stephen Weinberg

3
Bilgisayarlar bayt bloklarını kopyalamada oldukça hızlıdır. Ve bu bir http isteği olduğu için, iletim gecikmesinin bayt dizisini kopyalamak için gereken önemsiz süreden bir skilyon kat daha fazla olmayacağı bir senaryo hayal edemiyorum. Herhangi bir işlevsel dil, bu tür değişmez şeyleri her yerde kopyalar ve yine de çok hızlı çalışır.
daha keskin görün

Bu cevap güncel değil. strings.Builderbunu, temelin []byteasla sızmamasını sağlayarak ve stringileride desteklenecek bir şekilde kopyasız hale getirerek verimli bir şekilde yapar. Bu 2012'de yoktu. @ Dimchansky'nin aşağıdaki çözümü, 1.10'dan beri doğru olanıydı. Lütfen bir düzenleme yapmayı düşünün!
Nuno Cruces

102

Şimdiye kadar verilen yanıtlar, sorunun "tüm akış" kısmına değinmedi. Sanırım bunu yapmanın en iyi yolu ioutil.ReadAll. Senin ile io.ReaderCloseradlandırılmış rc, ben yazardım,

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

2
Teşekkürler, güzel cevap. Aynı buf.ReadFrom()zamanda EOF'ye kadar tüm akışı okuyor gibi görünüyor .
djd

8
Ne kadar komik: Sadece uygulamasını okudum ioutil.ReadAll()ve basitçe a bytes.Buffer' yı sarıyor ReadFrom. Ve tamponun String()yöntemi, dökümün etrafında basit bir sarmadır string- bu nedenle iki yaklaşım pratikte aynıdır!
djd

1
Bu en iyi, en özlü çözümdür.
mk12

1
Bunu yaptım ve işe yarıyor ... ilk seferinde. Diziyi okuduktan sonra bazı nedenlerden dolayı, ardışık okumalar boş bir dizge döndürür. Henüz neden emin değilim.
Aldo 'xoen' Giambelluca

1
@ Aldo'xoen'Giambelluca ReadAll okuyucuyu tüketir, bu nedenle bir sonraki aramada okunacak hiçbir şey kalmaz.
DanneJ


5

En verimli yol, []byteyerine her zaman kullanmak olacaktır string.

Adresinden alınan verileri yazdırmanız gerektiğinde io.ReadCloser, fmtpaket işleyebilir []byte, ancak bu verimli değildir çünkü fmtuygulama dahili []byteolarak string. Bu dönüşümü önlemek fmt.Formatteriçin , arayüzü gibi bir tür için uygulayabilirsiniz type ByteSlice []byte.


[] Bayttan dizgeye dönüştürme pahalı mı? String ([] bayt) 'ın [] baytı kopyalamadığını, sadece dilim elemanlarını bir dizi run olarak yorumladığını varsaydım. Bu nedenle Buffer.String () 'i haftalık.golang.org/src/pkg/bytes/buffer.go?s=1787:1819#L37 önerdim . Sanırım string ([] byte) çağrıldığında neler olduğunu bilmek iyi olur.
Nate

4
'Dan' []bytea dönüştürme stringoldukça hızlıdır, ancak soru "en verimli yol" ile ilgiliydi. Şu anda, Go çalışma zamanı her zaman yeni bir tahsis edecek stringdönüştürürken []byteiçin string. Bunun nedeni, derleyicinin []bytedönüşümden sonra değiştirilip değiştirilmeyeceğini nasıl belirleyeceğini bilmemesidir . Derleyici optimizasyonları için burada biraz yer var.

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
}


Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.