如何将零终止的字节数组转换为字符串?

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 are padded with 0s.

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

In C the string will terminate upon 0, so I wonder what's the best way of smartly transfer a byte array to string in Golang.

methods that read data into byte slices return the number of bytes read. You should save that number and then use it to create your string. n being the number of bytes read, your code would look like this:

s := string(byteArray[:n])

If for some reason you don't have n, you could use the bytes package to find it, assuming your input doesn't have a null character in it.

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

Or as icza pointed out, you can use the code below:

n := bytes.IndexByte(byteArray, 0)
  • Use slices instead of arrays for reading. e.g. 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 exact (not padded) slice of read data

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

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.

I when with a recursive solution.

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

What about?

s := string(byteArray[:])

Only use for performance tuning.

package main

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

func BytesToString(b []byte) string {
    bh := (*reflect.SliceHeader)(unsafe.Pointer(&b))
    sh := reflect.StringHeader{bh.Data, bh.Len}
    return *(*string)(unsafe.Pointer(&sh))
}

func StringToBytes(s string) []byte {
    sh := (*reflect.StringHeader)(unsafe.Pointer(&s))
    bh := reflect.SliceHeader{sh.Data, sh.Len, 0}
    return *(*[]byte)(unsafe.Pointer(&bh))
}

func main() {
    b := []byte{'b', 'y', 't', 'e'}
    s := BytesToString(b)
    fmt.Println(s)
    b = StringToBytes(s)
    fmt.Println(string(b))
}

Why not this?

bytes.NewBuffer(byteArray).String()

Simplistic solution:

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

I'm not sure how performant this is though.

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

I tried few methods few times I got panic:

runtime error: slice bounds out of range.

But this finally worked.

string(Data[:])

Here is the faster way:

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