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的一系列方法操作。

相关推荐
IT_陈寒8 小时前
闭包陷阱让我加了两天班,JavaScript你真行
前端·人工智能·后端
易协同低代码9 小时前
通达OA核心类库TD类深度解析
后端
Gopher_HBo9 小时前
Go语言学习笔记(十八)Gin处理Session
后端
谭光志10 小时前
工具塞满上下文窗口怎么办?深度拆解 AI Agent Tool Search 按需加载实现原理
前端·后端·ai编程
她说..10 小时前
Java 默认值设置方式
java·开发语言·后端·springboot
foggyprojects10 小时前
从0开始,一句话启动AI DataAgent
后端·数据分析·ai编程
郡杰10 小时前
一些基础和问题解决
后端
陈随易11 小时前
前端项目部署只要30秒
前端·后端·程序员
YIAN11 小时前
从零手写文件读取 MCP 服务:一文吃透 Model Context Protocol 全链路通信原理
前端·后端·mcp
Imchendiana11 小时前
《狂人日记NO.9》— 前后端一把梭,我的全栈实录
前端·后端