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

Posted

技术标签:

【中文标题】如何将零终止字节数组转换为字符串?【英文标题】:How can I convert a zero-terminated byte array to string? 【发布时间】:2012-12-23 04:49:07 【问题描述】:

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

由于并非所有strings 的长度都正好是100 个字符,所以byte array 的剩余部分用0s 填充。

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

在 C 中,string 将在 0 处终止,那么在 Go 中将 byte array 转换为 string 的最佳方法是什么?

【问题讨论】:

@AndréLaszlo:在操场上,^@ 没有显示,但如果你在终端或类似的地方测试它,它就会在那里。这样做的原因是,Go 在找到 0 时不会停止将字节数组转换为字符串。在您的示例中,len(string(bytes)) 是 5 而不是 1。这取决于输出函数,字符串是否完整(使用零)打印与否。 对于 http 响应正文,使用string(body) 【参考方案1】: 推荐的答案 Go Language

将数据读入字节片的方法返回读取的字节数。您应该保存该号码,然后使用它来创建您的字符串。如果n 是读取的字节数,您的代码将如下所示:

s := string(byteArray[:n])

要转换完整的字符串,可以使用:

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

这相当于:

s := string(byteArray[:])

如果由于某种原因您不知道n,您可以使用bytes 包来查找它,假设您的输入中没有嵌入空字符。

n := bytes.Index(byteArray[:], []byte0)

或者正如icza指出的,你可以使用下面的代码:

n := bytes.IndexByte(byteArray[:], 0)

【讨论】:

我知道我迟到了一年,但我应该提到 大多数 方法返回读取的字节数。比如 binary.Read() 可以读入一个 [32] 字节,但是你不知道你是否已经填满了所有 32 个字节。 您应该使用bytes.IndexByte() 搜索单个byte 而不是bytes.Index(),其中包含1 个字节的字节片。 实际上 string(byteArray) 也可以,并且会保存切片创建 只是要清楚一点,这是将字节序列转换为 希望 一个有效的 UTF-8 字符串(而不是说,Latin-1 等,或一些格式错误的 UTF-8 序列)。当你施放时,Go 不会为你检查这个。 @CameronKerr 来自blog.golang.org/strings:“重要的是要在前面说明字符串包含任意字节。它不需要包含 Unicode 文本、UTF-8 文本或任何其他预定义格式。就字符串的内容而言,它完全等价于一片字节。”【参考方案2】:

使用切片而不是数组进行读取。例如,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 an exact (not padded) slice of read data

【讨论】:

数据是别人写的,其他C语言写的,我只能看,所以无法控制写的方式。 哦,如果需要字符串,则使用长度值s := a[:n]s := string(a[:n]) 对字节数组进行切片。如果n 不是直接可用的,则必须计算它,例如正如 Daniel 建议的那样,通过在缓冲区(数组)中查找特定/零字节。【参考方案3】:

例如,

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

【讨论】:

【参考方案4】:

下面的代码正在寻找'\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 := []byte1, 2, 3, 0, 0, 0
    fmt.Println(FirstZero(b))

单纯地扫描字节数组寻找零字节可能会更快,尤其是当您的大多数字符串都很短时。

【讨论】:

您的代码无法编译,即使编译了,也无法工作。二分搜索算法在排序数组中查找指定值的位置。数组不一定是排序的。 @peterSO 你是对的,事实上它从来没有排序,因为它代表了一堆有意义的名字。 如果所有空字节都在字符串的末尾,则二进制搜索有效。 我不明白反对意见。代码编译并且是正确的,假设字符串除了末尾不包含 \0 。代码正在寻找 \0,并且在问题的假设下,数组可以被认为是“排序的”,因为所有非 \0 都在所有 \0 之前,这就是代码正在检查的所有内容。如果downvoters 可以找到代码不起作用的示例输入,那么我将删除答案。 如果输入为[]byte0,则给出错误结果。在这种情况下,FirstZero() 应该返回 0,因此当切片结果为 "" 时,它会返回 1,切片结果为 "\x00"【参考方案5】:

用途:

s := string(byteArray[:])

【讨论】:

