Jak przekonwertować tablicę bajtów zakończoną zerem na ciąg?


502

Muszę przeczytać, [100]byteaby przenieść sporo stringdanych.

Ponieważ nie wszystkie stringlitery mają dokładnie 100 znaków, pozostała część byte arrayjest wypełniona literą 0s.

Jeśli przekonwertować [100]bytedo stringprzez: string(byteArray[:]), odpadzie 0s są wyświetlane jako ^@^@s.

W C stringwygasa po 0, więc zastanawiam się, jaki jest najlepszy sposób przekonwertować to byte arraydo stringw Golang.


3
@ AndréLaszlo: Na placu zabaw ^@nie pokazuje się, ale byłoby tam, gdybyś przetestował to w terminalu lub coś podobnego. Powodem tego jest to, że Go nie przestaje konwertować tablicy bajtów na ciąg, gdy znajdzie 0. len(string(bytes))w twoim przykładzie jest 5, a nie 1. To zależy od funkcji wyjściowej, czy ciąg zostanie wydrukowany w całości (z zerami) albo nie.
nemo

8
Jako treść odpowiedzi http użyj string(body).
Ivan Chau

Odpowiedzi:


513

Metody, które odczytują dane do plasterków bajtów, zwracają liczbę odczytanych bajtów. Powinieneś zapisać tę liczbę, a następnie użyć jej do utworzenia łańcucha Jeśli njest to liczba odczytanych bajtów, kod wyglądałby następująco:

s := string(byteArray[:n])

Aby przekonwertować pełny ciąg, można tego użyć:

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

Jest to równoważne z:

s := string(byteArray)

Jeśli z jakiegoś powodu nie wiesz n, możesz użyć bytespakietu, aby go znaleźć, zakładając, że twój wpis nie zawiera znaku null.

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

Lub, jak wskazał icza, możesz użyć kodu poniżej:

n := bytes.IndexByte(byteArray, 0)

2
Wiem, że jestem spóźniony o rok, ale powinienem wspomnieć, że większość metod zwraca liczbę odczytanych bajtów. Na przykład binary.Read () może wczytywać bajt [32], ale nie wiesz, czy wypełniono wszystkie 32 bajty, czy nie.
Eric Lagergren,

7
Powinieneś użyć, bytes.IndexByte()który wyszukuje pojedynczy bytezamiast bytes.Index()z bajtem zawierającym 1 bajt.
icza

56
właściwie string (byteArray) też zrobi i zapisze tworzenie plasterka
throws_exceptions_at_you

3
Żeby było jasne, przekazuje to sekwencję bajtów do czegoś, co, mam nadzieję, jest poprawnym ciągiem UTF-8 (a nie mówiąc, Latin-1 itd. Lub jakąś źle zniekształconą sekwencję UTF-8). Go nie sprawdzi tego podczas rzucania.
Cameron Kerr,

Co się stanie, jeśli tablica bajtów jest w odwrotnej kolejności, czyli mała endian?
Sir,

374

Co powiesz na?

s := string(byteArray[:])

3
Na pewno najczystszy sposób na konwersję tablicy bajtów. Zastanawiam się, czy string.Trim pomógłby usunąć bajty zerowe? golang.org/pkg/strings/#example_Trim
andyvanee

24
pytanie wyraźnie mówi, że string(byteArray[:])zawiera ^@znaki
Robert

24
Jaka jest różnica string(byteArray)? Dlaczego musisz skopiować tablicę za pomocą [:]?
Robert Zaremba,

7
@RobertZaremba> ciąg jest w rzeczywistości fragmentem bajtów tylko do odczytu. Nie można przekonwertować tablicy bajtów bezpośrednio na ciąg, więc najpierw przekrój, a następnie ciąg.
ferhat elmas

3
@RobertZaremba W przypadku plasterków bajtów nie trzeba dodawać [:], w przypadku tablic bajtów wystarczy .
Drew LeSueur,

68

Uproszczone rozwiązanie:

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

Nie jestem jednak pewien, jak wydajne to jest.


17

Na przykład,

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)
}

Wynik:

C:  100 [97 98 99 0]
Go: 3 abc

8

Poniższy kod szuka „\ 0” i przy założeniu pytania tablicę można uznać za posortowaną, ponieważ wszystkie inne niż „\ 0” poprzedzają wszystkie „\ 0”. To założenie nie będzie miało zastosowania, jeśli tablica może zawierać „\ 0” w danych.

Znajdź lokalizację pierwszego zera za pomocą wyszukiwania binarnego, a następnie pokrój.

Możesz znaleźć bajt zerowy w ten sposób:

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))
}

Szybsze może być tylko naiwne skanowanie tablicy bajtów w poszukiwaniu bajtu zerowego, szczególnie jeśli większość ciągów znaków jest krótka.


8
Twój kod się nie kompiluje i nawet jeśli tak się stanie, nie będzie działać. Algorytm wyszukiwania binarnego znajduje pozycję określonej wartości w posortowanej tablicy. Tablica niekoniecznie jest posortowana.
peterSO

@peterSO Masz rację i tak naprawdę nigdy nie jest sortowany, ponieważ reprezentuje kilka znaczących nazw.
Derrick Zhang,

3
Jeśli wszystkie bajty zerowe znajdują się na końcu ciągu, wyszukiwanie binarne działa.
Paul Hankin,

