【问题标题】:Golang read from pipe reads tons of data从管道读取的 Golang 读取大量数据
【发布时间】:2014-11-30 23:37:04
【问题描述】:

我正在尝试读取一个被 tar 化、流式传输到标准输入的存档,但不知何故,我在管道中读取的数据比 tar 发送的数据多得多。

我这样运行我的命令:

tar -cf - somefolder | ./my-go-binary

源码是这样的:

package main

import (
    "bufio"
    "io"
    "log"
    "os"
)

// Read from standard input
func main() {
    reader := bufio.NewReader(os.Stdin)
    // Read all data from stdin, processing subsequent reads as chunks.
    parts := 0
    for {
        parts++
        data := make([]byte, 4<<20) // Read 4MB at a time
        _, err := reader.Read(data)
        if err == io.EOF {
            break
        } else if err != nil {
            log.Fatalf("Problems reading from input: %s", err)
        }
    }
    log.Printf("Total parts processed: %d\n", parts)
}

对于一个 100MB 的 tar 文件夹,我得到了 1468 个 4MB 的块(即 6.15GB)!此外,data []byte 数组有多大似乎并不重要:如果我将块大小设置为 40MB,我仍然会得到大约 1400 个 40MB 数据块,这根本没有意义。

我需要做些什么才能使用 Go 正确读取来自 os.Stdin 的数据吗?

【问题讨论】:

    标签: go stdin tar


    【解决方案1】:

    您的代码效率低下。每次循环都会分配和初始化data

    for {
        data := make([]byte, 4<<20) // Read 4MB at a time
    }
    

    reader 的代码io.Reader 是错误的。例如,您忽略了_, err := reader.Read(data) 读取的字节数,并且您没有正确处理err 错误。

    Package io

    import "io" 
    

    type Reader

    type Reader interface {
            Read(p []byte) (n int, err error)
    }
    

    Reader 是包装基本 Read 方法的接口。

    Read 最多将 len(p) 个字节读入 p。它返回字节数 读取 (0

    当 Read 在之后遇到错误或文件结束条件时 成功读取 n > 0 个字节,它返回读取的字节数。 它可能会从同一个调用返回(非零)错误或返回 来自后续调用的错误(和 n == 0)。这个将军的一个例子 情况是 Reader 最后返回非零字节数 的输入流可能会返回 err == EOF 或 err == nil。这 next Read 应该返回 0, EOF 无论如何。

    调用者应始终处理之前返回的 n > 0 个字节 考虑到错误错误。这样做可以正确处理 I/O 错误 在读取一些字节以及两个允许的 EOF 后发生 行为。

    不鼓励 Read 的实现返回零字节 计数为零错误,除非 len(p) == 0。调用者应处理 返回 0 和 nil 表示没有发生任何事情;在 特别是它不表示EOF。

    实现不得保留 p。

    这是一个符合io.Reader接口的模型文件读取程序:

    package main
    
    import (
        "bufio"
        "io"
        "log"
        "os"
    )
    
    func main() {
        nBytes, nChunks := int64(0), int64(0)
        r := bufio.NewReader(os.Stdin)
        buf := make([]byte, 0, 4*1024)
        for {
            n, err := r.Read(buf[:cap(buf)])
            buf = buf[:n]
            if n == 0 {
                if err == nil {
                    continue
                }
                if err == io.EOF {
                    break
                }
                log.Fatal(err)
            }
            nChunks++
            nBytes += int64(len(buf))
            // process buf
            if err != nil && err != io.EOF {
                log.Fatal(err)
            }
        }
        log.Println("Bytes:", nBytes, "Chunks:", nChunks)
    }
    

    输出:

    2014/11/29 10:00:05 字节:5589891 块:1365

    【讨论】:

    • 感谢您还涵盖了规范中的错误情况。当我尝试了它的变体时,这肯定会起作用。谢谢。
    【解决方案2】:

    阅读阅读文档:

    Read 将数据读入 p。它返回读入 p 的字节数。它 在底层 Reader 上最多调用一次 Read,因此 n 可能会更少 比 len(p)。在 EOF 时,计数为零,err 为 io.EOF。

    您一次读取的不是 4MB。您正在提供缓冲区空间并丢弃可以告诉您 Read 实际读取了多少的整数。缓冲区空间是最大的,但最常见的情况是每次调用读取 128k,至少在我的系统上是这样。自己试试吧:

    // Read from standard input
    func main() {
        reader := bufio.NewReader(os.Stdin)
        // Read all data from stdin, passing the data as parts into the channel
        // for processing.
        parts := 0
        for {
            parts++
            data := make([]byte, 4<<20) // Read 4MB at a time
            amount , err := reader.Read(data)
            // WILL NOT BE 4MB!
            log.Printf("Read: %v\n", amount)
            if err == io.EOF {
                break
            } else if err != nil {
                log.Fatalf("Problems reading from input: %s", err)
            }
        }
        log.Printf("Total parts processed: %d\n", parts)
    }
    

    您必须实现处理不同读取量的逻辑。

    【讨论】:

    • 啊,好吧。该诊断肯定符合症状。奇怪的是,我还尝试使用 NewReaderSize() 和 4
    • @user918176:这只是io.Reader 接口规范的一部分。例如,您忽略amount != 0 &amp;&amp; err == io.EOF
    • @user918176:每次循环分配和初始化data效率很低。
    • 确实如此,我立即注意到了不必要的分配。但是,这超出了问题的范围。
    猜你喜欢
    • 1970-01-01
    • 2018-04-19
    • 1970-01-01
    • 1970-01-01
    • 2023-03-06
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多