打开文件并在另一个函数中创建 NewReader 后如何关闭文件?

How to close a file after opening the file and creating a NewReader in another function?

我希望 func OpenFile() 读取 gzip 文件和 bzip2 文件。其他类型我稍后再补充。

func OpenFile(name string) io.Reader{

  file, err := os.Open(name)

  if err != nil {
   log.Fatal(err)
 }

  if(strings.Contains(name, ".gz")){

    gzip, gerr := gzip.NewReader(file)
    if gerr != nil {
        log.Fatal(gerr)
    }
    return gzip

  }else if(strings.Contains(name, ".bz2")){

    bzip2 := bzip2.NewReader(file)
    return bzip2    

  }else{
    return file     
  } 
}

我在另一个函数 A 中调用了 OpenFile():

    in := OpenFile(p)

    for _, d := range fdb.Detect(in) {
        set[d] = true
        counter++
    }
    ...

我的问题是,如果我在 OpenFile() 中使用 "defer file.Close()",文件会过早关闭,所以我无法获得任何输入值。如何关闭A中的文件?

请注意 gzip.NewReader(file) 和 bzip2.NewReader(file) return 不同的界面。

gzip: func NewReader(r io.Reader) (*Reader, error) // Reader 有一个 func Close()

bzip2: func NewReader(r io.Reader) io.Reader // io.Reader 没有 func Close()

这就是我无法 return NewReader(file) 首先的原因。

谢谢!

在这种情况下,因为 bzip2.NewReader() 不是 return 和 io.ReadCloser 那么 Andy 的答案应该是可以接受的。

但是,我的原始答案针对的是一般情况:

您可能想要 return io.ReadCloser 而不是 io.Reader - 这样该函数的使用者就可以调用 Close()

os.Open() 编写的 os.File return 满足 io.ReadCloser 所以唯一要更改的是您的 OpenFile()函数。

返回 io.ReadCloser 是惯用的,也是执行此操作的首选方法。它告诉调用者在完成 reader.

后应该调用 Close

另一个选项是return两个参数,reader和一个close函数。这就是 context.WithDeadline 和 context.WithTimeout 所做的:

func OpenFile(name string) (r io.Reader, close func() error) {
    // ...
    var file *os.File
    gzip, _ := gzip.NewReader(file)

    return gzip, func() error { return file.Close() }
}

这可能会使 OpenFile 更简单(因为您不必创建任何包装器类型),但在我看来,它在调用端有点笨拙。

正如其他人所提到的,您应该从您的函数中 return 一个 io.ReadCloser。由于 bzip2.NewReader() 的 return 值不满足 io.ReadCloser,您需要创建自己的类型。

type myFileType struct {
    io.Reader
    io.Closer
}

func OpenFile(name string) io.ReadCloser {

    file, err := os.Open(name)

    if err != nil {
        log.Fatal(err)
    }

    if strings.Contains(name, ".gz") {

        gzip, gerr := gzip.NewReader(file)
        if gerr != nil {
            log.Fatal(gerr)
        }
        return gzip

    } else if strings.Contains(name, ".bz2") {

        bzip2 := bzip2.NewReader(file)
        return myFileType{bzip2, file}

    } else {
        return file
    }
}