Go 语言内置了丰富的文件操作函数,可以很方便地处理文本文件。但对于音视频、图像等二进制文件,文本文件函数就不太适用了。
学习 Go 语言的二进制文件读写操作,可以更高效地处理这些非文本文件,在实际项目中也很常用。
Go 语言处理二进制文件具有以下优势
文件操作的一些基础知识。
使用 os.Create() 可以创建一个新文件并打开,使用 os.Open() 可以打开一个已存在的文件
file, err := os.Create("data.bin") // 创建文件
file, err := os.Open("data.bin") // 打开文件打开的文件使用后需要关闭
file.Close()文件操作可能会遇到一些错误,需做错误处理
if err != nil {
// 错误处理
}下面将详细介绍 Go 语言如何读取二进制文件的不同数据类型。
可使用 binary 包按照不同字节顺序读写整数。
读取一个 int32 类型的整数
var data int32
err := binary.Read(file, binary.LittleEndian, &data)使用 encoding/binary 包的 ReadUvarint 和 ReadVarint 函数可以读取可变长度编码的整数。
udata, err := binary.ReadUvarint(file)
data, err := binary.ReadVarint(file)字符串可以用 ReadString 直接读取指定长度的字符串:
str, err := binary.ReadString(file, length)要读取不定长字符串,可以先像上面那样读取一个整形长度,然后再读取指定长度的数据到字符串中。
可以直接读取到一个结构体变量中
var user StructUserInfo
err := binary.Read(file, binary.BigEndian, &user)data := int32(100)
err := binary.Write(file, binary.LittleEndian, data)使用 PutUvarint 和 PutVarint 写入可变长度编码的整数:
err := binary.PutUvarint(file, uint64(x))
err := binary.PutVarint(file, x)使用 WriteString 写入字符串:
data := "Hello World"
err := binary.WriteString(file, data)user := StructUserInfo{...}
err := binary.Write(file, binary.LittleEndian, user)可以通过获取和设置文件指针的位置来随机访问文件内容。
用 Seek 方法获取当前文件的偏移量
n, err := file.Seek(0, io.SeekCurrent) // 获取偏移量5.2 指针位置的设置
用 Seek 将指针移动到文件开头或结尾等位置
_, err := file.Seek(0, io.SeekStart) // 移动到开头
_, err := file.Seek(0, io.SeekEnd) // 移动到结尾在处理大量数据时,可通过缓冲区批量读取数据,提高效率。下面是一个批量读取的例子。
package main
import (
"fmt"
"os"
)
func main() {
file, err := os.Open("example.bin")
if err != nil {
fmt.Println("Error opening file:", err)
return
}
defer file.Close()
// 设置缓冲区大小为1024字节
buffer := make([]byte, 1024)
// 循环读取数据直到文件末尾
for {
n, err := file.Read(buffer)
if err != nil {
fmt.Println("Error reading data:", err)
break
}
if n == 0 {
break
}
// 处理读取到的数据
fmt.Printf("Read %d bytes: %s\n", n, buffer[:n])
}
}同样地,也可通过缓冲区批量写入数据。下面是批量写入的例子。
package main
import (
"fmt"
"os"
)
func main() {
file, err := os.Create("example.bin")
if err != nil {
fmt.Println("Error creating file:", err)
return
}
defer file.Close()
// 设置缓冲区大小为1024字节
buffer := make([]byte, 1024)
// 循环写入数据
for i := 0; i < 10; i++ {
// 将数据写入缓冲区
data := []byte(fmt.Sprintf("Data %d\n", i))
copy(buffer, data)
// 写入缓冲区数据到文件
_, err := file.Write(buffer)
if err != nil {
fmt.Println("Error writing data:", err)
return
}
}
fmt.Println("Batch writing completed.")
}下面以一个日志文件为例,演示二进制文件读写的实际运用。
假设日志文件的结构如下
type LogHeader struct {
Magic uint16 // 魔数
Version uint16 // 版本号
Length uint32 // 日志长度
}
type LogItem struct {
Time int64 // 时间
Message string // 日志消息
}解析该日志文件代码如下
func ReadLog(path string) ([]LogItem, error) {
file, err := os.Open(path)
if err != nil {
return nil, err
}
defer file.Close()
var header LogHeader
if err := binary.Read(file, binary.BigEndian, &header); err != nil {
return nil, err
}
var logs []LogItem
for i := 0; i < int(header.Length); i++ {
var log LogItem
if err := binary.Read(file, binary.BigEndian, &log); err != nil {
return nil, err
}
logs = append(logs, log)
}
return logs, nil
}func WriteLog(path string, logs []LogItem) error {
file, err := os.Create(path)
if err != nil {
return err
}
defer file.Close()
header := LogHeader{
Magic: 0xDEADBEEF,
Version: 1,
Length: uint32(len(logs)),
}
if err := binary.Write(file, binary.BigEndian, header); err != nil {
return err
}
for _, log := range logs {
if err := binary.Write(file, binary.BigEndian, log); err != nil {
return err
}
}
return nil
}通过缓冲区读写可以减少 IO 操作次数,优化性能。使用 bufio 包实现缓冲读写。
可通过 goroutine 实现文件读写的并发操作,提高性能。需要正确同步访问文件指针位置。
写入文件时,可以增加 CRC32、MD5 等数据校验,读取时验证数据完整性。
注意添加错误处理逻辑,防止程序异常退出。
通过上面介绍,了解了 Go 语言二进制文件的各种读写操作,包括整数、字符串、结构体的编码与解码,指针操作,批量读写与性能优化等技巧,并用日志文件解析和生成的例子做了实战演练。
Go 语言处理二进制文件的功能非常强大,可以开发出高性能和安全的文件处理程序。