ZetCode

Go 字节

最后修改于 2025 年 5 月 3 日

在本文中,我们将展示如何在 Golang 中处理字节。

在 Go 中,byte 是一个无符号的 8 位整数,它是 uint8 的类型别名。由于其数值范围为 0-255,因此常用于表示单个 ASCII 字符。在处理原始二进制数据或在字符级别处理文本数据时,byte 值经常被使用。

为了处理多字节字符,Go 提供了 rune 类型,它是 int32 的别名。与表示单字节值的 byte 不同,rune 可以存储 Unicode 码点,使其适用于处理国际文本和复杂的字符集。

为了方便字节级别的操作,Go 提供了 bytes 包,它实现了一系列函数,用于高效地处理字节切片。bytes 包与 strings 包有相似之处,但它操作的是字节切片而不是字符串值。这种区别使其在修改、比较和搜索原始二进制数据等任务中特别有用,同时最大限度地减少了不必要的内存分配。

Go 字节示例

在下一个示例中,我们将处理简单的字节。

first.go
package main

import "fmt"

func main() {

    var a1 byte = 97
    var a2 byte = 98
    var a3 byte = 99

    fmt.Println(a1)
    fmt.Println(a2)
    fmt.Println(a3)

    fmt.Printf("%c\n", a1)
    fmt.Printf("%c\n", a2)
    fmt.Printf("%c\n", a3)
}

我们有三个字节。

var a1 byte = 97
var a2 byte = 98
var a3 byte = 99

字节使用 byte 数据类型定义。

fmt.Printf("%c\n", a1)
fmt.Printf("%c\n", a2)
fmt.Printf("%c\n", a3)

使用 %c 格式动词,我们打印字节的字符表示。

$ go run first.go 
97
98
99
a
b
c

我们必须显式地将变量设置为 byte 类型;否则,我们会得到不同的类型。

types.go
package main

import (
     "fmt"
     "reflect"
)

func main() {

     var a byte = 97
     var b = 98
     c := 'c'

     fmt.Println(a)
     fmt.Println(b)
     fmt.Println(c)

     fmt.Println("-------------------------")

     fmt.Printf("%c\n", a)
     fmt.Printf("%c\n", b)
     fmt.Printf("%c\n", c)

     fmt.Println("-------------------------")

     fmt.Println(reflect.TypeOf(a))
     fmt.Println(reflect.TypeOf(b))
     fmt.Println(reflect.TypeOf(c))
}

在代码示例中,我们有三个变量。

var a byte = 97

a 变量具有 byte 数据类型。

var b = 98

由于我们没有显式设置数据类型,Go 会设置默认的 int 类型。

c := 'c'

字符字面量被设置为 rune 类型(int32)。

$ go run types.go 
97
98
99
-------------------------
a
b
c
-------------------------
uint8
int
int32

Go 字符串转字节

在下面的示例中,我们将字符串转换为字节。

str2bytes.go
package main

import (
     "fmt"
)

func main() {

     fmt.Println([]byte("falcon"))
     fmt.Println([]byte("čerešňa"))
}

我们使用 []byte() 类型转换将两个字符串转换为字节。

$ go run str2bytes.go 
[102 97 108 99 111 110]
[196 141 101 114 101 197 161 197 136 97]

Go 字节转字符串

在下面的示例中,我们将字节转换为字符串。

bytes2str.go
package main

import "fmt"

func main() {

     data := []byte{102, 97, 108, 99, 111, 110}

     fmt.Println(data)
     fmt.Println(string(data))
}

我们使用 string 函数将字节切片转换为字符串。

$ go run bytes2str.go 
[102 97 108 99 111 110]
falcon

Go 计算字节数

我们使用 len 函数计算字节数。要计算 rune 数,我们使用 utf8.RuneCountInString 函数。

counting.go
package main

import (
     "fmt"
     "unicode/utf8"
)

