Comment convertir un tableau d'octets à zéro en chaîne?

502

J'ai besoin de lire [100]bytepour transférer un tas de stringdonnées.

Étant donné que tous les strings ne comportent pas exactement 100 caractères, la partie restante du byte arrayest remplie de 0s.

Si je convertis [100]byteen stringpar string(byteArray[:]):, les 0s sont affichés comme ^@^@s.

En C la stringprendra fin à 0, alors je me demande quelle est la meilleure façon de convertir ce byte arrayà stringen golang.

Derrick Zhang
la source
3
@ AndréLaszlo: Dans la cour de récréation, le ^@ne s'affiche pas, mais il aurait été là si vous l'aviez testé dans le terminal ou quelque chose de similaire. La raison en est que Go n'arrête pas de convertir le tableau d'octets en chaîne lorsqu'il trouve un 0. len(string(bytes))dans votre exemple est 5 et non 1. Cela dépend de la fonction de sortie, si la chaîne est entièrement (avec des zéros) imprimée ou pas.
nemo
8
Pour le corps de réponse http, utilisez string(body).
Ivan Chau

Réponses:

513

Les méthodes qui lisent des données dans des tranches d'octets renvoient le nombre d'octets lus. Vous devez enregistrer ce nombre, puis l'utiliser pour créer votre chaîne. Si nest le nombre d'octets lus, votre code ressemblera à ceci:

s := string(byteArray[:n])

Pour convertir la chaîne complète, cela peut être utilisé:

s := string(byteArray[:len(byteArray)])

Cela équivaut à:

s := string(byteArray)

Si pour une raison que vous ne connaissez pas n, vous pouvez utiliser le bytespackage pour le trouver, en supposant que votre entrée ne comporte pas de caractère nul.

n := bytes.Index(byteArray, []byte{0})

Ou comme l'a souligné icza, vous pouvez utiliser le code ci-dessous:

n := bytes.IndexByte(byteArray, 0)
Daniel
la source
2
Je sais que j'ai un an de retard, mais je dois mentionner que la plupart des méthodes renvoient le nombre d'octets lus. Par exemple, binary.Read () peut lire dans un octet [32], mais vous ne savez pas si vous avez rempli les 32 octets ou non.
Eric Lagergren
7
Vous devez utiliser bytes.IndexByte()qui recherche un seul byteau lieu d' bytes.Index()une tranche d'octets contenant 1 octet.
icza
56
en fait, la chaîne (byteArray) fera également l'affaire et enregistrera une création de tranche
throws_exceptions_at_you
3
Juste pour être clair cependant, cela transforme une séquence d'octets en quelque chose qui, espérons-le, est une chaîne UTF-8 valide (et ne dit pas Latin-1 etc., ou une séquence UTF-8 mal formée). Go ne vérifiera pas cela pour vous lorsque vous lancez.
Cameron Kerr
Que faire si votre tableau d'octets est dans l'ordre inverse aka petit endian?
Sir
374

Qu'en est-il de?

s := string(byteArray[:])
mattes
la source
3
La façon la plus propre de convertir le tableau d'octets à coup sûr. Je me demande si strings.Trim aiderait à éliminer les octets nuls? golang.org/pkg/strings/#example_Trim
andyvanee
24
la question dit spécifiquement qui string(byteArray[:])contient des ^@personnages
Robert
24
Quelle est la différence string(byteArray)? Pourquoi devez-vous copier le tableau à l'aide de [:]?
Robert Zaremba
7
@RobertZaremba> une chaîne est en fait une tranche d'octets en lecture seule. Vous ne pouvez pas convertir le tableau d'octets directement en chaîne, donc d'abord tranche puis chaîne.
ferhat elmas
3
@RobertZaremba Pour les tranches d'octets, vous n'avez pas besoin d'ajouter le [:], pour les tableaux d'octets, vous le faites.
Drew LeSueur
68

Solution simpliste:

str := fmt.Sprintf("%s", byteArray)

Je ne sais pas à quel point c'est performant.

marcusljx
la source
17

Par exemple,

package main

import "fmt"

func CToGoString(c []byte) string {
    n := -1
    for i, b := range c {
        if b == 0 {
            break
        }
        n = i
    }
    return string(c[:n+1])
}

func main() {
    c := [100]byte{'a', 'b', 'c'}
    fmt.Println("C: ", len(c), c[:4])
    g := CToGoString(c[:])
    fmt.Println("Go:", len(g), g)
}

Production:

C:  100 [97 98 99 0]
Go: 3 abc
peterSO
la source
8

Le code suivant recherche '\ 0', et sous les hypothèses de la question, le tableau peut être considéré comme trié puisque tous les non-'\ 0' précèdent tous les '\ 0'. Cette hypothèse ne tiendra pas si le tableau peut contenir «\ 0» dans les données.

Recherchez l'emplacement du premier octet zéro à l'aide d'une recherche binaire, puis découpez.

