Go embed是Go 1.16版本支持的一项功能,如果在这之前需要把资源文件内置到编译后的执行文件里面,需要采用第三方的库,比如go-bindata,需要在编译源代码之前,把需要内置的资源编译成Go代码,之后调用提供的接口获取文件。

Go 1.16版本的官方实现省去了多余的步骤,直接在代码里面调用指令,注解相应的变量:

import _ "embed"

//go:embed hello.txt
var s string
print(s)

变量的类型可以是string/[]byte/embed.FS,前两个比较好理解,对应单个文件,embed.FS则是用于多个文件。

这个指令会选择性的忽略一些文件,比如image是一个文件夹:

//go:embed image
var content embed.FS

上面代码文件夹子目录中_.开头文件或文件夹会给忽略,下面代码则会包含对应的文件。

//go:embed image/*
var content embed.FS

io/fs

io/fs这个包是本次更新引入的,包括fs.FS表示只读文件系统,还有表示文件和文件头信息的接口:

type FS interface {
    Open(name string) (File, error)
}
type File interface {
    Stat() (FileInfo, error)
    Read([]byte) (int, error)
    Close() error
}
type FileInfo interface {
    Name() string
    Size() int64
    Mode() FileMode
    ModTime() time.Time
    IsDir() bool
    Sys() interface{}
}

fs.FS并没有注明遍历目录的方法,不过提供fs.Globfs.WalkDir两个方法可以查询的遍历文件,实现的原理是尝试将fs.FS转换成fs.ReadDirFS类型或是fs.GlobFS类型。

fs.WalkDir遍历文件,目录会优先出现,之后才是目录下面的文件,不过也与对应的接口实现有关,embed.FS是有提前对文件进行排序,并且目录分隔符是/,即使在Windows系统下也是/,所以操作的时候需要统一使用filepath.ToSlash进行处理。

在部分涉及文件读写的地方,也加入了对应fs.FS相关的函数,用来调用资源:

  • template.ParseFS
  • http.FS

文件系统到io.FS接口

os.DirFS函数可以从现有的文件系统实现io.FS接口:

var root:fs.FS = os.DirFS("public")

有了上面的函数,那么我们在开发程序的时候,如果只涉及到文件的读取,而不涉及文件写入,那么应该使用fs.FS接口代替原先直接读写文件的方式,这样一来,就可以在内置资源和文件系统间来回切换。

zip.Reader 也实现了fs.FS接口,打开一个zip文件,并且当成只读文件系统调用:

	content,_ := zip.OpenReader("resources.zip")
	http.Handle("/resources/", http.StripPrefix("/resources/", http.FileServer(http.FS(content))))