【问题标题】:Read random lines off a text file in go在 go 中从文本文件中读取随机行
【发布时间】:2014-04-05 00:14:47
【问题描述】:

我正在使用encoding/csv 来读取和解析一个非常大的 .csv 文件。
我需要随机选择行并通过一些测试。
我目前的解决方案是像

一样读取整个文件
reader := csv.NewReader(file)
lines, err := reader.ReadAll()

然后从lines中随机选择行
明显的问题是阅读整个内容需要很长时间,而且我需要大量内存。

问题:
我的问题是,encoding/csv 给了我一个io/reader 有没有办法使用它来读取随机行而不是一次加载整个内容?
更多地了解io/reader,而不是一个实际问题,这更像是一种好奇心,因为最终很可能读取一次并在内存中访问它会更有效,这样可以继续在磁盘。

【问题讨论】:

    标签: go random-access


    【解决方案1】:

    Apokalyptik 的答案与您想要的最接近。读者是流媒体,所以你不能随便跳到一个随机的地方(本身)。

    在读入任何给定行时,天真地选择保留任何给定行的概率可能会导致问题:您可能会在没有保留足够输入行的情况下到达文件末尾,或者您可能太快无法保留行和没有得到一个好的样品。两者都比正确猜测更有可能,因为您事先不知道文件中有多少行(除非您先对其进行一次迭代以计算它们)。

    你真正需要的是reservoir sampling

    基本上,逐行读取文件。每一行,您都可以选择是否持有它:您阅读的第一行,您有1/1 持有它的机会。阅读第二行后,您有1/2 机会用这一行替换您所持有的内容。在第三行之后,您有1/2 * 2/3 = 1/3 的机会改为保留该行。因此,您有1/N 机会保留任何给定的行,其中N 是您已读入的行数。这是more detailed look at the algorithm(不要尝试仅根据我所说的来实现它您仅在本段中)。

    【讨论】:

      【解决方案2】:

      最简单的解决方案是在您阅读每一行时做出决定是对其进行测试还是将其丢弃...让您的决定是随机的,这样您就不需要将整个内容保存在 RAM 中。 . 然后在运行测试后通过文件...您也可以通过非随机分布测试(例如在 X 字节或 X 行等之后)执行相同的样式

      【讨论】:

        【解决方案3】:

        我的建议是提前随机化输入文件,例如使用 shuf

        http://en.wikipedia.org/wiki/Shuf

        然后您可以根据需要简单地阅读前 n 行。

        这并不能帮助您更多地了解 io/readers,但可能会解决您的问题。

        【讨论】:

          【解决方案4】:

          我也有类似的需求:从大量文本文件中随机读取(特定)行。我写了一个包,我称之为ramcsv 来做到这一点。

          它首先读取整个文件一次并标记每行的字节偏移量(它将此信息存储在内存中,但不存储整行)。

          当您请求行号时,它会透明地寻找正确的偏移量并为您提供 csv 解析的行。

          (请注意,作为第二个参数传递给 ramcsv.New 的 csv.Reader 参数仅用于将设置复制到新读取器中。)这无疑可以提高效率,但对我来说已经足够了需要并让我免于将约 20GB 的文本文件读入内存。

          【讨论】:

            【解决方案5】:

            encoding/csv 没有给你一个io.Reader 它给你一个csv.Reader (注意csv.NewReader [1] 的定义中缺少包限定说明它返回的Reader 属于同一个包。

            csv.Reader 仅实现您在那里看到的方法,因此看起来除了编写自己的 CSV 解析器之外没有其他方法可以做您想做的事情。

            [1]http://golang.org/pkg/encoding/csv/#NewReader

            【讨论】:

            • 你是对的,但要明确一点,csv.Reader 实现了io.Reader,所以csv.Reader 实际上也是 io.Reader
            【解决方案6】:

            根据this SO answer,有一种相对节省内存的方法可以从大文件中读取单个随机行。

            package main
            
            import (
                "bufio"
                "bytes"
                "fmt"
                "io"
                "math/rand"
                "strconv"
                "time"
            )
            
            var words []byte
            
            func main() {
                prepareWordsVar()
            
                var r = rand.New(rand.NewSource(time.Now().Unix()))
            
                var line string
                for len(line) == 0 {
                    line = getRandomLine(r)
                }
            
                fmt.Println(line)
            }
            
            func prepareWordsVar() {
                base := []string{"some", "really", "file", "with", "many", "manyy", "manyyy", "manyyyy", "manyyyyy", "lines."}
            
                words = make([]byte, 200*len(base))
            
                for i := 0; i < 200; i++ {
                    for _, s := range base {
                        words = append(words, []byte(s+strconv.Itoa(i)+"\n")...)
                    }
                }
            }
            
            func getRandomLine(r *rand.Rand) string {
                wordsLen := int64(len(words))
                offset := r.Int63n(wordsLen)
            
                rd := bytes.NewReader(words)
                scanner := bufio.NewScanner(rd)
                _, _ = rd.Seek(offset, io.SeekStart)
            
                // discard - bound to be partial line
                if !scanner.Scan() {
                    return ""
                }
            
                scanner.Scan()
                if err := scanner.Err(); err != nil {
                    fmt.Printf("err: %s\n", err)
                    return ""
                }
            
                // now we have a random line.
                return scanner.Text()
            }
            

            Go Playground

            几个注意事项:

            • 如果需要加密安全,您应该使用 crypto/rand
            • 注意bufio.Scanner的默认MaxScanTokenSize,并相应地调整代码。
            • 根据原始 SO 答案,这确实会根据行的长度引入偏差。

            【讨论】:

              猜你喜欢
              • 2015-11-26
              • 1970-01-01
              • 2017-02-28
              • 1970-01-01
              • 1970-01-01
              • 2015-01-06
              • 2011-09-29
              • 1970-01-01
              • 2010-10-07
              相关资源
              最近更新 更多