Golang中bufio.Scanner为何只读取文本文件的最后一行
Golang中bufio.Scanner为何只读取文本文件的最后一行 背景:
- 在 Windows 10 上使用 Golang 版本 go1.11
- 输入文本文件由 Python 生成,其中行尾仅包含 ‘\r’ 字符。在文本编辑器中打开文件,可以看到逐行显示。Python 代码能够正确逐行读取该文件。
scanner.Split(bufio.ScanLines)仅读取文件的最后一行,且未报告任何错误。- 使用文本编辑器编辑文件并添加更多行后,GO 开始出现异常行为;例如,返回最后一行时包含前一行的字符。
scanner.Split(bufio.ScanLines)
更多关于Golang中bufio.Scanner为何只读取文本文件的最后一行的实战教程也可以访问 https://www.itying.com/category-94-b0.html
该函数的行为符合文档描述。如果您有不同需求,需要编写符合您规范的代码。
更多关于Golang中bufio.Scanner为何只读取文本文件的最后一行的实战系列教程也可以访问 https://www.itying.com/category-94-b0.html
能否请你提供一个代码片段,展示你读取文件的具体方式?如果这是长程序的一部分,请用Go编写一个简短的程序来演示文件读取过程并展示你描述的行为。
这是一种"用户错误"的情况,生成的文件中包含无效的行尾符。我们在此讨论的是 Golang 在此类情况下的行为表现。需要注意的是,文本编辑器和 Python(我尚未尝试 Java)能够读取此类文件。
你好 @jayts
以下是写入文件的Python代码:
with open('c:/temp/test.txt', 'w') as out:
out.write('Hello Golang!\rI am a Python programmer.\rI think I like to Go\r')
读取文件的Golang代码(如下方 @johandalabacka 所发布):
func main() {
f, _ := os.Open("test.txt")
defer f.Close()
scanner := bufio.NewScanner(f)
for scanner.Scan() {
fmt.Println(">", scanner.Text())
}
}
@NobbZ 你说得对,ScanLines 确实是按照规范工作的。我原以为这是个 bug… 😕

