在Go中逐行读取文件:从新手到专家(2025版)
\n\n您是否曾思考过如何高效地读取巨大的日志文件或在Go中处理CSV数据?如果是,那么您来对地方了。逐行读取文件是Stack Overflow上最热门的话题之一,这并非偶然。Go为我们提供了强大的工具,但选择正确的方法有时并不简单。
\n\n在本文中,我们不仅会展示如何从文件中读取行。我们还将深入探讨2025年的最佳实践:从快速的bufio.Scanner到自定义的缓冲解决方案。您将学习如何避免陷阱(例如,超过64KB的行),正确处理错误,并加速处理大数据。
准备好了吗?让我们开始吧!
\n\n为什么读取行不仅仅是“拿来就读”?
\n\n在Go中,有几种读取文件的方法,每种方法都针对不同的任务。有人使用ioutil.ReadFile(顺便说一句,从Go 1.16开始它已被弃用),有人使用带缓冲区的os.File。但对于逐行处理,王者是bufio.Scanner。
为什么?因为它:
\n- \n
- 内存效率高 — 不会将整个文件加载到RAM中 \n
- 使用简单 — 代码量最少 \n
- 能够处理错误 — 检查每一步 \n
但有一个细微之处:Scanner的默认缓冲区是64KB。如果您的行更长,就会收到错误。对于这种情况,需要其他方法。
\n\n基本示例:bufio.Scanner
\n\n我们从最常用的方法开始。以下是最小的工作代码:
\n\npackage main\n\nimport (\n "bufio"\n "fmt"\n "log"\n "os"\n)\n\nfunc main() {\n file, err := os.Open("data.txt")\n if err != nil {\n log.Fatal(err)\n }\n defer file.Close()\n\n scanner := bufio.NewScanner(file)\n for scanner.Scan() {\n line := scanner.Text()\n fmt.Println(line)\n }\n\n if err := scanner.Err(); err != nil {\n log.Fatal(err)\n }\n}\n\n\n这里重要的是什么?
\n- \n
defer file.Close()— 即使在panic时也能关闭文件 \n scanner.Text()— 返回不带换行符(\\n)的字符串 \n scanner.Err()— 检查读取过程中是否有错误 \n
这段代码对于90%的任务来说是理想的:配置文件、日志、小型CSV文件。
\n\n长行问题:增加缓冲区
\n\n想象一下:您正在读取一个包含100KB JSON行的文件。Scanner会因错误bufio.Scanner: token too long而崩溃。解决方案是增加缓冲区:
scanner := bufio.NewScanner(file)\nbuf := make([]byte, 0, 1024*1024) // 1 MB\nscanner.Buffer(buf, 10*1024*1024) // 每行最大10 MB\n\nfor scanner.Scan() {\n // ... 处理\n}\n\n\n重要提示:第二个参数是行的最大大小。如果行更大,会再次出错。请留出余量,但不要太大——内存不是无限的。
\n\n当Scanner不适用时:替代方案
\n\n尽管很流行,bufio.Scanner并非万能。有两种情况需要考虑其他方法:
1. 超大文件(GB级别)
\n如果文件大于可用内存(虽然罕见,但确实存在),Scanner仍然很好。但如果您需要读取二进制数据或处理带有自定义分隔符的行,最好使用bufio.Reader:
reader := bufio.NewReader(file)\nfor {\n line, err := reader.ReadString('\\n')\n if err != nil {\n break\n }\n // line 末尾包含 \\n\n line = strings.TrimRight(line, "\\n")\n fmt.Println(line)\n}\n\n\n优点:您可以控制每次读取操作。缺点:代码更多,出错风险更高。
\n\n2. 无需加载到内存的处理
\n有时只需要计算行数或查找模式。这时bufio.Scanner就足够了。