Как преобразовать нулевой байтовый массив в строку?


502

Мне нужно прочитать, [100]byteчтобы передать кучу stringданных.

Поскольку не все strings имеют длину в 100 символов, оставшаяся часть byte arrayдополняется символом 0s.

Если я преобразую [100]byteв string:, string(byteArray[:])хвосты 0s отображаются как ^@^@s.

В C stringзакончится по 0, так интересно , что это лучший способ , чтобы преобразовать это byte arrayв stringв Golang.


3
@ AndréLaszlo: На игровой площадке ^@не показывается, но он был бы там, если бы вы проверили его в терминале или что-то подобное. Причина этого в том, что Go не прекращает преобразование массива байтов в строку, когда находит 0 len(string(bytes))в вашем примере, а не 1. Это зависит от функции вывода, полностью ли (с нулями) напечатана строка или нет.
Немо

8
Для тела ответа http используйте string(body).
Иван Чау

Ответы:


513

Методы, которые читают данные в байтовые фрагменты, возвращают количество прочитанных байтов. Вы должны сохранить это число и затем использовать его для создания вашей строки. Если nэто число прочитанных байтов, ваш код будет выглядеть так:

s := string(byteArray[:n])

Для преобразования полной строки это может быть использовано:

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

Это эквивалентно:

s := string(byteArray)

Если по какой-то причине вы не знаете n, вы можете использовать bytesпакет, чтобы найти его, предполагая, что в вашем вводе нет встроенного нулевого символа.

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

Или, как указал icza, вы можете использовать код ниже:

n := bytes.IndexByte(byteArray, 0)

2
Я знаю, что опоздал на год, но я должен отметить, что большинство методов возвращает количество прочитанных байтов. Например, binary.Read () может считывать байт [32], но вы не знаете, заполнили ли вы все 32 байта или нет.
Эрик Лагергрен

7
Вы должны использовать bytes.IndexByte()поиск по одному, byteа не по bytes.Index()байтовому срезу, содержащему 1 байт.
icza

56
на самом деле строка (byteArray) тоже подойдет и сохранит создание среза
throws_exceptions_at_you

3
Просто чтобы прояснить это, это приведение последовательности байтов к чему-то, что, мы надеемся, является допустимой строкой UTF-8 (а не, скажем, Latin-1 и т. Д., Или какой-то неверно сформированной последовательностью UTF-8). Go не будет проверять это для вас, когда вы разыгрываете.
Кэмерон Керр

Что делать, если ваш байтовый массив находится в обратном порядке, то есть в порядке байтов?
Сэр

374

Что о?

s := string(byteArray[:])

3
Самый чистый способ конвертировать байтовый массив наверняка. Интересно, если строки. Трим поможет удалить нулевые байты? golang.org/pkg/strings/#example_Trim
andyvanee

24
вопрос конкретно говорит, что string(byteArray[:])содержит ^@символы
Роберт

24
Какая разница string(byteArray)? Почему вам нужно скопировать массив с помощью [:]?
Роберт Заремба

7
@RobertZaremba> строка фактически является частью байтов только для чтения. Вы не можете конвертировать байтовый массив непосредственно в строку, поэтому сначала срез, а затем строку.
Ferhat Elmas

3
@RobertZaremba Для байтовых срезов вам не нужно добавлять [:], для байтовых массивов вы это делаете.
Drew LeSueur

68

Упрощенное решение:

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

Я не уверен, насколько это эффективно.


17

Например,

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

Вывод:

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

8

Следующий код ищет «\ 0», и в соответствии с предположениями вопроса массив можно считать отсортированным, поскольку все не-\ 0 предшествуют всем «\ 0». Это предположение не будет выполнено, если массив может содержать '\ 0' в данных.

Найдите местоположение первого нулевого байта с помощью двоичного поиска, а затем среза.

Вы можете найти нулевой байт следующим образом:

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

Может быть быстрее просто наивно сканировать байтовый массив в поисках нулевого байта, особенно если большинство ваших строк короткие.


8
Ваш код не компилируется, и даже если он это сделал, он не будет работать. Алгоритм бинарного поиска находит позицию указанного значения в отсортированном массиве. Массив не обязательно отсортирован.
peterSO

@peterSO Вы правы, и на самом деле он никогда не сортируется, поскольку он представляет собой набор значимых имен.
Деррик Чжан

3
Если все нулевые байты находятся в конце строки, работает двоичный поиск.
Пол Ханкин