Vous pouvez trouver le zéro octet comme ceci:

package main

import "fmt"

func FirstZero(b []byte) int {
    min, max := 0, len(b)
    for {
        if min + 1 == max { return max }
        mid := (min + max) / 2
        if b[mid] == '\000' {
            max = mid
        } else {
            min = mid
        }
    }
    return len(b)
}
func main() {
    b := []byte{1, 2, 3, 0, 0, 0}
    fmt.Println(FirstZero(b))
}

Il peut être plus rapide de balayer naïvement le tableau d'octets à la recherche du zéro octet, surtout si la plupart de vos chaînes sont courtes.

Paul Hankin
la source
8
Votre code ne se compile pas et, même si c'est le cas, il ne fonctionnera pas. Un algorithme de recherche binaire trouve la position d'une valeur spécifiée dans un tableau trié. Le tableau n'est pas nécessairement trié.
peterSO
@peterSO Vous avez raison, et en fait, il n'est jamais trié car il représente un tas de noms significatifs.
Derrick Zhang
3
Si tous les octets nuls se trouvent à la fin de la chaîne, une recherche binaire fonctionne.
Paul Hankin
6
Je ne comprends pas les downvotes. Le code se compile et est correct, en supposant que la chaîne ne contient aucun \ 0 sauf à la fin. Le code recherche \ 0, et sous les hypothèses de la question, le tableau peut être considéré comme "trié", car tous les non \ 0 précèdent tous les \ 0 et c'est tout ce que le code vérifie. Si les downvoters pouvaient trouver un exemple d'entrée sur lequel le code ne fonctionne pas, alors je supprimerai la réponse.
Paul Hankin
1
Donne un résultat incorrect si l'entrée l'est []byte{0}. Dans ce cas, il FirstZero()devrait revenir 0quand le résultat de la coupe serait "", mais à la place, il reviendrait 1et la coupe résulterait en "\x00".
icza
3

Lorsque vous ne connaissez pas la longueur exacte des octets non nuls dans le tableau, vous pouvez d'abord le couper:

chaîne (octets.Trim (arr, "\ x00"))

zach
la source
1
a) bytes.Trimprend une tranche, pas un tableau (vous auriez besoin arr[:]si arr est en fait un [100]bytecomme l'indique la question). b) bytes.Trimest la mauvaise fonction à utiliser ici. Pour une entrée telle []byte{0,0,'a','b','c',0,'d',0}qu'elle renverra "abc \ x00d" au lieu de "" c), il existe déjà une réponse correcte qui utilise bytes.IndexByte, la meilleure façon de trouver le premier octet zéro.
Dave C
1

Pourquoi pas ça?

bytes.NewBuffer(byteArray).String()
Bhagya Prasad NR
la source
1
Parce que a) la question dit un tableau, donc vous auriez besoin byteArray[:]depuis bytes.NewBufferprend un []byte; b) la question disait que le tableau a des zéros de fin que vous ne traitez pas; c) si à la place votre variable est un []byte(la seule façon dont votre ligne se compilera), alors votre ligne est juste une manière lente de le faire string(v).
Dave C
1

À utiliser uniquement pour l'optimisation des performances.

package main

import (
    "fmt"
    "reflect"
    "unsafe"
)

func BytesToString(b []byte) string {
    return *(*string)(unsafe.Pointer(&b))
}

func StringToBytes(s string) []byte {
    return *(*[]byte)(unsafe.Pointer(&s))
}

func main() {
    b := []byte{'b', 'y', 't', 'e'}
    s := BytesToString(b)
    fmt.Println(s)
    b = StringToBytes(s)
    fmt.Println(string(b))
}
Yuen
la source
1
-1: Je ne sais pas si c'est une réponse sérieuse, mais vous ne voulez certainement pas invoquer de la réflexion et du code dangereux juste pour convertir une tranche d'octets en chaîne
Austin Hyde
1
Un mot d'avertissement: l'utilisation de dangereux pour convertir une tranche d'octets en stringpeut avoir de sérieuses implications si plus tard la tranche d'octets est modifiée. stringles valeurs de Go sont définies comme immuables, sur lesquelles l'intégralité du runtime Go et des bibliothèques s'appuient. Vous vous téléporterez au milieu des bugs les plus mystérieux et des erreurs d'exécution si vous suivez cette voie.
icza
Modifié, car cela est contraire à l'utilisation du pointeur (il a le même comportement que la conversion directe, en d'autres termes, le résultat ne sera pas récupéré). Lire le paragraphe (6) golang.org/pkg/unsafe/#Pointer
Laevus Dexter
0
  • Utilisez des tranches au lieu de tableaux pour la lecture. par exemple io.Readeraccepte une tranche, pas un tableau.

  • Utilisez le découpage au lieu du remplissage nul.

Exemple:

