Go 在缓冲读取器中查找

Go 在缓冲读取器中查找,go,Go,我需要为现有子级创建缓冲读取器io.reader,但该读取器必须支持在已从子级读取并缓冲的数据中查找 因此,当已读取n字节时,我希望能够将读取器重置为偏移量0,然后再次读取该块 不幸的是,bufio.Reader不支持搜索 是否有支持此功能的标准读取器,或者我必须实现自己的读取器?bufio的目的是提供缓冲I/O。缓冲I/O用于性能,而不是时间旅行 您可以将数据读入一个字节片,然后使用它进一步处理。我想知道在打开的os.File中查找,然后使用bufio.Reset()是否是一个答案,这有点像,

我需要为现有子级创建缓冲读取器
io.reader
,但该读取器必须支持在已从子级读取并缓冲的数据中查找

因此,当已读取
n
字节时,我希望能够将读取器重置为偏移量
0
,然后再次读取该块

不幸的是,
bufio.Reader
不支持搜索


是否有支持此功能的标准读取器,或者我必须实现自己的读取器?

bufio的目的是提供缓冲I/O。缓冲I/O用于性能,而不是时间旅行


您可以将数据读入一个字节片,然后使用它进一步处理。

我想知道在打开的
os.File
中查找,然后使用
bufio.Reset()
是否是一个答案,这有点像,但并不理想。首先,
bufio.Reset
的文档中说它“丢弃任何缓冲数据”,但是,操作系统不也缓存最近读取的文件内容吗

第二,它在BUFIO从指定文件位置开始读取和缓冲的意义上是正确的,但是它不考虑扇区对齐:不管起始点如何,它一次缓冲整个缓冲器。因此,假设缓冲区大小(默认为4096)等于文件系统的集群大小,并且除非起点与集群对齐,否则bufio将在每次需要读取时从2个集群读取。我并不是说对性能的影响是显而易见的。事实上,通过尽可能多地提前阅读,性能可能比我对对齐的痴迷可能产生的效果要好

我认为这段代码演示了这一点,从可执行文件本身读取了几个2000字节的块:

package main

import (
    "bufio"
    "crypto/md5"
    "fmt"
    "os"
)

func readBytes(r *bufio.Reader, block []byte) {
    for i := 1; i < len(block); i++ {
        var err error
        block[i], err = r.ReadByte()
        if err != nil {
            panic(err)
        }
    }
}

func status(f *os.File, r *bufio.Reader, block []byte, what string) {
    fpos, err := f.Seek(0, os.SEEK_CUR)
    if err != nil {
        panic(err)
    }
    fmt.Printf("%s: fpos=%5d, buffered=%4d, md5=%X\n", what, fpos, r.Buffered(), md5.Sum(block))
}

func main() {
    f, err := os.Open(os.Args[0])
    if err != nil {
        panic(err)
    }
    defer func() { f.Close() }()
    r := bufio.NewReader(f)
    var block = make([]byte, 2000)
    status(f, r, block, "initial")
    readBytes(r, block)
    status(f, r, block, "block 1")
    readBytes(r, block)
    status(f, r, block, "block 2")
    readBytes(r, block)
    status(f, r, block, "block 3")
    f.Seek(2000, os.SEEK_SET) // return to start of buf1a
    r.Reset(f)
    readBytes(r, block)
    status(f, r, block, "block 2")
    readBytes(r, block)
    status(f, r, block, "block 3")
    readBytes(r, block)
    status(f, r, block, "block 4")
}

不幸的是,我需要读取的文件是相对较大的多部分文件,分块读取,这就是为什么我不能使用
字节。Reader
(需要将所有内容加载到内存中)。看来我需要使用自定义实现了。@ErikAigner:自定义实现怎么样?我也遇到了类似的情况,在将读取器传递到
CSV.NewReader()
之前,需要查看CSV数据的
io.Reader
字节流以检测分隔符。这非常令人失望,我看不出有什么理由去不去。通常,我们需要缓冲以提高性能,同时也希望能够向前寻求。这有什么问题?@wchargin Rust的
BufReader
仅实现
Seek
,如果底层
R
也实现
Seek
。Go的类型系统太弱,无法表达此约束。偷看和寻找不一样;Go's
bufio.Reader
确实有
Peek
,它可以让您尽可能地提前查看缓冲区大小(我认为)。要在围棋中寻找,如果你有一个基本的
io.Reader
,并且你不知怎么知道它也是一个
io.seek
,你总是可以丢弃
bufio.Reader
,寻找基本的
io.readseek
,然后创建一个新的
bufio.Reader
来包装它。@托马斯:说得好,谢谢;我没有意识到Go的类型系统太弱,无法表达约束。虽然对底层读卡器的seeking似乎允许您从
io.SeekStart
/
io.SeekEnd
实现
Seek
,但不允许
io.SeekCurrent
,特别是不需要使用
底层.Seek之类的东西引用两个读卡器就无法实现
ftell
(0,io.SeekCurrent)-bufReader.Buffered()
。这是部分答案,但
Seek
的一个目的是确定文件中的当前位置(
ftell(3)
在Go as
Seek(0,io.SeekCurrent)
中实现)。一个适当的缓冲读取器,但如果您只能访问基础文件,则无法执行此操作,如您的示例所示,
fpos
仅查看缓冲值。
initial: fpos=    0, buffered=   0, md5=CF40A1DE3F93B4A025409B5EFA5AA210
block 1: fpos= 4096, buffered=2096, md5=C7015DD984AB85CCCBD206BA8243647D
block 2: fpos= 4096, buffered=  96, md5=E0D75F4A6DE681316515F5CD53F0D95C
block 3: fpos= 8192, buffered=2192, md5=7961B1A889E9793344374B3022314CD0
block 2: fpos= 6096, buffered=2096, md5=E0D75F4A6DE681316515F5CD53F0D95C
block 3: fpos= 6096, buffered=  96, md5=7961B1A889E9793344374B3022314CD0
block 4: fpos=10192, buffered=2192, md5=2A2F77C23EF4651E630855D9C3AA29DE