在 golang 中从缓冲读取器(buffered reader)中读取特定字节数的内容

42

我知道来自于 bufio 包的特定函数在 Golang 中的作用。

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

Peek方法可以返回接下来的n个字节,但不会使读取器前进。这些字节在下一次读取调用时将变得无效。如果Peek返回少于n个字节,则它还会返回一个错误,解释为什么读取是不完整的。如果n大于b的缓冲区大小,则该错误是ErrBufferFull。

我需要从Reader中读取一定数量的字节并使读取器前进。基本上与上面的函数相同,但它会推进读取器。有人知道如何实现吗?

7个回答

88

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

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

即使阅读器已缓冲,这也可以起作用。


6
这应该是被接受的答案。它可以消除“短”读取时的困扰,即不需要循环和检查io.EOF等情况。文档中也有一个很好的例子:https://golang.org/pkg/io/#ReadFull - colm.anseo
7
也许值得一提的是,io.ReadFull只是对以下调用的包装器:io.ReadAtLeast(reader, p, len(p))同时,在使用io.ReadFull时,你应该先定义p,并使其长度等于你想读取的字节数,但在使用io.ReadAtLeast时,p的长度可以随意设置,只要它比你想读取的字节数大或相等就行。 - sepehr
@sepehr 是正确的。问题的措辞是“特定字节数”,即如果我理解英文正确,那就等于“确切的字节数”。此外,如果您想在流上实现消息定界,则这是一个非常实际的问题。“确切”的或“特定”的并不意味着“大于或等于”。如果我需要正好4个字节,那么如果读取了400字节怎么办?问题就是关于这个的,具体而言。 - latitov
实际上,io.LimitReader 只有在你想限制一些你无法控制的代码时才有用,例如一些外部库代码。如果是你自己的代码,那么它就没有用处,因为它只调用了一个普通的 Read() _once_,无论如何都不能读取比你请求的更多。现在人们正在问的问题,我也在试图找到答案:到底在哪里说了读取不能超过请求的数量?答案是:没有地方。在 Linux 文档中,在底层,他们说“通常”和“预期”。Go 中的任何内容都没有提到。可能对于 Google 的人来说,这太明显了? - latitov
我的意思是,在其他地方,人们建议使用LimitReader来限制读取的最大字节数。实际上,那只是一个多余的安全网,因为io.ReadFull()实际上已经完成了工作,并且准确地读取了您请求的数量。但是,如果您查看源代码,就会感到怀疑,正如@sepehr在上面指出的那样。我错过的是在Read()文档中的一些提示,说明它实际上不能读取超过请求的内容。 - latitov

17

22
这不会始终读取特定数量的字节,它只会限制读取的字节数为len(p)。 - dustinevan
同时,它可能根本无法读取。 根据此解决方案,您可能需要多次调用 Read 直到获得预期数据。 并非所有读取器都是相同的。 此答案假定它们是相同的。 - Inanc Gumus

9

简而言之:

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 相比,使用 io.LimitReader 的一个优点是您无需手动创建 buf,其中 len(buf) 是您想要读取的字节数,然后在 Read 时将 buf 作为参数传递。
相反,您只需告诉 io.LimitReader 您最多从 myReader 中读取 42 字节,并调用 ioutil.ReadAll 读取它们所有,将结果作为字节切片返回。如果成功,返回的切片长度保证为 42。

1
关于您最后一段的内容,切片不能保证长度为42个字节。io.Reader可能会返回比请求的字节数更少的字节。ReadFull将尝试读取您想要的尽可能多的字节,而LimitReader将限制您要读取的字节数。ReadFull简化了当您真正想读取N个字节时的处理情况。 - Inanc Gumus
...否则它将返回一个错误(这是一件好事)。+ ReadAll可能会创建比您需要的更大的缓冲区。 - Inanc Gumus
这必须是一种被接受的方法。也许在结尾稍微改一下措辞会更好,但除此之外它解决了问题并回答了这个问题。我想知道为什么没有一个直接的函数来做这件事。感谢@navigaid。 - latitov
实际上,io.LimitReader只有在您想限制一些您无法控制的代码(例如某些外部库代码)时才有用。如果是您自己的代码,则没有用处,因为它只调用了一个普通的Read() _once_,而无论如何都不能读取比您请求的更多。现在人们正在问的问题,我也试图找到答案:究竟在哪里说不能读取超过请求的内容?答案是:没有地方。在Linux文档中,底层级别中,他们说“通常”和“预期”。Go中的任何内容都没有说出一句话。也许对于谷歌的员工来说,这太过显而易见了?)))) - latitov

3

我更倾向于使用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))
}

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

2
这并不保证Read()函数一定会读取所有的n字节。在某些边缘情况下,如果没有EOF,可能会导致浮点错误。最好分配一个确切大小的缓冲区buf := make([]byte, n),然后使用io.ReadAtLeast(reader, buf, len(buf))函数。 - Denis

0

如果你想从一个 io.Reader 中读取字节并写入到一个 io.Writer 中,那么你可以使用 io.CopyN

CopyN 从 src 复制 n 个字节(或直到出现错误)到 dst。它返回复制的字节数以及在复制过程中遇到的最早错误。

返回时,只有当 err == nil 时 written 才等于 n。

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

-1

要做到这一点,您只需要创建一个字节切片,并使用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中,并返回读入的字节数。 这些字节最多可以从底层Reader的一个Read操作中获取, 因此,实际读取的字节数可能小于len(p)


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