6
Nie rozumiem głosów negatywnych. Kod kompiluje się i jest poprawny, zakładając, że ciąg nie zawiera \ 0, z wyjątkiem końca. Kod szuka \ 0 i przy założeniu pytania tablicę można uznać za „posortowaną”, ponieważ wszystkie inne niż \ 0 poprzedzają wszystkie \ 0 i to wszystko kod sprawdza. Jeśli downvoters mógłby znaleźć przykładowe dane wejściowe, na których kod nie działa, usunę odpowiedź.
Paul Hankin,

1
Daje zły wynik, jeśli dane wejściowe są []byte{0}. W takim przypadku FirstZero()powinien zwrócić, 0więc kiedy wynik krojenia będzie "", ale zamiast tego zwraca 1i krojenie wyników "\x00".
icza

3

Jeśli nie znasz dokładnej długości bajtów innych niż zero w tablicy, możesz ją najpierw przyciąć:

string (bytes.Trim (arr, „\ x00”))


1
a) bytes.Trimpobiera plasterek, a nie tablicę (będziesz potrzebować, arr[:]jeśli arr jest w rzeczywistości, [100]bytejak podaje pytanie). b) bytes.Trimjest niewłaściwą funkcją do użycia tutaj. Dla danych wejściowych takich jak []byte{0,0,'a','b','c',0,'d',0}zwróci „abc \ x00d” zamiast „” c) istnieje już poprawna odpowiedź, która używa bytes.IndexByte, najlepszy sposób na znalezienie pierwszego bajtu zerowego.
Dave C

1

Dlaczego nie to

bytes.NewBuffer(byteArray).String()

1
Ponieważ a) pytanie zawiera tablicę, więc będziesz potrzebować, byteArray[:]ponieważ bytes.NewBuffertrwa []byte; b) pytanie mówi, że tablica ma końcowe zera, z którymi nie masz do czynienia; c) jeśli zamiast tego twoja zmienna jest []byte(jedynym sposobem, w jaki twoja linia się skompiluje), to twoja linia jest tylko powolnym sposobem string(v).
Dave C

1

Używaj tylko do strojenia wydajności.

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))
}

1
-1: Nie jestem pewien, czy jest to poważna odpowiedź, ale prawie na pewno nie chcesz wywoływać refleksji i niebezpiecznego kodu tylko po to, aby przekonwertować kawałek bajtu na ciąg znaków
Austin Hyde

1
Słowo ostrzeżenia: użycie niebezpiecznej do konwersji wycinka bajtu na a stringmoże mieć poważne konsekwencje, jeśli później zostanie zmodyfikowany wycinek bajtu. stringwartości w Go są zdefiniowane jako niezmienne, na których bazują całe środowisko wykonawcze Go i biblioteki. Jeśli pójdziesz tą ścieżką, przeniesiesz się w sam środek najbardziej tajemniczych błędów i błędów w czasie wykonywania.
icza

Edytowano, ponieważ jest to sprzeczne z użyciem wskaźnika (ma to samo zachowanie, co rzutowanie bezpośrednie, innymi słowy wynik nie zostanie wyrzucony). Przeczytaj akapit (6) golang.org/pkg/unsafe/#Pointer
Laevus Dexter

0
  • Do czytania używaj wycinków zamiast tablic. np. io.Readerakceptuje plasterek, a nie tablicę.

  • Zastosuj krojenie zamiast zerowania.

Przykład:

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

Dane są pisane przez innych i przez inny język C. Musiałem je tylko przeczytać, więc nie mogę kontrolować sposobu, w jaki są zapisywane.
Derrick Zhang

1
Och, a następnie pokrój tablicę bajtów przy użyciu wartości długości s := a[:n]lub s := string(a[:n])jeśli potrzebujesz łańcucha. Jeśli nnie jest bezpośrednio dostępny, należy go obliczyć, np. Szukając określonego / zerowego bajtu w buforze (tablicy), jak sugeruje Daniel.
zzzz


0

Chociaż nie jest to wyjątkowo wydajne, jedynym możliwym do odczytania rozwiązaniem jest

  //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]

Pełny przykład posiadania tablicy bajtów w stylu 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)
}

Pełny przykład posiadania łańcucha stylu go z wyłączeniem wartości 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)
}

To przydziela plasterek bajtu. Miej oko na wydajność, jeśli jest intensywnie lub wielokrotnie używana.


-1

Oto kod do kompresji tablicy bajtów na ciąg

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
}

-2

Oto szybszy sposób:

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

Następnym razem przeczytaj najpierw pytanie (i istniejące odpowiedzi). (Plus, jeśli faktycznie chcesz wydrukować kawałek bajtu za pomocą, fmtjest to szybsze fmt.Printf("%s", bytes)niż użycie string(bytes)).
Dave C

-7

Ja z rozwiązaniem rekurencyjnym.

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, ""))
}

Dlaczego lubisz rozwiązania rekurencyjne?
peterSO

Przypadek testowy fmt.Println(CToGoString([]byte("ctogo\x00\x00"), "") == "ctogo")powinien zostać wydrukowany true, zostanie wydrukowany false.
peterSO

1
Pytanie pyta, jaki jest najlepszy sposób. Jest to tak złe, jak to tylko możliwe: trudne do zrozumienia i bardzo wolne, a także nie konwertuje a, [100]byteale []bytenie usuwa '\x00'bajtów. Jego prędkość (w zależności od danych wejściowych) jest mniejsza o wiele rzędów wielkości w porównaniu do prędkości przyjętej odpowiedzi.
icza
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.