如何在Go中逐个字符读取文件

8
我有一些大的JSON文件需要解析,我想避免一次性将所有数据加载到内存中。我想要一个函数/循环,可以一次返回一个字符。
我找到了这个示例来迭代字符串中的单词,bufio包中的ScanRunes函数看起来可以一次返回一个字符。我还使用了bufio.Reader的ReadRune函数,但那感觉像是一种沉重的方法。
编辑
我比较了三种方法。所有方法都使用循环从bufio.Reader或bufio.Scanner中提取内容。
  1. 使用.ReadRunebufio.Reader上循环读取符文。检查从调用.ReadRune返回的错误。
  2. 在调用.Split(bufio.ScanRunes)后,从bufio.Scanner中读取字节。在每次迭代中调用.Scan.Bytes,检查.Scan调用是否有误。
  3. 与#2相同,但是使用.Textbufio.Scanner中读取文本而不是字节。我使用strings.Join([]strings, "")连接字符串切片来形成最终的文本块,而不是使用string([]runes)连接符文切片。

对于一个23 MB的json文件,每个方法运行10次的时间如下:

  1. 0.65秒
  2. 2.40秒
  3. 0.97秒

看起来ReadRune还不错。它还可以减少调用次数,因为每个符文只需要一个操作(.ReadRune)就可以获取,而不是两个操作(.Scan.Bytes)。


为什么你认为它很重?我建议你试一试,看看它是否适合你。 - Alex Netkachov
@AlexAtNet 手动调用函数似乎是一种奇怪的方法,而不是使用某种内置迭代器(我认为这就是ScanRunes提供的)。另外,我还需要将字符串强制转换为符文以检查字符序列,然后将符文转回字符串以保存部分输出。类似于我链接到的第一个示例,但使用bufio更好。真正的答案是它闻起来有些可笑 - 对于应该非常简单的事情来说过于复杂了一点。 - turtlemonvh
1
ReadRune 是逐个读取文件符文最方便的方法。要解析 JSON,您可以一次读取一个字节的文件,因为所有语法都在 ASCII 范围内。 - Charlie Tumahai
@BravadaZadada 的观点非常好,关于所有有效的 JSON 都在 ASCII 范围内,并且能够逐字节读取。我的一个担忧是使用 bufio 读取到一个字节数组中并意外地截断了一半的 utf-8 字符。这显示了我对编码的无知。 :/ - turtlemonvh
@BravadaZadada 是的,我的意思是使用像ReadReadBytes这样的东西将文件读取到字节数组中,然后在此之后将其分成符文。我认为如果我那样做可能会遇到问题。但是看看那些以字节为输入并提取符文的函数,它们似乎无论如何都会抱怨输入无效,所以我可能过早地担心了这个问题。 - turtlemonvh
显示剩余3条评论
3个回答

10

只需在循环中按顺序读取每个符文... 查看示例

package main

import (
    "bufio"
    "fmt"
    "io"
    "log"
    "strings"
)

var text = `
The quick brown fox jumps over the lazy dog #1.
Быстрая коричневая лиса перепрыгнула через ленивую собаку.
`

func main() {
    r := bufio.NewReader(strings.NewReader(text))
    for {
        if c, sz, err := r.ReadRune(); err != nil {
            if err == io.EOF {
                break
            } else {
                log.Fatal(err)
            }
        } else {
            fmt.Printf("%q [%d]\n", string(c), sz)
        }
    }
}

最终我使用了这个解决方案(只是在循环中调用readRune,就像我以前做的)。我只是不知道在一个rune切片上调用"string"会将其转换为字符串,并且我认为它可能很慢。我想明天尝试一下@AlexAtNet的解决方案,进行比较,再决定接受哪个答案。 - turtlemonvh

8

这段代码从输入中读取符文。不需要转换,类似于迭代器:

package main

import (
    "bufio"
    "fmt"
    "strings"
)

func main() {
    in := `{"sample":"json string"}`

    s := bufio.NewScanner(strings.NewReader(in))
    s.Split(bufio.ScanRunes)

    for s.Scan() {
        fmt.Println(s.Text())
    }
}

Alex - 我最终选择了tez的解决方案,因为它更快速且稍微简单一些。但还是非常感谢你 - 你的解决方案是我认为我想要的。 - turtlemonvh

1

是的,我知道这个。它确实与内存大小有关,但对于我们的数据来说,我们可以通过假设在JSON内容中"{"和"}"永远不会出现不匹配的情况,并通过简单计数来找到对象边界,从而避免使用更复杂的解析方式。这使得分解对象非常快速。同时也意味着我们不必等待那个版本发布! - turtlemonvh

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接