在Go中使用没有File的io.WriteSeeker

orc*_*man 3 go

我正在使用第三方库来生成PDF.为了在最后编写PDF(在使用lib的API添加所有内容之后),该pdfWriter类型具有Write期望的函数io.WriteSeeker.

如果我想处理文件,这是可以的,但我需要在内存中工作.麻烦的是,我找不到任何办法 - 我发现实现的唯一本机类型io.WriteSeeker是File.

这是通过使用作品的部分Fileio.WriterWrite的功能pdfWriter:

fWrite, err := os.Create(outputPath)
if err != nil {
    return err
}

defer fWrite.Close()

err = pdfWriter.Write(fWrite)
Run Code Online (Sandbox Code Playgroud)

没有实际的文件,有没有办法做到这一点?喜欢得到[]byte什么?

icz*_*cza 8

遗憾的是io.WriteSeeker,在标准库中没有现成的内存实现解决方案.

但与往常一样,您可以随时实施自己的.这并不难.

An io.WriteSeeker是an io.Writer和an io.Seeker,所以基本上你只需要实现2个方法:

Write(p []byte) (n int, err error)
Seek(offset int64, whence int) (int64, error)
Run Code Online (Sandbox Code Playgroud)

在他们的文档中阅读这些方法的一般合同应该如何表现.

这是一个使用内存中字节slice([]byte)的简单实现.它没有针对速度进行优化,这只是一个"演示"实现.

type mywriter struct {
    buf []byte
    pos int
}

func (m *mywriter) Write(p []byte) (n int, err error) {
    minCap := m.pos + len(p)
    if minCap > cap(m.buf) { // Make sure buf has enough capacity:
        buf2 := make([]byte, len(m.buf), minCap+len(p)) // add some extra
        copy(buf2, m.buf)
        m.buf = buf2
    }
    if minCap > len(m.buf) {
        m.buf = m.buf[:minCap]
    }
    copy(m.buf[m.pos:], p)
    m.pos += len(p)
    return len(p), nil
}

func (m *mywriter) Seek(offset int64, whence int) (int64, error) {
    newPos, offs := 0, int(offset)
    switch whence {
    case io.SeekStart:
        newPos = offs
    case io.SeekCurrent:
        newPos = m.pos + offs
    case io.SeekEnd:
        newPos = len(m.buf) + offs
    }
    if newPos < 0 {
        return 0, errors.New("negative result pos")
    }
    m.pos = newPos
    return int64(newPos), nil
}
Run Code Online (Sandbox Code Playgroud)

是的,就是这样.

测试它:

my := &mywriter{}
var ws io.WriteSeeker = my

ws.Write([]byte("hello"))
fmt.Println(string(my.buf))

ws.Write([]byte(" world"))
fmt.Println(string(my.buf))

ws.Seek(-2, io.SeekEnd)
ws.Write([]byte("k!"))
fmt.Println(string(my.buf))

ws.Seek(6, io.SeekStart)
ws.Write([]byte("gopher"))
fmt.Println(string(my.buf))
Run Code Online (Sandbox Code Playgroud)

输出(在Go Playground上试试):

hello
hello world
hello work!
hello gopher
Run Code Online (Sandbox Code Playgroud)

可以改进的事情:

  • mywriter使用初始空buf切片创建一个值,但其容量很可能会覆盖结果PDF文档的大小.例如,如果您估计结果PDF大约为1 MB,请创建一个容量为2 MB的缓冲区,如下所示:
    my := &mywriter{buf: make([]byte, 0, 2<<20)}

  • mywriter.Write()需要增加容量(并且复制现有内容)时,使用更大的增量(例如,在一定程度上将当前容量加倍)可能是有利可图的,这为将来的附加预留空间并最小化重新分配.

  • 真棒的答案,谢谢。小注释:在SeekEnd情况下,您需要增加偏移量,而不是减去它。请查看stdlib的reader.go以获取参考。由于我在多个项目中都使用了此功能,因此将其制作为一个lib库,并在许可证和自述文件中给予您荣誉。签出:https://github.com/orcaman/writerseeker (3认同)