func main() {

     msg := "one 🐜"
     n1 := len(msg)
     n2 := utf8.RuneCountInString(msg)

     fmt.Println(n1)
     fmt.Println(n2)
}

我们计算 msg 字符串中的字节数和 rune 数。

$ go run counting.go 
8
5

有五个 rune 和八个字符。这意味着我们有一个 rune 包含四个字节。

Go 字节读取文件

Go 中的许多内置 I/O 函数都返回一个字节切片。

words.txt
falcon
sky
cup
oak
water

我们读取这个文本文件。

read_file.go
package main

import (
     "fmt"
     "io/ioutil"
     "log"
)

func main() {

     content, err := ioutil.ReadFile("words.txt")

     if err != nil {
          log.Fatal(err)
     }

     fmt.Println(content)
     fmt.Println("-------------")
     fmt.Println(string(content))
}

ioutil.ReadFile 读取指定文件并将其内容作为字节切片返回。

$ go run read_file.go 
[102 97 108 99 111 110 10 115 107 121 10 99 117 112 10 111 97 107 10 119 97 116 101 114]
-------------
falcon
sky
cup
oak
water

Go 字节读取二进制文件

在下面的示例中,我们读取一个二进制文件并以十六进制视图输出。

read_binary.go
package main

import (
     "bufio"
     "encoding/hex"
     "fmt"
     "io"
     "log"
     "os"
)

func main() {

     f, err := os.Open("favicon.ico")

     if err != nil {
          log.Fatal(err)
     }

     defer f.Close()

     reader := bufio.NewReader(f)
     buf := make([]byte, 256)

     for {
          _, err := reader.Read(buf)

          if err != nil {
               if err != io.EOF {
                    fmt.Println(err)
               }
               break
          }

          fmt.Printf("%s", hex.Dump(buf))
     }
}

hex.Dump 函数返回一个包含给定数据十六进制转储的字符串。十六进制转储的格式与 hexdump -C Unix 命令的输出相匹配。

