从 golang 中的缓冲读取器读取特定数量的字节

Posted

技术标签:

【中文标题】从 golang 中的缓冲读取器读取特定数量的字节【英文标题】:Reading specific number of bytes from a buffered reader in golang 【发布时间】:2012-11-19 14:20:14 【问题描述】:

我从 bufio 包中了解到 golang 中的特定功能。

func (b *Reader) Peek(n int) ([]byte, error)

Peek 返回接下来的 n 个字节不推进阅读器。字节 在下一次读取调用时停止有效。如果 Peek 返回小于 n 字节,它还返回一个错误,解释为什么读取很短。这 如果 n 大于 b 的缓冲区大小,则错误为 ErrBufferFull。

我需要能够从阅读器中读取特定数量的字节,这将推动阅读器。基本上,与上面的功能相同,但它使读者进步。有谁知道如何做到这一点?

【问题讨论】:

【参考方案1】:

如果您想从io.Reader 读取字节并进入io.Writer,那么您可以使用io.CopyN

CopyN 从 src 复制 n 个字节(或直到出错)到 dst。它返回复制的字节数和复制时遇到的最早错误。

返回时,写入 == n 当且仅当 err == nil。

written, err := io.CopyN(dst, src, n)
if err != nil 
    // We didn't read the desired number of bytes
 else 
   // We can proceed successfully

【讨论】:

【参考方案2】:

TLDR:

my42bytes, err := ioutil.ReadAll(io.LimitReader(myReader, 42))

完整答案:

@monicuta 提到了io.ReadFull,效果很好。这里我提供另一种方法。它通过将ioutil.ReadAllio.LimitReader 链接在一起来工作。让我们先阅读文档:

$ go doc ioutil.ReadAll
func ReadAll(r io.Reader) ([]byte, error)
     ReadAll reads from r until an error or EOF and returns the data it read. A
     successful call returns err == nil, not err == EOF. Because ReadAll is
     defined to read from src until EOF, it does not treat an EOF from Read as an
     error to be reported. 

$ go doc io.LimitReader
func LimitReader(r Reader, n int64) Reader
     LimitReader returns a Reader that reads from r but stops with EOF after n
     bytes. The underlying implementation is a *LimitedReader.

所以如果你想从myReader 获取 42 个字节,你可以这样做

import (
        "io"
        "io/ioutil"
)

func main() 
        // myReader := ...
        my42bytes, err := ioutil.ReadAll(io.LimitReader(myReader, 42))
        if err != nil 
                panic(err)
        
        //...

这是io.ReadFull的等效代码

$ go doc io.ReadFull
func ReadFull(r Reader, buf []byte) (n int, err error)
    ReadFull reads exactly len(buf) bytes from r into buf. It returns the number
    of bytes copied and an error if fewer bytes were read. The error is EOF only
    if no bytes were read. If an EOF happens after reading some but not all the
    bytes, ReadFull returns ErrUnexpectedEOF. On return, n == len(buf) if and
    only if err == nil. If r returns an error having read at least len(buf)
    bytes, the error is dropped.
import (
        "io"
)

func main() 
        // myReader := ...
        buf := make([]byte, 42)
        _, err := io.ReadFull(myReader, buf)
        if err != nil 
                panic(err)
        
        //...

io.ReadFull相比,一个优点是您不需要手动创建buf,其中len(buf)是您要读取的字节数,然后在您传递buf作为参数时阅读

相反,您只需告诉io.LimitReader 您最多需要来自myReader 的42 个字节,然后调用ioutil.ReadAll 将它们全部读取,并将结果作为字节片返回。如果成功,则保证返回的切片长度为 42。

【讨论】:

关于您的最后一段,不能保证切片的长度为 42 字节。 io.Reader 可能返回的字节数少于请求的字节数。 ReadFull 将尝试读取尽可能多的字节,LimitReader 将限制您要读取的字节。当您想真正读取 N 个字节时,ReadFull 可以简化处理。 ...否则会返回错误(一件好事)。 + ReadAll 可能会产生比您需要的更大的缓冲区。 这必须是一种公认​​的方法。也许最后稍微改写一下会有所帮助,但否则它可以解决问题并回答问题。我想知道为什么没有直接的功能。谢谢@navigaid 实际上,io.LimitReader 仅在您想限制某些您无法控制的代码时才有用,例如一些外部库代码。如果它是你的代码,那么它就没有用处,因为它只是调用一个普通的 Read() once,无论如何它不能读取超过你请求的内容。现在的事情,人们在问,我试图找到一个答案:它到底是在哪里被告知阅读不能阅读超过要求的?答案是:无处。在 Linux 文档中,底层级别,他们说“通常”和“预期”。 Go 中的任何东西都不说一句话。可能,对于谷歌的人来说这太明显了? ))))【参考方案3】:

