go读文件的几种姿势

写程序时经常需要从一个文件读取数据,然后输出到另一个文件。最典型的例子比如,从stdin读取,然后输出到stdout。go提供了几种读取文件的方式,我们逐个来看一看~

fmt.Scan

scss 复制代码
func fmtScan() {
    str := ""
    for {
        fmt.Scan(&str)
        fmt.Printf("[fmt.scan]: %q (%d)\n", str, len(str))
    }
}
// input: abc def ghi
// output:
//[fmt.scan]: "abc" (3)
//[fmt.scan]: "def" (3)
//[fmt.scan]: "ghi" (3)

fmt.Scan 从stdin读取数据,输出到内存变量。它会将空白符作为分割符,把分割后的字符串,依次赋值给传入参数。在上面的例子中,就是循环了3次分别赋值给str。

os.Stdin.Read

lua 复制代码
func osStdinRead() {
    p := make([]byte, 5)
    for {
        nn, err := os.Stdin.Read(p)
        if err != nil && err != io.EOF {
            panic(err)
        }
        fmt.Printf("[os.stdin.read]: %q (%d)\n", p, nn)
        p = make([]byte, 5) // 清空p
    }
}
// input: abcdefghijk
// output:
//[os.stdin.read]: "abcde" (5)
//[os.stdin.read]: "fghij" (5)
//[os.stdin.read]: "k\n\x00\x00\x00" (2)

最多读取len(p)大小的数据,如果输入的内容超出了len(p),则Read会在下一轮循环中接着读取。

io系列

ReadAll

scss 复制代码
func ioReadAll() {
    for {
        p, err := io.ReadAll(os.Stdin)
        if err != nil {
            panic(err)
        }
        fmt.Printf("[io.ReadAll]: %q (%d)\n", p, len(p))
    }
}

// input: abc\nEOF
// output: [io.ReadAll]: "abc\n" (4)

ReadAll 读取全部,不把EOF当作错误返回。

ReadFull

go 复制代码
func ioReadFull() {
    p := make([]byte, 5)
    for {
        nn, err := io.ReadFull(os.Stdin, p)
        if err != nil && err != io.EOF {
            fmt.Printf("[io.ReadFull]: %q (%d)\n", p, nn)
            panic(err)
        }
        fmt.Printf("[io.ReadFull]: %q (%d)\n", p, nn)
        p = make([]byte, 5)
    }
}

// input: aaaaaa
// output: [io.ReadFull]: "aaaaa" (5)

// input: aa\n\x00
// output: [io.ReadFull]: "aa\n\x00\x00" (3)

ReadFull 的目标是把p填满,也就是说正常情况下只有读到的数据长度nn==len(p)才返回。这里有两种情况需要注意:

  • 没有数据可读,ReadFull返回 (0, EOF)
  • 有一些数据可读,但在填满p前遇到了EOF,ReadFull返回 (读取的长度, ErrUnexpectedEOF)

bufio系列

bufio给io操作提供了一个缓冲,避免了每次都要访问磁盘。

Read

go 复制代码
func bufioRead() {
    br := bufio.NewReader(os.Stdin)
    p := make([]byte, 5)
    for {
        nn, err := br.Read(p)
        if err != nil && err != io.EOF{
            panic(err)
        }
        fmt.Printf("[bufio.read]: %q (%d)\n", p, nn)
    }
}

// input: abcdefghijk
// output:
//[bufio.read]: "abcde" (5)
//[bufio.read]: "fghij" (5)
//[bufio.read]: "k\n\x00\x00\x00" (2)

最多读取len(p)大小的数据,如果输入的内容超出了len(p),则Read会在下一轮循环中接着读取。

ReadBytes

scss 复制代码
func bufioReadBytes() {
    br := bufio.NewReader(os.Stdin)
    for {
        p, err := br.ReadBytes('\n')
        if err != nil && err != io.EOF{
            panic(err)
        }
        fmt.Printf("[bufio.ReadBytes]: %q (%d)\n", p, len(p))
    }
}
// input: abcdef
// output: [bufio.ReadBytes]: "abcdef\n" (7)

ReadBytes 读取直到遇到给定的分界符,这里是'\n'(返回的p包含\n)。如果在读到分界符之前遇到了err,则返回读到的数据和err(一般是EOF)。

ReadString

scss 复制代码
func bufioReadString() {
    br := bufio.NewReader(os.Stdin)
    for {
        str, err := br.ReadString('\n')
        if err != nil && err != io.EOF {
            panic(err)
        }
        fmt.Printf("[bufio.ReadString]: %q (%d)\n", str, len(str))
    }
}

// input: abcdef
// output: [bufio.ReadString]: "abcdef\n" (7)

ReadString 与 ReadBytes 类似,都是直到读到分界符才返回。

ReadRune

go 复制代码
func bufioReadRune() {
    br := bufio.NewReader(os.Stdin)
    for {
        r, sz, err := br.ReadRune()
        if err != nil && err != io.EOF {
            panic(err)
        }
        fmt.Printf("[bufio.ReadRune]: %q (%d)\n", r, sz)
    }
}

// input: abc
// output: 
//[bufio.ReadRune]: 'a' (1)
//[bufio.ReadRune]: 'b' (1)
//[bufio.ReadRune]: 'c' (1)
//[bufio.ReadRune]: '\n' (1)

ReadRune 一次读取一个UTF8字符。

Scan

scss 复制代码
func bufioScan() {
    scanner := bufio.NewScanner(os.Stdin)
    for scanner.Scan() {
        text := scanner.Text()
        fmt.Printf("[bufio.Scan]: %q (%d)\n", text, len(text))
    }
    if scanner.Err() != nil {
        fmt.Print(scanner.Err().Error())
    }
}

Scan 扫描文件到下一个token并返回true,随后可以通过Text,Bytes读取token。当扫描停止时,Scan返回false,这时可以通过Err获取错误,如果是因为EOF而扫描停止,Err=nil。

scanner在创建时,默认使用 ScanLines 作为分割函数。它会将输入文本分割成一系列的行,每行作为一个token。除此之外还有其他分割函数:

  • ScanLines(默认):按行分割。
  • ScanWords:按空白字符分割的单词。
  • ScanBytes:按字节分割。
  • ScanRunes:按UTF-8编码的字符分割。

总结

如果只是简单从stdin读取,fmt.Scan 足够短小好用。当文件很大时 io.ReadAll, io.ReadFull 感觉在使用时不是很灵活,还是推荐用bufio的一系列方法操作。

相关推荐
小强19887 分钟前
SQL Server 慢查询怎么定位?一套从“卡”到“快”的完整排查流程
后端
水深火乐9 分钟前
interface和any
后端
步行cgn10 分钟前
MyBatis Error evaluating expression ‘ids‘. Return value (3) was not iterable 错误详
java·后端
神奇小汤圆13 分钟前
Spring Boot + LangChain4j实现RAG——从零搭建企业级知识库问答系统
后端
H_Peak18 分钟前
Python运算符与流程控制:if条件判断
后端
H_Peak20 分钟前
Python字符串操作:从基础到正则入门
后端
自进化Agent智能体20 分钟前
Hermes 子代理委派 —— 并行工作的艺术
后端
步行cgn23 分钟前
MyBatis 动态 SQL 中的 <foreach> 标签完全解析
后端
SomeB1oody1 小时前
【RustyML入门】5.0. 模型评估
开发语言·后端·机器学习·rust·教程
唐青枫2 小时前
别只把 enum 当常量列表:Zig 枚举、状态机与 Tagged Union 实战
后端