读写同一文件时出现重复行的问题排查
读写同一文件时出现重复行的问题排查
问题回顾
你编写的Go代码旨在将文件中所有包含AAA的字符串替换为BBB,但执行后文件末尾出现了重复/乱码内容。原代码同时使用两个独立的文件句柄读写同一文件,示例中处理后的文件末尾出现了多余的lue</SomeValue>和</Configuration>行。
问题根源
核心问题在于同时使用两个独立的文件句柄操作同一文件,再加上未对文件进行截断处理,具体原因如下:
- 独立的文件偏移量:
fileRead和fileWrite是两个完全独立的文件描述符,各自维护自己的读写指针(偏移量)。当你通过fileRead读取内容时,它的指针前进;通过fileWrite写入时,它的指针也前进,但两者的偏移量不会自动同步,容易导致读写位置错乱。 - 未截断文件:当你完成所有写入操作后,原文件中未被新内容覆盖的部分会被保留。如果原文件长度大于最终写入的内容长度,末尾的旧内容就会残留,表现为重复或乱码。
- 额外小问题:原代码中
if strings.Contains(line, "AAAA")的判断是多余的,strings.ReplaceAll本身会自动忽略不包含目标字符串的行,无需提前判断。
修复方案
处理文件内容修改的安全方式有两种,根据文件体积选择:
方案1:内存读写(适合小文件)
直接将整个文件读取到内存中修改,再覆盖写入,逻辑简单且避免了句柄同步问题:
import ( "os" "strings" "log/slog" ) func updateRunSettingsFile(path string, logger *slog.Logger) error { // 读取文件全部内容 content, err := os.ReadFile(path) if err != nil { return err } // 执行替换操作 updatedContent := strings.ReplaceAll(string(content), "AAA", "BBB") // 覆盖写入原文件 err = os.WriteFile(path, []byte(updatedContent), 0644) if err != nil { logger.Error("could not write to file", slog.String("error", err.Error())) return err } return nil }
方案2:临时文件中转(适合大文件)
如果文件体积较大,不适合加载到内存,可以通过临时文件中转,最终原子替换原文件,避免文件损坏:
import ( "bufio" "errors" "io" "os" "path/filepath" "strings" "log/slog" ) func updateRunSettingsFile(path string, logger *slog.Logger) error { // 创建临时文件,放在原文件同目录下 tempFile, err := os.CreateTemp(filepath.Dir(path), "temp_*.xml") if err != nil { return err } tempPath := tempFile.Name() // 确保操作失败时清理临时文件 defer func() { _ = os.Remove(tempPath) }() // 打开原文件 originalFile, err := os.Open(path) if err != nil { return err } defer originalFile.Close() reader := bufio.NewReader(originalFile) writer := bufio.NewWriter(tempFile) defer writer.Flush() for { line, err := reader.ReadString('\n') if err != nil { if errors.Is(err, io.EOF) { // 处理无换行的最后一行 if line != "" { line = strings.ReplaceAll(line, "AAA", "BBB") _, writeErr := writer.WriteString(line) if writeErr != nil { logger.Error("could not write to temp file", slog.String("error", writeErr.Error())) return writeErr } } err = nil break } logger.Error("could not read from file", slog.String("error", err.Error())) return err } // 直接执行替换,无需提前判断 line = strings.ReplaceAll(line, "AAA", "BBB") _, writeErr := writer.WriteString(line) if writeErr != nil { logger.Error("could not write to temp file", slog.String("error", writeErr.Error())) return writeErr } } // 确保临时文件内容写入磁盘 if err := tempFile.Close(); err != nil { return err } // 原子替换原文件(避免中途失败导致文件损坏) if err := os.Rename(tempPath, path); err != nil { return err } return nil }
验证结果
使用上述任意一种方案后,文件替换操作会准确完成,不会出现末尾重复/乱码的问题。比如你的示例XML文件处理后会变为:
<?xml version="1.0" encoding="utf-8"?> <Configuration> <Param1>BBB</Param1> <Param2>DDD</Param2> <SomeValue>Value</SomeValue> </Configuration>
备注:内容来源于stack exchange,提问作者Alex F
相关产品推荐
相关产品推荐