$ go run read_binary.go 
00000000  00 00 01 00 01 00 10 10  00 00 00 00 00 00 68 05  |..............h.|
00000010  00 00 16 00 00 00 28 00  00 00 10 00 00 00 20 00  |......(....... .|
00000020  00 00 01 00 08 00 00 00  00 00 00 01 00 00 00 00  |................|
00000030  00 00 00 00 00 00 00 01  00 00 00 00 00 00 00 00  |................|
00000040  00 00 ff ff ff 00 4d 45  3d 00 00 00 00 00 00 00  |......ME=.......|
00000050  00 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00  |................|
00000060  00 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00  |................|
...

Go 转义字节

任意字符值都可以用反斜杠转义来编码,并在字符串或 rune 字面量中使用。Go 支持一种常见的格式,其中字节表示为 \x 后面跟着两个十六进制值。

escaped_bytes.go
package main

import (
     "fmt"
)

func main() {

     fmt.Println("\xF0\x9F\x92\xBF")
     fmt.Println("\xF0\x9F\x8E\xB2")
     fmt.Println("\xF0\x9F\x90\xA8")
     fmt.Println("\xF0\x9F\x90\xA7")
     fmt.Println("\xF0\x9F\x90\xAB")
     fmt.Println("\xF0\x9F\x90\xAC")
}

在代码示例中,我们打印了六个 emoji 字符。这些 emoji 是通过转义字节指定的。

$ go run escaped_bytes.go 
💿
🎲
🐨
🐧
🐫
🐬

Go 字节函数

bytes 包包含用于操作字节切片的函数。

byte_funs.go
package main

import (
     "bytes"
     "fmt"
)

func main() {

     data1 := []byte{102, 97, 108, 99, 111, 110} // falcon
     data2 := []byte{111, 110}                   // on

     if bytes.Contains(data1, data2) {
          fmt.Println("contains")
     } else {
          fmt.Println("does not contain")
     }

     if bytes.Equal([]byte("falcon"), []byte("owl")) {
          fmt.Println("equal")
     } else {
          fmt.Println("not equal")
     }

     data3 := []byte{111, 119, 108, 9, 99, 97, 116, 32, 32, 32, 32, 100, 111,
          103, 32, 112, 105, 103, 32, 32, 32, 32, 98, 101, 97, 114}

     fields := bytes.Fields(data3)
     fmt.Println(fields)

     for _, e := range fields {
          fmt.Printf("%s ", string(e))
     }

     fmt.Println()
}

在代码示例中,我们使用了 ContainsEqualFields 函数。

if bytes.Contains(data1, data2) {
     fmt.Println("contains")
} else {
     fmt.Println("does not contain")
}

使用 Contains,我们检查 data2 切片是否是 data1 的子切片。

if bytes.Equal([]byte("falcon"), []byte("owl")) {
     fmt.Println("equal")
} else {
     fmt.Println("not equal")
}

使用 Equal,我们检查两个切片是否具有相同的长度并包含相同的字节。

fields := bytes.Fields(data3)

Fields 函数将字节切片分割成子切片,去除任何空格字符,包括换行符。

$ go run byte_funs.go 
contains
not equal
[[111 119 108] [99 97 116] [100 111 103] [112 105 103] [98 101 97 114]]
owl cat dog pig bear 

在下一个示例中,我们使用了另外三个函数。

byte_funs2.go
package main

import (
     "bytes"
     "fmt"
)

func main() {

     data := [][]byte{[]byte("an"), []byte("old"), []byte("wolf")}
     joined := bytes.Join(data, []byte(" "))

     fmt.Println(data)
     fmt.Println(joined)
     fmt.Println(string(joined))

     fmt.Println("--------------------------")

     data2 := []byte{102, 97, 108, 99, 111, 110, 32}
     data3 := bytes.Repeat(data2, 3)

     fmt.Println(data3)
     fmt.Println(string(data3))

     fmt.Println("--------------------------")

     data4 := []byte{32, 32, 102, 97, 108, 99, 111, 110, 32, 32, 32}
     data5 := bytes.Trim(data4, " ")

     fmt.Println(data5)
     fmt.Println(string(data5))
}

该示例使用 Join 连接字节切片,使用 Repeat 重复字节切片,并使用 Trim 修剪字节切片中的指定字节。

$ go run byte_funs2.go 
[[97 110] [111 108 100] [119 111 108 102]]
[97 110 32 111 108 100 32 119 111 108 102]
an old wolf
--------------------------
[102 97 108 99 111 110 32 102 97 108 99 111 110 32 102 97 108 99 111 110 32]
falcon falcon falcon 
--------------------------
[102 97 108 99 111 110]
falcon

Go bytes.Buffer

bytes.Buffer 是一个可变大小的字节缓冲区,具有 Read 和 Write 方法。

buffer.go
package main

import (
     "bytes"
     "fmt"
)

func main() {

     var buf bytes.Buffer

     buf.Write([]byte("a old"))
     buf.WriteByte(32)
     buf.WriteString("cactus")
     buf.WriteByte(32)
     buf.WriteByte(32)
     buf.WriteRune('🌵')

     fmt.Println(buf)
     fmt.Println(buf.String())
}

我们使用 WriteWriteByteWriteStringWriteByteWriteRune 方法构建了一个 bytes.Buffer

$ go run buffer.go 
{[97 32 111 108 100 32 99 97 99 116 117 115 32 32 240 159 140 181] 0 0}
a old cactus  🌵

来源

Go bytes 包 - 参考

在本文中,我们已经在 Golang 中处理了字节。

作者

我叫 Jan Bodnar,是一名热情的程序员,拥有丰富的编程经验。我从 2007 年开始撰写编程文章。至今,我已撰写了 1400 多篇文章和 8 本电子书。我在编程教学方面拥有十多年的经验。

列出所有 Go 教程