bufio package - bufio - Go Packages
bufio 包实现了带缓冲的 I/O。它包装了一个 io.Reader 或 io.Writer 对象,创建另一个也实现了该接口的对象(Reader 或 Writer),同时提供了缓冲和一些文本 I/O 的帮助功能。
行尾标记是一个可选的回车符后跟一个必需的换行符。用正则表达式表示就是
\r?\n
func main() {
// 示例代码
scanner := bufio.NewScanner(strings.NewReader("input"))
for scanner.Scan() {
fmt.Println(scanner.Text())
}
}
btnsjc:
bufio.ScanLines
你说得对,扫描器无法处理仅以\r作为行尾的文件
package main
import (
"bufio"
"fmt"
"os"
)
func main() {
f, _ := os.Open("test.txt")
defer f.Close()
scanner := bufio.NewScanner(f)
for scanner.Scan() {
fmt.Println(">", scanner.Text())
}
}
对于这样的文本:
Hello, it's me
I was wondering if after all these years you'd like to meet
To go over everything
They say that time's supposed to heal ya
But I ain't done much healing
Hello, can you hear me
I'm in California dreaming about who we used to be
When we were younger and free
I've forgotten how it felt before the world fell at our feet
只会输出最后一行,但不带>符号
我早已忘记在世界崩塌前那是怎样的感觉
我从源代码中提取了ScanLines函数并进行了修改,使其能够处理\r字符,但现在它只能处理\r而无法处理\n或
package main
import (
"bufio"
"bytes"
"fmt"
"os"
)
func ScanLinesWithCR(data []byte, atEOF bool) (advance int, token []byte, err error) {
if atEOF && len(data) == 0 {
return 0, nil, nil
}
if i := bytes.IndexByte(data, '\r'); i >= 0 {
// We have a full carriage return-terminated line.
return i + 1, data[0:i], nil
}
// If we're at EOF, we have a final, non-terminated line. Return it.
if atEOF {
return len(data), data, nil
}
// Request more data.
return 0, nil, nil
}
func main() {
f, _ := os.Open("test.txt")
defer f.Close()
scanner := bufio.NewScanner(f)
scanner.Split(ScanLinesWithCR)
for scanner.Scan() {
fmt.Println(">", scanner.Text())
}
}
但实际上标准函数应该处理这种情况。我会看看是否能报告这个问题。
引用 btnsjc 的话:
以仅包含 ‘\r’ 字符结尾的行。
这并非行尾标识。
行尾应为 \n(MacOS X 和 Linux 系统)或 \r\n(Windows 系统)。单独的 \r 仅出现在八九十年代的老式计算机中,现在通常已经见不到了。
实际上扫描器会扫描全部内容,而不仅是你所说的最后"一行"。但由于大多数终端模拟器在遇到 \r 时会将光标移至行首,后续字符会覆盖先前内容。请看以下示例:
XXXXXXXXXXXXXXXXXXX one
Greetings from line
(你可能需要手动替换行尾符)。
编辑
bufio.ScanLines 的文档甚至通过正则表达式和通俗语句说明了行尾符的格式要求:
ScanLines 是 Scanner 的分割函数,用于返回去除行尾标记的文本行。返回的行可能为空。行尾标记由一个可选回车符和一个必需换行符组成。用正则表达式表示为
\r?\n。即使最后一行没有换行符,只要非空就会被返回。
func main() {
// 示例代码
scanner := bufio.NewScanner(strings.NewReader("input"))
for scanner.Scan() {
fmt.Println(scanner.Text())
}
}
在Go语言中,bufio.Scanner 默认使用 bufio.ScanLines 作为分割函数,它设计用于处理标准的行结束符(如 \n 或 \r\n)。然而,如果您的文本文件仅包含 \r 作为行尾字符(常见于旧版Mac系统或某些特定生成的文件),ScanLines 可能无法正确识别行边界,导致仅读取最后一行或出现异常行为。这是因为 ScanLines 在遇到 \r 时,如果后面没有 \n,它可能不会将其视为完整的行结束。
问题根源在于 bufio.ScanLines 的实现:它处理 \r\n 作为单个行结束符,并单独处理 \n,但对于孤立的 \r,它可能不会触发行分割,除非 \r 后紧跟 \n 或文件结束。在您的文件中,行尾只有 \r,因此 Scanner 可能将整个文件内容视为单个“行”,直到文件结束,然后返回最后一部分数据。
要解决这个问题,您可以自定义一个分割函数来处理仅以 \r 结尾的行。以下是一个示例代码,展示如何实现自定义分割函数:
package main
import (
"bufio"
"bytes"
"fmt"
"io"
"strings"
)
// 自定义分割函数,处理以 '\r' 或标准行结束符结尾的行
func scanLinesWithCR(data []byte, atEOF bool) (advance int, token []byte, err error) {
if atEOF && len(data) == 0 {
return 0, nil, nil
}
// 查找 '\r' 或 '\n' 的位置
if i := bytes.IndexAny(data, "\r\n"); i >= 0 {
// 找到行结束符,返回该行
return i + 1, data[0:i], nil
}
// 如果到达文件末尾,返回剩余数据
if atEOF {
return len(data), data, nil
}
// 请求更多数据
return 0, nil, nil
}
func main() {
// 示例输入数据:模拟包含 '\r' 行尾的文件内容
input := "第一行\r第二行\r第三行\r"
scanner := bufio.NewScanner(strings.NewReader(input))
// 使用自定义分割函数
scanner.Split(scanLinesWithCR)
// 逐行读取并打印
for scanner.Scan() {
line := scanner.Text()
fmt.Printf("读取的行: %q\n", line)
}
if err := scanner.Err(); err != nil {
fmt.Printf("扫描错误: %v\n", err)
}
}
在这个示例中,scanLinesWithCR 函数通过 bytes.IndexAny 查找 \r 或 \n 字符,从而正确分割行。运行此代码将输出:
读取的行: "第一行"
读取的行: "第二行"
读取的行: "第三行"
对于您的实际文件,您可以将 scanner 初始化为从文件读取(例如使用 os.Open),并应用相同的自定义分割函数。这应该能解决仅读取最后一行的问题。如果文件较大或性能关键,请确保处理可能的错误,如 scanner.Err()。