该问题明确表示string(byteArray[:]) 包含^@ 字符 string(byteArray) 有什么区别?为什么需要使用[:] 复制数组? @RobertZaremba > 字符串实际上是只读的字节片。您不能将字节数组直接转换为字符串,因此首先切片然后字符串。 @RobertZaremba 对于字节切片,您不需要添加[:],对于字节数组,您可以这样做。 请阅读问题。它明确指出这不会在第一个 null 处终止字符串(就像 C 会说 strcpy 一样)。这个不正确的答案被如此赞成,这很烦人。 IMO 的最佳答案是上面提到的,使用 IndexByte()。【参考方案6】:

仅用于性能调整。

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:不确定这是否是一个严肃的答案,但您几乎肯定不想调用反射和不安全代码只是为了将字节切片转换为字符串 一句警告:使用 unsafe 将字节切片转换为 string 可能会在以后修改字节切片时产生严重影响。 Go 中的string 值被定义为不可变的,整个 Go 运行时和库都在此基础上构建。如果你走这条路,你将把自己传送到最神秘的错误和运行时错误的中间。 已编辑,因为这违反了指针使用(它与直接转换具有相同的行为,换句话说结果不会被垃圾收集)。阅读第(6)段golang.org/pkg/unsafe/#Pointer【参考方案7】:

使用这个:

bytes.NewBuffer(byteArray).String()

【讨论】:

因为a)问题是一个数组,所以你需要byteArray[:],因为bytes.NewBuffer需要[]byte; b)问题说数组有你不处理的尾随零; c) 如果您的变量是[]byte(您的行将编译的唯一方式),那么您的行只是执行string(v) 的缓慢方式。【参考方案8】:

简单的解决方案:

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

不过我不确定它的性能如何。

【讨论】:

不幸的是,这不会删除尾随零。 str 的长度为 100。请参阅 play.golang.org/p/XWrmqCbIwkB 不起作用,不知道为什么很多人赞成这个答案【参考方案9】:

当不知道数组中非nil字节的确切长度时,可以先修剪一下:

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

【讨论】:

a) bytes.Trim 需要一个切片,而不是一个数组(如果 arr 实际上是 [100]byte 如问题所述,则需要 arr[:])。 b) bytes.Trim 是这里使用的错误函数。对于像[]byte0,0,'a','b','c',0,'d',0 这样的输入,它将返回“abc\x00d”而不是“” c) 已经有一个使用bytes.IndexByte 的正确答案,这是找到第一个零字节的最佳方法。【参考方案10】:

虽然性能不是特别好,但唯一可读的解决方案是:

  // Split by separator and pick the first one.
  // This has all the characters till null, excluding null itself.
  retByteArray := bytes.Split(byteArray[:], []byte0) [0]

  // OR

  // If you want a true C-like string, including the null character
  retByteArray := bytes.SplitAfter(byteArray[:], []byte0) [0]

具有 C 样式字节数组的完整示例:

package main

import (
    "bytes"
    "fmt"
)

func main() 
    var byteArray = [6]byte97,98,0,100,0,99

    cStyleString := bytes.SplitAfter(byteArray[:], []byte0) [0]
    fmt.Println(cStyleString)

Go 风格字符串不包括空值的完整示例:

package main

import (
    "bytes"
    "fmt"
)

func main() 
    var byteArray = [6]byte97, 98, 0, 100, 0, 99

    goStyleString := string(bytes.Split(byteArray[:], []byte0) [0])
    fmt.Println(goStyleString)

这会分配一片字节片。因此,如果大量使用或重复使用,请注意性能。

【讨论】:

【参考方案11】:

这是一个删除空字节的选项:

package main
import "golang.org/x/sys/windows"

func main() 
   b := []byte'M', 'a', 'r', 'c', 'h', 0
   s := windows.ByteSliceToString(b)
   println(s == "March")

https://pkg.go.dev/golang.org/x/sys/unix#ByteSliceToString https://pkg.go.dev/golang.org/x/sys/windows#ByteSliceToString

【讨论】:

这似乎只适用于 Windows。在 Linux 中,我得到 build constraints exclude all Go files in...

以上是关于如何将零终止字节数组转换为字符串?的主要内容,如果未能解决你的问题,请参考以下文章

如何将字节数组的字符串转换为字节数组

如何将字节数组转换为字符串[重复]

如何将字符串转换为字节数组? [关闭]

错误 1075:从 UDF 接收到一个字节数组。无法确定如何将字节数组转换为字符串

如何将字节数组转换为字符串? [复制]

如何在c ++中将数组字节转换为字符串?