[go] How can I convert a zero-terminated byte array to string?

I need to read [100]byte to transfer a bunch of string data.

Because not all of the strings are precisely 100 characters long, the remaining part of the byte array is padded with 0s.

If I convert [100]byte to string by: string(byteArray[:]), the tailing 0s are displayed as ^@^@s.

In C, the string will terminate upon 0, so what's the best way to convert this byte array to string in Go?

This question is related to go

The answer is


When you do not know the exact length of non-nil bytes in the array, you can trim it first:

string(bytes.Trim(arr, "\x00"))


The following code is looking for '\0', and under the assumptions of the question the array can be considered sorted since all non-'\0' precede all '\0'. This assumption won't hold if the array can contain '\0' within the data.

Find the location of the first zero-byte using a binary search, then slice.

You can find the zero-byte like this:

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

It may be faster just to naively scan the byte array looking for the zero-byte, especially if most of your strings are short.


Use:

s := string(byteArray[:])

For example,

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

Output:

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

Though not extremely performant, the only readable solution is:

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

A full example to have a C-style byte array:

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

A full example to have a Go style string excluding the nulls:

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

This allocates a slice of slice of bytes. So keep an eye on performance if it is used heavily or repeatedly.


  • Use slices instead of arrays for reading. For example, io.Reader accepts a slice, not an array.

  • Use slicing instead of zero padding.

Example:

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

consume(buf[:n]) // consume() will see an exact (not padded) slice of read data

Only use for performance tuning.

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

Use this:

bytes.NewBuffer(byteArray).String()

Simplistic solution:

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

I'm not sure how performant this is though.