4.1.1 文件读写基础 #
文件读写是系统编程中最基础也是最重要的操作之一。Go 语言提供了丰富而灵活的文件操作接口,让开发者能够高效地处理各种文件操作需求。本节将详细介绍 Go 语言中文件读写的基础知识和最佳实践。
文件操作基础概念 #
文件句柄与文件描述符 #
在 Go 语言中,文件操作主要通过 os.File
类型来实现。os.File
是对操作系统文件描述符的封装,提供了统一的文件操作接口。
type File struct {
// 包含过滤或未导出的字段
}
每个打开的文件都有一个唯一的文件描述符,操作系统通过这个描述符来跟踪文件的状态和位置。
文件打开模式 #
Go 语言支持多种文件打开模式,通过不同的标志位组合来控制文件的访问方式:
const (
O_RDONLY int = syscall.O_RDONLY // 只读模式
O_WRONLY int = syscall.O_WRONLY // 只写模式
O_RDWR int = syscall.O_RDWR // 读写模式
O_APPEND int = syscall.O_APPEND // 追加模式
O_CREATE int = syscall.O_CREAT // 创建文件
O_EXCL int = syscall.O_EXCL // 与 O_CREATE 一起使用,文件必须不存在
O_SYNC int = syscall.O_SYNC // 同步写入
O_TRUNC int = syscall.O_TRUNC // 截断文件
)
文件创建与打开 #
创建新文件 #
使用 os.Create
函数可以创建新文件,如果文件已存在则会被截断:
package main
import (
"fmt"
"os"
)
func main() {
// 创建新文件
file, err := os.Create("example.txt")
if err != nil {
fmt.Printf("创建文件失败: %v\n", err)
return
}
defer file.Close()
fmt.Println("文件创建成功")
// 获取文件信息
info, err := file.Stat()
if err != nil {
fmt.Printf("获取文件信息失败: %v\n", err)
return
}
fmt.Printf("文件名: %s\n", info.Name())
fmt.Printf("文件大小: %d 字节\n", info.Size())
fmt.Printf("修改时间: %v\n", info.ModTime())
}
打开已存在的文件 #
使用 os.Open
函数以只读模式打开文件:
func openFileExample() {
// 以只读模式打开文件
file, err := os.Open("example.txt")
if err != nil {
fmt.Printf("打开文件失败: %v\n", err)
return
}
defer file.Close()
fmt.Println("文件打开成功")
}
使用 OpenFile 进行高级控制 #
os.OpenFile
提供了最灵活的文件打开方式:
func openFileAdvanced() {
// 以读写模式打开文件,如果不存在则创建
file, err := os.OpenFile("data.txt", os.O_RDWR|os.O_CREATE, 0644)
if err != nil {
fmt.Printf("打开文件失败: %v\n", err)
return
}
defer file.Close()
fmt.Println("文件打开成功,模式:读写+创建")
}
文件读取操作 #
基础读取方法 #
Go 语言提供了多种文件读取方法,适用于不同的场景:
package main
import (
"fmt"
"io"
"os"
)
func readFileBasic() {
file, err := os.Open("example.txt")
if err != nil {
fmt.Printf("打开文件失败: %v\n", err)
return
}
defer file.Close()
// 方法1: 使用 Read 方法读取固定大小的数据
buffer := make([]byte, 1024)
n, err := file.Read(buffer)
if err != nil && err != io.EOF {
fmt.Printf("读取文件失败: %v\n", err)
return
}
fmt.Printf("读取了 %d 字节: %s\n", n, string(buffer[:n]))
}
读取整个文件 #
对于小文件,可以一次性读取整个文件内容:
func readEntireFile() {
// 方法1: 使用 os.ReadFile (Go 1.16+)
content, err := os.ReadFile("example.txt")
if err != nil {
fmt.Printf("读取文件失败: %v\n", err)
return
}
fmt.Printf("文件内容: %s\n", string(content))
// 方法2: 使用 io.ReadAll
file, err := os.Open("example.txt")
if err != nil {
fmt.Printf("打开文件失败: %v\n", err)
return
}
defer file.Close()
content2, err := io.ReadAll(file)
if err != nil {
fmt.Printf("读取文件失败: %v\n", err)
return
}
fmt.Printf("文件内容: %s\n", string(content2))
}
逐行读取文件 #
对于文本文件,经常需要逐行处理:
import (
"bufio"
"fmt"
"os"
)
func readFileLineByLine() {
file, err := os.Open("example.txt")
if err != nil {
fmt.Printf("打开文件失败: %v\n", err)
return
}
defer file.Close()
scanner := bufio.NewScanner(file)
lineNumber := 1
for scanner.Scan() {
line := scanner.Text()
fmt.Printf("第 %d 行: %s\n", lineNumber, line)
lineNumber++
}
if err := scanner.Err(); err != nil {
fmt.Printf("读取文件时出错: %v\n", err)
}
}
使用缓冲读取器 #
对于需要高性能读取的场景,可以使用缓冲读取器:
func readWithBuffer() {
file, err := os.Open("large_file.txt")
if err != nil {
fmt.Printf("打开文件失败: %v\n", err)
return
}
defer file.Close()
// 创建缓冲读取器
reader := bufio.NewReader(file)
for {
line, err := reader.ReadString('\n')
if err != nil {
if err == io.EOF {
if len(line) > 0 {
fmt.Print(line) // 处理最后一行(可能没有换行符)
}
break
}
fmt.Printf("读取错误: %v\n", err)
break
}
fmt.Print(line)
}
}
文件写入操作 #
基础写入方法 #
func writeFileBasic() {
file, err := os.Create("output.txt")
if err != nil {
fmt.Printf("创建文件失败: %v\n", err)
return
}
defer file.Close()
// 方法1: 使用 Write 方法
data := []byte("Hello, World!\n")
n, err := file.Write(data)
if err != nil {
fmt.Printf("写入文件失败: %v\n", err)
return
}
fmt.Printf("写入了 %d 字节\n", n)
// 方法2: 使用 WriteString 方法
n2, err := file.WriteString("这是第二行内容\n")
if err != nil {
fmt.Printf("写入字符串失败: %v\n", err)
return
}
fmt.Printf("写入了 %d 字节\n", n2)
}
一次性写入整个文件 #
func writeEntireFile() {
content := "这是要写入文件的完整内容\n包含多行文本\n"
// 使用 os.WriteFile (Go 1.16+)
err := os.WriteFile("complete.txt", []byte(content), 0644)
if err != nil {
fmt.Printf("写入文件失败: %v\n", err)
return
}
fmt.Println("文件写入成功")
}
追加写入 #
func appendToFile() {
// 以追加模式打开文件
file, err := os.OpenFile("log.txt", os.O_WRONLY|os.O_APPEND|os.O_CREATE, 0644)
if err != nil {
fmt.Printf("打开文件失败: %v\n", err)
return
}
defer file.Close()
// 追加内容
_, err = file.WriteString("新的日志条目\n")
if err != nil {
fmt.Printf("追加内容失败: %v\n", err)
return
}
fmt.Println("内容追加成功")
}
使用缓冲写入器 #
对于频繁的写入操作,使用缓冲写入器可以提高性能:
func writeWithBuffer() {
file, err := os.Create("buffered_output.txt")
if err != nil {
fmt.Printf("创建文件失败: %v\n", err)
return
}
defer file.Close()
// 创建缓冲写入器
writer := bufio.NewWriter(file)
defer writer.Flush() // 确保缓冲区内容被写入
// 写入多行数据
for i := 1; i <= 1000; i++ {
_, err := writer.WriteString(fmt.Sprintf("第 %d 行数据\n", i))
if err != nil {
fmt.Printf("写入失败: %v\n", err)
return
}
}
fmt.Println("缓冲写入完成")
}
文件位置操作 #
文件指针定位 #
func fileSeekExample() {
file, err := os.OpenFile("data.txt", os.O_RDWR|os.O_CREATE, 0644)
if err != nil {
fmt.Printf("打开文件失败: %v\n", err)
return
}
defer file.Close()
// 写入一些数据
file.WriteString("0123456789ABCDEF")
// 移动到文件开头
offset, err := file.Seek(0, io.SeekStart)
if err != nil {
fmt.Printf("定位失败: %v\n", err)
return
}
fmt.Printf("当前位置: %d\n", offset)
// 移动到文件中间
offset, err = file.Seek(5, io.SeekStart)
if err != nil {
fmt.Printf("定位失败: %v\n", err)
return
}
// 读取当前位置的数据
buffer := make([]byte, 5)
n, err := file.Read(buffer)
if err != nil {
fmt.Printf("读取失败: %v\n", err)
return
}
fmt.Printf("从位置 %d 读取 %d 字节: %s\n", offset, n, string(buffer))
// 移动到文件末尾
offset, err = file.Seek(0, io.SeekEnd)
if err != nil {
fmt.Printf("定位失败: %v\n", err)
return
}
fmt.Printf("文件大小: %d 字节\n", offset)
}
文件信息获取 #
获取文件状态信息 #
func getFileInfo() {
info, err := os.Stat("example.txt")
if err != nil {
fmt.Printf("获取文件信息失败: %v\n", err)
return
}
fmt.Printf("文件名: %s\n", info.Name())
fmt.Printf("文件大小: %d 字节\n", info.Size())
fmt.Printf("文件模式: %v\n", info.Mode())
fmt.Printf("修改时间: %v\n", info.ModTime())
fmt.Printf("是否为目录: %v\n", info.IsDir())
// 检查文件权限
mode := info.Mode()
fmt.Printf("权限: %s\n", mode.Perm())
fmt.Printf("是否可读: %v\n", mode&0400 != 0)
fmt.Printf("是否可写: %v\n", mode&0200 != 0)
fmt.Printf("是否可执行: %v\n", mode&0100 != 0)
}
错误处理最佳实践 #
常见错误类型 #
func handleFileErrors() {
_, err := os.Open("nonexistent.txt")
if err != nil {
// 检查特定错误类型
if os.IsNotExist(err) {
fmt.Println("文件不存在")
} else if os.IsPermission(err) {
fmt.Println("权限不足")
} else {
fmt.Printf("其他错误: %v\n", err)
}
return
}
}
资源清理 #
func properResourceCleanup() {
file, err := os.Create("temp.txt")
if err != nil {
fmt.Printf("创建文件失败: %v\n", err)
return
}
// 使用 defer 确保文件被关闭
defer func() {
if closeErr := file.Close(); closeErr != nil {
fmt.Printf("关闭文件失败: %v\n", closeErr)
}
}()
// 文件操作...
_, err = file.WriteString("临时数据")
if err != nil {
fmt.Printf("写入失败: %v\n", err)
return
}
// 同步数据到磁盘
err = file.Sync()
if err != nil {
fmt.Printf("同步失败: %v\n", err)
return
}
}
性能优化技巧 #
选择合适的缓冲区大小 #
func optimizedFileOperation() {
const bufferSize = 64 * 1024 // 64KB 缓冲区
srcFile, err := os.Open("large_source.txt")
if err != nil {
fmt.Printf("打开源文件失败: %v\n", err)
return
}
defer srcFile.Close()
dstFile, err := os.Create("large_destination.txt")
if err != nil {
fmt.Printf("创建目标文件失败: %v\n", err)
return
}
defer dstFile.Close()
// 使用大缓冲区进行高效复制
buffer := make([]byte, bufferSize)
for {
n, readErr := srcFile.Read(buffer)
if n > 0 {
_, writeErr := dstFile.Write(buffer[:n])
if writeErr != nil {
fmt.Printf("写入失败: %v\n", writeErr)
return
}
}
if readErr != nil {
if readErr == io.EOF {
break
}
fmt.Printf("读取失败: %v\n", readErr)
return
}
}
fmt.Println("文件复制完成")
}
实际应用示例 #
日志文件管理器 #
package main
import (
"bufio"
"fmt"
"os"
"time"
)
type LogManager struct {
file *os.File
writer *bufio.Writer
}
func NewLogManager(filename string) (*LogManager, error) {
file, err := os.OpenFile(filename, os.O_WRONLY|os.O_APPEND|os.O_CREATE, 0644)
if err != nil {
return nil, err
}
return &LogManager{
file: file,
writer: bufio.NewWriter(file),
}, nil
}
func (lm *LogManager) WriteLog(message string) error {
timestamp := time.Now().Format("2006-01-02 15:04:05")
logEntry := fmt.Sprintf("[%s] %s\n", timestamp, message)
_, err := lm.writer.WriteString(logEntry)
if err != nil {
return err
}
return lm.writer.Flush()
}
func (lm *LogManager) Close() error {
if err := lm.writer.Flush(); err != nil {
return err
}
return lm.file.Close()
}
func main() {
logger, err := NewLogManager("application.log")
if err != nil {
fmt.Printf("创建日志管理器失败: %v\n", err)
return
}
defer logger.Close()
// 写入日志
logger.WriteLog("应用程序启动")
logger.WriteLog("处理用户请求")
logger.WriteLog("应用程序关闭")
fmt.Println("日志写入完成")
}
小结 #
本节介绍了 Go 语言中文件读写的基础知识,包括:
- 文件操作基础 - 文件句柄、打开模式和基本概念
- 文件创建与打开 - 不同的文件打开方式和参数控制
- 文件读取 - 多种读取方法,适用于不同场景
- 文件写入 - 基础写入、追加写入和缓冲写入
- 文件位置操作 - 文件指针定位和随机访问
- 错误处理 - 常见错误类型和资源清理
- 性能优化 - 缓冲区优化和高效操作技巧
掌握这些基础知识后,你就能够在 Go 语言中高效地进行文件操作。在下一节中,我们将学习更高级的文件系统遍历技术。