De io.Reader à chaîne dans Go


129

J'ai un io.ReadCloserobjet (d'un http.Responseobjet).

Quelle est la manière la plus efficace de convertir l'intégralité du flux en stringobjet?

Réponses:


175

ÉDITER:

Depuis la 1.10, strings.Builder existe. Exemple:

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

INFORMATIONS DÉPASSÉES CI-DESSOUS

La réponse courte est que cela ne sera pas efficace car la conversion en chaîne nécessite de faire une copie complète du tableau d'octets. Voici la bonne façon (non efficace) de faire ce que vous voulez:

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

Cette copie est faite comme un mécanisme de protection. Les chaînes sont immuables. Si vous pouviez convertir un octet [] en chaîne, vous pourriez changer le contenu de la chaîne. Cependant, go vous permet de désactiver les mécanismes de sécurité de type à l'aide du package unsafe. Utilisez l'emballage dangereux à vos propres risques. Espérons que le nom à lui seul est un avertissement suffisant. Voici comment je le ferais en utilisant unsafe:

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

Voilà, vous avez maintenant converti efficacement votre tableau d'octets en chaîne. En réalité, tout cela ne fait que tromper le système de types en l'appelant une chaîne. Il y a quelques mises en garde à cette méthode:

  1. Il n'y a aucune garantie que cela fonctionnera dans tous les compilateurs go. Bien que cela fonctionne avec le compilateur plan-9 gc, il repose sur des "détails d'implémentation" non mentionnés dans la spécification officielle. Vous ne pouvez même pas garantir que cela fonctionnera sur toutes les architectures ou ne sera pas modifié dans gc. En d'autres termes, c'est une mauvaise idée.
  2. Cette chaîne est mutable! Si vous faites des appels sur ce tampon , il va changer la chaîne. Soyez très prudent.

Mon conseil est de s'en tenir à la méthode officielle. Faire une copie n'est pas si cher et cela ne vaut pas les maux de dangereux. Si la chaîne est trop volumineuse pour faire une copie, vous ne devez pas en faire une chaîne.


Merci, c'est une réponse très détaillée. Le "bon" moyen semble à peu près équivalent à la réponse de @ Sonia aussi (puisque buf.String fait juste le casting en interne).
djd

1
Et cela ne fonctionne même pas avec ma version, il semble ne pas pouvoir obtenir un pointeur de & but.Bytes (). Utilisation de Go1.
sinni800

@ sinni800 Merci pour le tuyau. J'ai oublié que les retours de fonction n'étaient pas adressables. C'est maintenant corrigé.
Stephen Weinberg

3
Eh bien, les ordinateurs sont très rapides pour copier des blocs d'octets. Et étant donné qu'il s'agit d'une requête http, je ne peux pas imaginer un scénario où la latence de transmission ne sera pas un squillion de fois plus grande que le temps insignifiant qu'il faut pour copier le tableau d'octets. Tout langage fonctionnel copie ce type de choses immuables partout et fonctionne toujours très vite.
voir plus net

Cette réponse est périmée. strings.Builderfait cela efficacement en garantissant que le sous-jacent []bytene fuit jamais et en effectuant stringune conversion sans copie d'une manière qui sera prise en charge à l'avenir. Cela n'existait pas en 2012. La solution de @ dimchansky ci-dessous est la bonne depuis Go 1.10. Veuillez envisager une modification!
Nuno Cruces

102

Les réponses jusqu'à présent n'ont pas abordé la partie "flux entier" de la question. Je pense que la bonne façon de faire est ioutil.ReadAll. Avec ton io.ReaderClosernom rc, j'écrirais,

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

2
Merci, bonne réponse. Il semble buf.ReadFrom()également lire le flux entier jusqu'à EOF.
djd

8
Comme c'est drôle: je viens de lire l'implémentation de ioutil.ReadAll()et ça enveloppe simplement un bytes.Buffer's ReadFrom. Et la String()méthode du tampon est un simple enroulement autour du casting string- donc les deux approches sont pratiquement les mêmes!
djd

1
C'est la solution la meilleure et la plus concise.
mk12 du

1
J'ai fait ça et ça marche ... la première fois. Pour une raison quelconque, après avoir lu la chaîne, les lectures séquentielles renvoient une chaîne vide. Je ne sais pas encore pourquoi.
Aldo 'xoen' Giambelluca

1
@ Aldo'xoen'Giambelluca ReadAll consomme le lecteur, donc lors du prochain appel, il ne reste plus rien à lire.
DanneJ


5

Le moyen le plus efficace serait de toujours utiliser à la []byteplace de string.

Au cas où vous auriez besoin d'imprimer les données reçues de io.ReadCloser, le fmtpackage peut gérer []byte, mais ce n'est pas efficace car l' fmtimplémentation sera convertie en interne []byteen string. Afin d'éviter cette conversion, vous pouvez implémenter l' fmt.Formatterinterface pour un type comme type ByteSlice []byte.


La conversion de [] octet en chaîne est-elle coûteuse? J'ai supposé que string ([] byte) ne copiait pas réellement l'octet [], mais interprétait simplement les éléments de tranche comme une série de runes. C'est pourquoi j'ai suggéré Buffer.String () hebdomadaire.golang.org/src/pkg/bytes/buffer.go?s=1787:1819#L37 . Je suppose qu'il serait bon de savoir ce qui se passe lorsque string ([] byte) est appelé.
Nate

4
La conversion de []byteà stringest raisonnablement rapide, mais la question était de savoir "le moyen le plus efficace". Actuellement, le runtime Go allouera toujours un nouveau stringlors de la conversion []byteen string. La raison en est que le compilateur ne sait pas comment déterminer si le []bytesera modifié après la conversion. Il y a de la place pour les optimisations du compilateur ici.

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
}


En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.