MST

星途 面试题库

面试题:Go文件操作与标准库的深度优化

在一个高并发的网络服务场景下,多个协程会频繁地对不同类型的文件(如配置文件、日志文件、数据文件等)进行读写操作。请基于Go的标准库设计一套完整的文件操作管理机制,确保文件操作的原子性、一致性和高性能,同时要处理好文件锁、缓存以及不同操作系统下的兼容性问题。详细阐述设计思路并给出关键代码示例。
32.7万 热度难度
编程语言Go

知识考点

AI 面试

面试题答案

一键面试

设计思路

  1. 原子性操作:使用os包的文件操作函数,在写文件时先写入临时文件,然后通过os.Rename原子性地替换目标文件。对于读操作,由于Go的文件读取操作本身是原子的(针对单个读取调用),不需要额外处理。
  2. 一致性:通过文件锁保证同一时间只有一个协程可以对文件进行写操作。读操作在获取文件锁后进行,避免在写操作过程中读取到不一致的数据。
  3. 高性能:引入缓存机制,对于频繁读取的文件,将其内容缓存起来,减少文件读取次数。同时,合理地复用文件描述符,避免频繁打开和关闭文件。
  4. 文件锁:使用syscall.Flock函数实现文件锁,它在不同操作系统(Linux、Windows等)下都有较好的兼容性。
  5. 缓存:使用map来实现简单的缓存,键为文件名,值为文件内容。
  6. 操作系统兼容性:除了使用syscall.Flock保证文件锁的兼容性外,在文件路径处理上使用path/filepath包,它可以根据不同操作系统自动处理路径分隔符。

关键代码示例

package main

import (
    "fmt"
    "io/ioutil"
    "os"
    "path/filepath"
    "syscall"
    "sync"
)

var (
    fileCache = make(map[string][]byte)
    cacheLock sync.RWMutex
    fileLocks = make(map[string]*os.File)
    lockMutex sync.Mutex
)

// 获取文件锁
func acquireLock(filePath string) (*os.File, error) {
    lockMutex.Lock()
    defer lockMutex.Unlock()

    if f, ok := fileLocks[filePath]; ok {
        return f, nil
    }

    lockFile, err := os.OpenFile(filepath.Join(filePath, ".lock"), os.O_WRONLY|os.O_CREATE, 0666)
    if err != nil {
        return nil, err
    }

    err = syscall.Flock(int(lockFile.Fd()), syscall.LOCK_EX)
    if err != nil {
        lockFile.Close()
        return nil, err
    }

    fileLocks[filePath] = lockFile
    return lockFile, nil
}

// 释放文件锁
func releaseLock(filePath string) {
    lockMutex.Lock()
    defer lockMutex.Unlock()

    if f, ok := fileLocks[filePath]; ok {
        f.Close()
        os.Remove(filepath.Join(filePath, ".lock"))
        delete(fileLocks, filePath)
    }
}

// 读取文件内容
func readFile(filePath string) ([]byte, error) {
    cacheLock.RLock()
    if content, ok := fileCache[filePath]; ok {
        cacheLock.RUnlock()
        return content, nil
    }
    cacheLock.RUnlock()

    lockFile, err := acquireLock(filePath)
    if err != nil {
        return nil, err
    }
    defer releaseLock(filePath)

    content, err := ioutil.ReadFile(filePath)
    if err != nil {
        return nil, err
    }

    cacheLock.Lock()
    fileCache[filePath] = content
    cacheLock.Unlock()

    return content, nil
}

// 写入文件内容
func writeFile(filePath string, content []byte) error {
    lockFile, err := acquireLock(filePath)
    if err != nil {
        return err
    }
    defer releaseLock(filePath)

    tempFilePath := filePath + ".tmp"
    err = ioutil.WriteFile(tempFilePath, content, 0644)
    if err != nil {
        return err
    }

    err = os.Rename(tempFilePath, filePath)
    if err != nil {
        os.Remove(tempFilePath)
        return err
    }

    cacheLock.Lock()
    fileCache[filePath] = content
    cacheLock.Unlock()

    return nil
}