为此,您只需创建一个字节切片并将read 数据放入该切片中

n := 512
buff := make([]byte, n)
fs.Read(buff)  // fs is your reader. Can be like this fs, _ := os.Open('file')

func (b *Reader) Read(p []byte) (n int, err error)

Read 将数据读入 p。它返回读入 p 的字节数。 这些字节最多取自底层 Reader 上的一个 Read, 因此 n 可能小于 len(p)

【讨论】:

【参考方案4】:

我更喜欢 Read() 尤其是当你要读取任何类型的文件时,它在以块的形式发送数据时也很有用,下面是一个例子来展示它是如何使用的

fs, err := os.Open("fileName"); 

if err != nil
    fmt.Println("error reading file")
    return


defer fs.Close()

reader := bufio.NewReader(fs)

buf := make([]byte, 1024)

for
    v, _ := reader.Read(buf) //ReadString and ReadLine() also applicable or alternative

    if v == 0
        return
    
    //in case it is a string file, you could check its content here...
    fmt.Print(string(buf))

【讨论】:

【参考方案5】:

注意bufio.Read 方法最多调用底层io.Read 一次,这意味着它可以返回n < len(p),而不会到达EOF。如果您想准确读取len(p) 字节或因错误而失败,您可以像这样使用io.ReadFull

n, err := io.ReadFull(reader, p)

即使阅读器被缓冲,这也有效。

【讨论】:

这应该是公认的答案。消除了“短”阅读的烦恼,即无需循环和检查 io.EOF 等。Doc 在这里也有一个很好的例子:golang.org/pkg/io/#ReadFull 也许值得注意的是 io.ReadFull 只是这个调用的一个包装器:io.ReadAtLeast(reader, p, len(p)) 同样对于io.ReadFull,您应该首先定义p,其长度等于大小您要读取的字节数,但对于 io.ReadAtLeastp 的长度可以是任意长度,只要它大于或等于您要读取的大小即可。 @sepehr 是对的。问题的措辞是“特定的字节数”,即如果我理解英语正确,则等于“确切的字节数”。此外,如果您想通过流实现消息框架,这是一个非常实际的问题。 “精确”或“具体”与“大于或等于”不同。如果我正好需要 4 个字节,那么如果它读取 400 怎么办?问题是关于这个,特别是。 实际上,io.LimitReader 仅在您想限制某些您无法控制的代码时才有用,例如一些外部库代码。如果它是你的代码,那么它就没有用处,因为它只是调用一个普通的 Read() once,无论如何它不能读取超过你请求的内容。现在的事情,人们在问,我试图找到一个答案:它到底是在哪里被告知阅读不能阅读超过要求的?答案是:无处。在 Linux 文档中,底层级别,他们说“通常”和“预期”。 Go 中的任何东西都不说一句话。也许,对于谷歌的人来说这太明显了? 我的意思是,在其他地方人们建议使用 LimitReader 来限制读取的最大字节数。实际上,这只是一个多余的安全网,因为 io.ReadFull() 确实完成了这项工作,并准确地读取了您请求的数字。尽管如此,如果您查看源代码,您会被怀疑感染,正如@sepehr 上面指出的那样。 ))) 我想念的是 Read() 文档中的一些提示,说它实际上不能阅读超过请求的内容。【参考方案6】:
func (b *Reader) Read(p []byte) (n int, err error)

http://golang.org/pkg/bufio/#Reader.Read

读取的字节数将被限制为len(p)

【讨论】:

这不会“总是”读取特定数量的字节,它只会将读取的字节限制为 len(p)。 另外,它可能根本看不懂。根据此解决方案,您可能需要反复调用Read,直到获得预期的数据。并非所有的读者都是一样的。这个答案假设他们是。【参考方案7】:

将一个 n 字节大小的缓冲区传递给阅读器。

【讨论】:

这不能保证所有n 字节都会被Read() 获取。在某些导致浮动错误的边缘情况下,如果没有 EOF,它可能会更少。最好先分配精确大小的缓冲区buf := make([]byte, n),然后再分配io.ReadAtLeast(reader, buf, len(buf))

以上是关于从 golang 中的缓冲读取器读取特定数量的字节的主要内容,如果未能解决你的问题,请参考以下文章

从缓冲区读取字节(字符)

golang net/http包 http请求的字节码读取与解析。

libevent:从缓冲区读取所有字节

Golang中的channel代码示例----无缓冲有缓冲rangeclose

golang 从文件中读取字节并将其转换为字符串

JavaSE——转换流和缓冲流