【问题标题】:How to read a file character by character in Go如何在 Go 中逐字符读取文件
【发布时间】:2015-08-06 14:04:05
【问题描述】:

我有一些要解析的大型 json 文件,并且我想避免一次将所有数据加载到内存中。我想要一个可以一次返回每个字符的函数/循环。

我发现 this example 用于迭代字符串中的单词,而 bufio 包中的 the ScanRunes 函数看起来一次可以返回一个字符。我也有来自 bufio 的 ReadRune 函数大部分都在工作,但这感觉是一种相当繁重的方法。

编辑

我比较了 3 种方法。所有人都使用循环从 bufio.Reader 或 bufio.Scanner 中提取内容。

  1. bufio.Reader 上使用.ReadRune 循环读取符文。检查调用 .ReadRune 的错误。
  2. 在扫描仪上调用.Split(bufio.ScanRunes) 后从bufio.Scanner 读取字节。在每次迭代中调用.Scan.Bytes,检查.Scan 调用是否有错误。
  3. 与 #2 相同,但从 bufio.Scanner 读取文本,而不是使用 .Text 读取字节。我没有将一段符文与string([]runes) 连接起来,而是将一段字符串与strings.Join([]strings, "") 连接起来以形成最终的文本块。

在 23 MB json 文件上每次运行 10 次的时间是:

  1. 0.65 s
  2. 2.40 s
  3. 0.97 s

所以看起来ReadRune毕竟还不错。它还可以减少更详细的调用,因为每个符文都是在 1 次操作(.ReadRune)中获取的,而不是 2 次(.Scan.Bytes)。

【问题讨论】:

  • 为什么觉得它很重?我建议您试试看它是否适合您。
  • @AlexAtNet 每次手动调用该函数似乎是一种奇怪的方法,而不是使用某种内置迭代器(我认为这是ScanRunes 提供的)。此外,我必须将字符串转换为符文以检查字符序列,然后将符文转换回字符串以保存部分输出。看起来像我链接的第一个示例但与 bufio 一起使用的东西会更好。真正的答案是它闻起来很有趣——对于应该非常简单的东西来说有点太复杂了。
  • ReadRune 是逐个 rune 读取文件最方便的方法。要解析 JSON,您可以一次读取文件一个字节,因为所有语法都在 ASCII 范围内。
  • 看我的回答。这很简单,我认为...
  • @BravadaZadada 关于所有 valid JSON 都在 ASCII 范围内并且能够逐字节读取的优点。我曾经担心的是使用 bufio 读取字节数组并意外切断了半个 utf-8 字符。显示我对编码的无知。 ://

标签: json parsing go io


【解决方案1】:

只需在循环中一个一个地读取每个符文...See example

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,这是我之前的做法)。我只是不知道在一片符文上调用“字符串”会将其转换为字符串,我认为它会很慢。在我接受答案之前,我想在明天尝试 @AlexAtNet 的解决方案进行比较。
【解决方案2】:

此代码从输入中读取符文。不需要强制转换,它类似于迭代器:

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 的解决方案,因为它更快更简单。但非常感谢 - 你的解决方案正是我认为我想要的。
【解决方案3】:

如果它只是关于内存大小。在即将发布的版本中(很快),将会对 json 解码器进行令牌样式增强: 你可以在这里看到它

https://tip.golang.org/pkg/encoding/json/#Decoder.Token

【讨论】:

  • 是的,我确实知道。这是定义。关于内存大小,而且,对于我们的数据,我们可以通过假设“{”和“}”永远不会在 json 内容中出现不匹配的情况来避免复杂的解析,并且可以通过简单的计数来找到对象边界。这使得分解对象非常快。这也意味着我们不必等待发布!
猜你喜欢
  • 2012-02-04
  • 1970-01-01
  • 1970-01-01
  • 2011-06-16
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多