buf := make([]byte, 100)
n, err := myReader.Read(buf)
if n == 0 && err != nil {
        log.Fatal(err)
}

consume(buf[:n]) // consume will see exact (not padded) slice of read data
zzzz
la source
Les données sont écrites par d'autres et par un autre langage C, et je n'ai pu les lire que je ne peux pas contrôler la façon dont elles sont écrites.
Derrick Zhang
1
Oh, puis découpez le tableau d'octets en utilisant une valeur de longueur s := a[:n]ou s := string(a[:n])si vous avez besoin d'une chaîne. S'il nn'est pas directement disponible, il doit être calculé, par exemple en recherchant un octet spécifique / zéro dans le tampon (tableau) comme le suggère Daniel.
zzzz
0

J'ai essayé quelques méthodes quelques fois, j'ai eu la panique:

erreur d'exécution: tranches hors limites.

Mais cela a finalement fonctionné.

string(Data[:])

Gaurav Singh
la source
3
Cela n'ajoute pas beaucoup d'informations et répète essentiellement la réponse de 2013: stackoverflow.com/a/18615786/349333 .
Jochem Schulenklopper
0

Bien que peu performante, la seule solution lisible est

  //split by separator and pick the first one. 
  //This has all the characters till null excluding null itself.
  retByteArray := bytes.Split(byteArray[:], []byte{0}) [0]

  // OR 

  //If you want a true C-like string including the null character
  retByteArray := bytes.SplitAfter(byteArray[:], []byte{0}) [0]

Exemple complet pour avoir un tableau d'octets de style C:

package main

import (
    "bytes"
    "fmt"
)

func main() {
    var byteArray = [6]byte{97,98,0,100,0,99}

    cStyleString := bytes.SplitAfter(byteArray[:],  []byte{0}) [0]
    fmt.Println(cStyleString)
}

Exemple complet pour avoir une chaîne de style go excluant les null:

package main

import (
    "bytes"
    "fmt"
)

func main() {
    var byteArray = [6]byte{97,98,0,100,0,99}

    goStyleString := string( bytes.Split(byteArray[:],  []byte{0}) [0] )
    fmt.Println(goStyleString)
}

Cela alloue une tranche de tranche d'octets. Gardez donc un œil sur les performances en cas d'utilisation intensive ou répétée.

manikawnth
la source
-1

Voici le code pour compresser le tableau d'octets en chaîne

package main

import (
    "fmt"
)

func main() {
    byteArr := [100]byte{'b', 'y', 't', 'e', 's'}
    firstHalf := ToString(byteArr)
    fmt.Println("Bytes to str", string(firstHalf))
}
func ToString(byteArr [100]byte) []byte {
    arrLen := len(byteArr)
    firstHalf := byteArr[:arrLen/2]
    secHalf := byteArr[arrLen/2:]
    for {
        // if the first element is 0 in secondHalf discard second half
        if len(secHalf) != 0 && secHalf[0] == 0 {
            arrLen = len(firstHalf)
            secHalf = firstHalf[arrLen/2:]
            firstHalf = firstHalf[:arrLen/2]
            continue
        } else {
            for idx := 0; len(secHalf) > idx && secHalf[idx] != 0; idx++ {
                firstHalf = append(firstHalf, secHalf[idx])
            }
        }
        break
    }
    return firstHalf
}
Lalit Sharma
la source
-2

Voici le moyen le plus rapide:

resp, _ := http.Get("https://www.something.com/something.xml")
bytes, _ := ioutil.ReadAll(resp.Body)
resp.Body.Close()
fmt.Println(string(bytes)) //just convert with string() function
Arturo Morales Rangel
la source
La prochaine fois, lisez d'abord la question (et les réponses existantes). (De plus, si vous voulez réellement imprimer une tranche d'octets via, fmtc'est plus rapide à faire fmt.Printf("%s", bytes)qu'à utiliser string(bytes)).
Dave C
-7

Je quand avec une solution récursive.

func CToGoString(c []byte, acc string) string {

    if len(c) == 0 {
        return acc
    } else {
        head := c[0]
        tail := c[1:]
        return CToGoString(tail, acc + fmt.Sprintf("%c", head))
    }
}

func main() {
    b := []byte{some char bytes}
    fmt.Println(CToGoString(b, ""))
}
Romel Campbell
la source
Pourquoi aimez-vous une solution récursive?
peterSO
Le cas de test fmt.Println(CToGoString([]byte("ctogo\x00\x00"), "") == "ctogo")doit imprimer true, il imprime false.
peterSO
1
La question demande quelle est la meilleure façon. C'est aussi mauvais que possible: difficile à comprendre et extrêmement lent, il ne convertit pas non plus un [100]bytemais un []byteet ne supprime pas les '\x00'octets. Sa vitesse (dépend de l'entrée) est plus lente de plusieurs ordres de grandeur par rapport à la vitesse de la réponse acceptée.
icza