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


502

我需要阅读[100]byte以传输一堆string数据。

由于并非所有strings的长度都恰好是100个字符,因此s的其余部分将byte array0s 填充。

如果我将by 转换[100]bytestringstring(byteArray[:]),则尾部0s将显示为^@^@s。

在C中,stringwill终止于0,所以我想知道将其转换byte arraystringGolang 的最佳方法是什么。


3
@AndréLaszlo:在操场^@上没有显示,但是如果您在终端或类似的地方进行测试,它会一直在那儿。这样做的原因是,Go在找到0时不会停止将bytes数组转换为字符串。len(string(bytes))在您的示例中为5而不是1。这取决于输出函数,即是否完整打印了字符串(带有零)或不。
nemo

8
对于http响应正文,请使用string(body)
Ivan Chau

Answers:


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()which搜索单个byte而不是bytes.Index()包含1个字节的字节片。
icza 2015年

56
实际上,string(byteArray)也会这样做,并将保存切片创建
throws_exceptions_at_you

3
只是要清楚不过,这是铸造字节的东西的顺序是希望有效的UTF-8字符串(而不是说,拉美-1等,或一些畸形的UTF-8序列)。投放时,Go不会为您检查此项。
卡梅隆·克尔

如果您的字节数组以相反的顺序(又称为小尾数法)怎么办?
爵士

374

关于什么?

s := string(byteArray[:])

3
确保转换字节数组的最干净方法。我不知道strings.Trim是否有助于去除空字节?golang.org/pkg/strings/#example_Trim
andyvanee

24
这个问题专门说其中string(byteArray[:])包含^@字符
罗伯特·罗伯特(Robert Robert)

24
有什么区别string(byteArray)?为什么需要使用复制数组[:]
罗伯特·扎伦巴

7
@RobertZaremba>字符串实际上是只读的字节片段。您不能将字节数组直接转换为字符串,因此先切片然后再字符串。
ferhat elmas 2015年

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 2013年

@peterSO您是对的,并且实际上它从未被排序,因为它代表了一堆有意义的名称。
Derrick Zhang

3
如果所有空字节都在字符串的末尾,则进行二进制搜索。
保罗·汉金

6
我不明白反对票。假定字符串除末尾外不包含\ 0,代码可以编译并且正确。代码正在寻找\ 0,并且在问题的假设下,可以将数组视为“已排序”,因为所有非\ 0都在所有\ 0之前,这就是所有要检查的代码。如果下降投票者可以找到代码无法正常使用的示例输入,那么我将删除答案。
保罗·汉金

1
如果输入为,则给出错误的结果[]byte{0}。在这种情况下,FirstZero()应返回,0这样切片结果将是"",但相反,它将返回1并在中切片结果"\x00"
icza 2015年

3

当您不知道数组中非零字节的确切长度时,可以先对其进行修剪:

字符串(bytes.Trim(arr,“ \ x00”))


1
a)bytes.Trim取一个切片,而不是数组(arr[:]如果[100]byte问题指出arr实际上是a ,则需要)。b)bytes.Trim在这里使用错误的功能。对于类似[]byte{0,0,'a','b','c',0,'d',0}这样的输入,它将返回“ abc \ x00d”而不是“” c),已经有使用的正确答案bytes.IndexByte,这是查找第一个零字节的最佳方法。
Dave C

1

为什么不呢?

bytes.NewBuffer(byteArray).String()

1
因为)这个问题说,一个数组,以便在你需要byteArray[:],因为bytes.NewBuffer需要[]byte; b)这个问题说数组有尾随的零,您不会处理;c)如果您的变量是[]byte(行的唯一编译方式),那么您的行只是一种缓慢的方式string(v)
Dave C

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:不确定这是否是一个严肃的答案,但是您几乎绝对不希望仅将字节片转换为字符串就调用反射和不安全的代码
Austin Hyde

1
一个警告:使用不安全的方法将字节片转换为a string可能会对以后的字节片产生严重影响。stringGo中的值被定义为不可变的,整个Go运行时和库都基于此值。如果您走这条路,您将自己传送到最神秘的错误和运行时错误的中间。
icza

编辑,因为这是针对指针用法的(它与直接强制转换具有相同的行为,换句话说,结果将不会被垃圾回收)。阅读(6)款golang.org/pkg/unsafe/#Pointer
Laevus德克斯特

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

数据是由其他人和其他C语言编写的,我只能读取它,所以我无法控制其编写方式。
Derrick Zhang

1
哦,然后使用长度值s := a[:n]s := string(a[:n])如果需要字符串来对字节数组进行切片。如果n不能直接获得,则必须对其进行计算,例如,按照丹尼尔的建议,在缓冲区(数组)中查找特定/零字节。
zzzz


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

具有不包含null的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 2013年

1
问题问什么是最好的方法。这是很糟糕的:很难理解,而且非常慢,而且它不会转换a [100]byte而是a []byte,也不会剥离'\x00'字节。与接受答案的速度相比,其速度(取决于输入)要慢多个数量级。
icza 2015年
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.