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


502

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

Тому що не всі stringз довжиною рівно 100 символів, то решта byte arrayзаповнюється 0с.

Якщо я перетворюю [100]byteна string:, string(byteArray[:])хвости 0s відображаються як ^@^@s.

У C stringзакінчиться по 0, так цікаво , що це кращий спосіб , щоб перетворити це byte arrayв stringв Golang.


3
@ AndréLaszlo: На ігровому майданчику ^@це не відображається, але воно було б там, якби ви тестували його в терміналі чи щось подібне. Причиною цього є те, що Go не зупиняє перетворення масиву байтів у рядок, коли знаходить 0. len(string(bytes))У вашому прикладі є 5, а не 1. Це залежить від функції виводу, чи надруковано повністю (з нулями) рядок чи ні.
nemo

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) теж зробить і збереже створення фрагмента
bacs_exceptions_at_you

3
Просто щоб бути ясно , хоча, це кидає сукупність електронних даних до чого - то , сподіваюся , є допустимою UTF-8 рядок (і не сказати, Latin-1 і т.д., або який - то спотворена послідовність UTF-8). Go не перевірить це для вас, коли ви граєте.
Камерон Керр

Що робити, якщо ваш байтовий масив знаходиться в зворотному порядку, як мало ендіан?
Сер

374

А як на рахунок?

s := string(byteArray[:])

3
Найпростіший спосіб перетворення байтового масиву точно. Цікаво, чи strings.Trim допоможе викреслити нульові байти? golang.org/pkg/strings/#example_Trim
andyvanee

24
У запитанні конкретно сказано, що string(byteArray[:])містить ^@персонажів
Роберт

24
У чому різниця string(byteArray)? Чому потрібно скопіювати масив за допомогою [:]?
Роберт Заремба

7
@RobertZaremba> рядок насправді є фрагментом байтів лише для читання. Ви не можете перетворити байтовий масив безпосередньо в рядок, тому перший фрагмент, а потім рядок.
ferhat elmas

3
@RobertZaremba Для байтових фрагментів вам не потрібно додавати [:]масиви байтів.
Дрю Лесюер

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, і все це код перевіряє. Якщо поточні користувачі могли знайти приклад введення, на якому не працює код, я видалю відповідь.
Пол Ханкін

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, найкращий спосіб знайти перший нульовий байт.
Дейв C

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
Слово попередження: використання небезпечного для перетворення байтового фрагмента у формат 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]

Повний приклад мати байтовий масив у стилі С:

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)).
Дейв C

-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
Питання задає, який найкращий спосіб. Це так само погано, як це може отримати: важко зрозуміти і надзвичайно повільно, також він не конвертує а, [100]byteа а []byte, і не знімає '\x00'байти. Його швидкість (залежить від введення) повільніше на кілька порядків порівняно зі швидкістю прийнятої відповіді.
icza
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.