6
Я не понимаю отрицательных голосов. Код компилируется и является правильным, предполагая, что строка не содержит \ 0, кроме как в конце. Код ищет \ 0, и в предположениях вопроса массив можно считать «отсортированным», поскольку все не-0 предшествуют всем \ 0, и это все, что проверяет код. Если downvoters могут найти пример ввода, на котором код не работает, то я удалю ответ.
Пол Ханкин

1
Дает неверный результат, если ввод []byte{0}. В этом случае FirstZero()должен возвращать 0так, когда результат нарезки будет "", но вместо этого он возвращает 1и результаты нарезки в "\x00".
icza

3

Если вы не знаете точную длину ненулевых байтов в массиве, вы можете сначала обрезать ее:

строка (bytes.Trim (arr, "\ x00"))


1
а) bytes.Trimберет срез, а не массив (вам нужно, arr[:]если arr на самом деле является a, [100]byteкак указано в вопросе). б) bytes.Trimэто неправильная функция для использования здесь. Для ввода, подобного []byte{0,0,'a','b','c',0,'d',0}ему, будет возвращено «abc \ x00d» вместо «» c) там уже есть правильный ответ, который использует bytes.IndexByteлучший способ найти первый нулевой байт.
Дейв С

1

Почему не это?

bytes.NewBuffer(byteArray).String()

1
Потому что а) вопрос говорит о массиве, так что вам нужно, byteArray[:]так как bytes.NewBufferпринимает []byte; б) вопрос о том, что массив имеет конечные нули, с которыми вы не имеете дело; c) если вместо этого ваша переменная - это []byte(единственный способ, которым ваша строка будет компилироваться), то ваша строка - это просто медленный способ выполнения string(v).
Дейв С

1

Используйте только для настройки производительности.

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: Не уверен, если это серьезный ответ, но вы почти наверняка не хотите вызывать рефлексию и небезопасный код только для того, чтобы преобразовать фрагмент байта в строку
Остин Хайд,

1
Предупреждение: использование небезопасного для преобразования байтового фрагмента в a stringможет иметь серьезные последствия, если позже модифицировать байтовый фрагмент. stringзначения в Go определены как неизменяемые, на которых основывается вся среда выполнения Go и библиотеки. Если вы пойдете по этому пути, вы будете телепортироваться в самые загадочные ошибки и ошибки времени выполнения.
icza

Отредактировано, потому что это против использования указателя (оно ведет себя так же, как и прямое приведение, другими словами, результат не будет собирать мусор). Прочитайте абзац (6) golang.org/pkg/unsafe/#Pointer
Laevus Dexter

0
  • Используйте фрагменты вместо массивов для чтения. например, io.Readerпринимает срез, а не массив.

  • Используйте нарезку вместо заполнения нулями.

Пример:

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

Данные пишутся другими и другим языком Си, и мне нужно только прочитать их, поэтому я не могу контролировать способ их записи.
Деррик Чжан

1
О, тогда нарежьте массив байтов, используя значение длины s := a[:n]или, s := string(a[:n])если вам нужна строка. Если nон не доступен напрямую, он должен быть рассчитан, например, путем поиска определенного / нулевого байта в буфере (массиве), как предлагает Даниэль.
zzzz

0

Я попробовал несколько методов, несколько раз я впал в панику:

ошибка во время выполнения: границы среза вне диапазона.

Но это наконец сработало.

string(Data[:])


3
Это не добавляет много информации и по сути повторяет ответ 2013 года: stackoverflow.com/a/18615786/349333 .
Йохем Шуленклоппер

0

Хотя и не очень производительный, единственное читаемое решение

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

Полный пример наличия байтового массива в стиле 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)
}

Полный пример, чтобы иметь строку стиля go, исключая нули:

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

Это выделяет кусочек кусочка байтов. Так что следите за производительностью, если она используется интенсивно или многократно.


-1

Вот код для сжатия байтового массива в строку

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

Вот более быстрый способ:

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

В следующий раз сначала прочитайте вопрос (и существующие ответы). (Плюс, если вы действительно хотите распечатать байтовый фрагмент через fmtэто быстрее, fmt.Printf("%s", bytes)чем использовать string(bytes)).
Дейв С

-7

Я когда с рекурсивным решением.

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

Почему вам нравится рекурсивное решение?
peterSO

Тестовый пример fmt.Println(CToGoString([]byte("ctogo\x00\x00"), "") == "ctogo")должен распечатать true, он печатает false.
peterSO

1
Вопрос спрашивает, что лучше . Это настолько плохо, насколько это возможно: трудно понять и чрезвычайно медленно, также оно не конвертирует a, [100]byteа a []byteи не снимает '\x00'байты. Его скорость (зависит от ввода) медленнее на несколько порядков по сравнению со скоростью принятого ответа.
icza
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.