ChatGPT解决这个技术问题 Extra ChatGPT

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

go

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

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

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

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

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

D
Deleplace

将数据读入字节片的方法返回读取的字节数。您应该保存该号码,然后使用它来创建您的字符串。如果 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)

我知道我迟到了一年,但我应该提到大多数方法返回读取的字节数。例如,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 文本或任何其他预定义格式。就字符串的内容而言而言,它完全相当于一个字节片。”
P
Peter Mortensen

利用:

s := string(byteArray[:])

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

简单的解决方案:

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

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


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

例如,

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

T
T0xicCode

以下代码正在寻找'\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))
}

天真地扫描字节数组以查找零字节可能会更快,尤其是在您的大多数字符串都很短的情况下。


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

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

字符串(字节。修剪(arr,“\x00”))


a) bytes.Trim 需要一个切片,而不是一个数组(如果 arr 实际上是一个 [100]byte,则需要 arr[:])。 b) bytes.Trim 是在这里使用的错误函数。对于像 []byte{0,0,'a','b','c',0,'d',0} 这样的输入,它将返回 "abc\x00d" 而不是 "" c) 已经有一个使用 bytes.IndexByte 的正确答案,这是找到第一个零字节的最佳方法。
L
Laevus Dexter

仅用于性能调整。

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 可能会产生严重影响。 string Go 中的值被定义为不可变的,整个 Go 运行时和库都以此为基础。如果你走这条路,你会将自己传送到最神秘的错误和运行时错误的中间。
已编辑,因为这违反了指针使用(它具有与直接转换相同的行为,换句话说,结果将不会被垃圾收集)。阅读第 (6) 段golang.org/pkg/unsafe/#Pointer
P
Peter Mortensen

用这个:

bytes.NewBuffer(byteArray).String()

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

虽然不是非常高效,但唯一可读的解决方案是:

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

具有不包括空值的 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)
}

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


P
Peter Mortensen

使用切片而不是数组进行读取。例如, 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 建议的那样,通过在缓冲区(数组)中查找特定/零字节。
Z
Zombo

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

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...

关注公众号,不定期副业成功案例分享
关注公众号

不定期副业成功案例分享

领先一步获取最新的外包任务吗?

立即订阅