4.1.1 文件读写基础

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 语言中文件读写的基础知识,包括:

  1. 文件操作基础 - 文件句柄、打开模式和基本概念
  2. 文件创建与打开 - 不同的文件打开方式和参数控制
  3. 文件读取 - 多种读取方法,适用于不同场景
  4. 文件写入 - 基础写入、追加写入和缓冲写入
  5. 文件位置操作 - 文件指针定位和随机访问
  6. 错误处理 - 常见错误类型和资源清理
  7. 性能优化 - 缓冲区优化和高效操作技巧

掌握这些基础知识后,你就能够在 Go 语言中高效地进行文件操作。在下一节中,我们将学习更高级的文件系统遍历技术。