Golang 1.16 新特性-embed 包及其使用
标题
- 1. Golang 1.16 新特性-embed 包及其使用
- 1.1. embed 是什么
- 1.2. 为什么需要 embed 包
- 1.3. embed 的常用场景
- 1.4. embed 的基本用法
- 1.5. embed 例子
- 1.6. 软链接&硬链接
- 1.7. 将文件目录结构映射成 embed.FS 文件类型
- 1.8. 读取单个文件
- 1.9. 读取多个文件
- 1.10. 嵌入多个目录
- 1.11. 按正则嵌入匹配目录或文件
- 1.12. 在 http web 中的使用
- 1.13. 在模板中的应用
- 1.14. Gin 静态文件服务
- 1.15. Gin HTML 模板
- 1.16. embed 的使用实例-一个简单的静态 web 服务
- 1.17. embed 使用中注意事项
1. Golang 1.16 新特性-embed 包及其使用
1.1. embed 是什么
embed
是在 Go 1.16 中新加入的包。它通过 //go:embed
指令, 可以在编译阶段将静态资源文件打包进编译好的程序中, 并提供访问这些文件的能力。
1.2. 为什么需要 embed 包
在以前, 很多从其他语言转过来 Go 语言的同学会问到, 或者踩到一个坑。就是以为 Go 语言所打包的二进制文件中会包含配置文件的联同编译和打包。
结果往往一把二进制文件挪来挪去, 就无法把应用程序运行起来了。因为无法读取到静态文件的资源。
无法将静态资源编译打包二进制文件的话, 通常会有两种解决方法:
- 第一种是识别这类静态资源, 是否需要跟着程序走。
- 第二种就是将其打包进二进制文件中。
第二种情况的话, Go 以前是不支持的, 大家就会借助各种花式的开源库, 例如: go-bindata/go-bindata
来实现。
但是在 Go1.16 起, Go 语言自身正式支持了该项特性。
它有以下优点
- 能够将静态资源打包到二进制包中, 部署过程更简单。传统部署要么需要将静态资源与已编译程序打包在一起上传, 或者使用 docker 和 dockerfile 自动化前者, 这是很麻烦的。
- 确保程序的完整性。在运行过程中损坏或丢失静态资源通常会影响程序的正常运行。
- 静态资源访问没有 io 操作, 速度会非常快。
1.3. embed 的常用场景
- Go 模版: 模版文件必须可用于二进制文件(模版文件需要对二进制文件可用)。对于 Web 服务器二进制文件或那些通过提供 init 命令的 CLI 应用程序, 这是一个相当常见的用例。在没有嵌入的情况下, 模版通常内联在代码中。
- 静态 web 服务: 有时, 静态文件(如 index.html 或其他 HTML, JavaScript 和 CSS 文件之类的静态文件)需要使用 golang 服务器二进制文件进行传输, 以便用户可以运行服务器并访问这些文件。
- 数据库迁移: 另一个使用场景是通过嵌入文件被用于数据库迁移脚本。
1.4. embed 的基本用法
embed 包是 golang 1.16 中的新特性, 所以, 请确保你的 golang 环境已经升级到了 1.16 版本。
Go embed 的使用非常简单, 首先导入 embed
包, 再通过 //go:embed
文件名 将对应的文件或目录结构导入到对应的变量上。
特别注意:
embed
这个包一定要导入, 如果导入不使用的话, 使用_
导入即可。
嵌入的这个基本概念是通过在代码里添加一个特殊的注释实现的, Go 会根据这个注释知道要引入哪个或哪几个文件。注释的格式是:
//go:embed FILENAME(S)
FILENAME 可以是 string 类型也可以是 []byte
类型, 取决于你引入的是单个文件、还是 embed.FS
类型的一组文件。go:embed
命令可以识别 Go 的文件格式, 比如 files/*.html
这种文件格式也可以识别到(但要注意不要写成 **/*.html
这种递归的匹配规则)。
文件格式 https://pkg.go.dev/path#Match
可以看下官方文档的说明。https://golang.org/pkg/embed/
embed
可以嵌入的静态资源文件支持三种数据类型: 字符串、字节数组、embed.FS
文件类型
数据类型 | 说明 |
---|---|
[]byte
|
表示数据存储为二进制格式, 如果只使用 []byte 和 string 需要以 import (_ "embed") 的形式引入 embed 标准库
|
string
|
表示数据被编码成 utf8 编码的字符串, 因此不要用这个格式嵌入二进制文件比如图片, 引入 embed 的规则同 []byte
|
embed.FS
|
表示存储多个文件和目录的结构, []byte 和 string 只能存储单个文件
|
1.5. embed 例子
例如: 在当前目录下新建文件 version.txt
, 并在文件中输入内容: 0.0.1
将文件内容嵌入到字符串变量中
package mainimport (_ "embed""fmt"
)//go:embed version.txt
var version stringfunc main() {fmt.Printf("version: %q\n", version)
}
当嵌入文件名的时候, 如果文件名包含空格, 则需要用引号将文件名括起来。如下, 假设文件名是 “version info.txt”, 如下代码第 8 行所示:
package mainimport (_ "embed""fmt"
)//go:embed "version info.txt"
var version stringfunc main() {fmt.Printf("version: %q\n", version)
}
将文件内容嵌入到字符串或字节数组类型变量的时候, 只能嵌入 1 个文件, 不能嵌入多个文件, 并且文件名不支持正则模式, 否则运行代码会报错
如代码第 8 行所示:
package mainimport (_ "embed""fmt"
)//go:embed version.txt info.txt
var version stringfunc main() {fmt.Printf("version %q\n", version)
}
运行代码, 得到错误提示:
sh-3.2# go run .
# demo
./main.go:8:5: invalid go:embed: multiple files for type string
1.6. 软链接&硬链接
嵌入指令是否支持嵌入文件的软链接呢? 如下: 在当前目录下创建一个指向 version.txt
的软链接 v
ln -s version.txt v
package mainimport (_ "embed""fmt"
)
//go:embed v
var version string
func main() {fmt.Printf("version %q\n", version)
}
运行程序, 得到不能嵌入软链接文件的错误:
sh-3.2# go run .# demomain.go:8:12: pattern v: cannot embed irregular file vsh-3.2#
结论: //go:embed
指令不支持文件的软链接
让我们再来看看文件的硬链接, 如下:
sh-3.2# rm v
sh-3.2# ln version.txt h
import (_ "embed""fmt"
)
//go:embed v
var version stringfunc main() {fmt.Printf("version %q\n", version)
}
运行程序, 能够正常运行并输出, 如下:
sh-3.2# go run .version 0.0.1
结论: //go:embed
指令支持文件的硬链接。因为硬链接本质上是源文件的一个拷贝。
我们能不能将嵌入指令用于 初始化的变量呢? 如下:
package mainimport (_ "embed""fmt"
)//go:embed v
var version string = ""func main() {fmt.Printf("version %q\n", version)
}
运行程序, 得到 error 结果:
sh-3.2# go run ../main.go:12:3: go:embed cannot apply to var with initializersh-3.2#
结论: 不能将嵌入指令用于已经初始化的变量上。
将文件内容嵌入到字节数组变量中
package mainimport (_ "embed""fmt"
)
//go:embed version.txt
var versionByte []bytefunc main() {fmt.Printf("version %q\n", string(versionByte))
}
1.7. 将文件目录结构映射成 embed.FS 文件类型
使用 embed.FS
类型, 可以读取一个嵌入到 embed.FS
类型变量中的目录和文件树, 这个变量是只读的, 所以是线程安全的。
embed.FS
结构主要有 3 个对外方法, 如下:
// Open 打开要读取的文件, 并返回文件的 fs.File 结构。
func (f FS) Open(name string) (fs.File, error)// ReadDir 读取并返回整个命名目录
func (f FS) ReadDir(name string) ([]fs.DirEntry, error)// ReadFile 读取并返回 name 文件的内容。
func (f FS) ReadFile(name string) ([]byte, error)
1.8. 读取单个文件
package mainimport ("embed""fmt""log"
)//go:embed "version.txt"
var f embed.FSfunc main() {data, err := f.ReadFile("version.txt")if err != nil {log.Fatal(err)}fmt.Println(string(data))
}
1.9. 读取多个文件
首先, 在项目根目录下建立 templates
目录, 以及在 templates
目录下建立多个文件, 如下:
|-templates
|-—— t1.html
|——— t2.html
|——— t3.html
package mainimport ("embed""fmt""io/fs"
)//go:embed templates/*
var files embed.FSfunc main() {templates, _ := fs.ReadDir(files, "templates")//打印出文件名称for _, template := range templates {fmt.Printf("%q\n", template.Name())}
}
1.10. 嵌入多个目录
通过使用多个 //go:embed
指令, 可以在同一个变量中嵌入多个目录。我们在项目根目录下再创建一个 cpp
目录, 在该目录下添加几个示例文件名。如下:
|-cpp
|——— cpp1.cpp
|——— cpp2.cpp
|——— cpp3.cpp
如下代码, 第 9、10 行所示:
package mainimport ("embed""fmt""io/fs"
)//go:embed templates/*
//go:embed cpp/*
var files embed.FSfunc main() {templates, _ := fs.ReadDir(files, "templates")//打印出文件名称for _, template := range templates {fmt.Printf("%q\n", template.Name())}cppFiles, _ := fs.ReadDir(files, "cpp")for _, cppFile := range cppFiles {fmt.Printf("%q\n", cppFile.Name())}
}
1.11. 按正则嵌入匹配目录或文件
只读取 templates
目录下的 txt
文件, 如下代码第 9 行所示:
package mainimport ("embed""fmt""io/fs"
)//go:embed templates/*.txt
var files embed.FSfunc main() {templates, _ := fs.ReadDir(files, "templates")//打印出文件名称for _, template := range templates {fmt.Printf("%q\n", template.Name())}
}
只读取 templates
目录下的 t2.html
和 t3.html
文件, 如下代码第 9 行所示:
package mainimport ("embed" "fmt" "io/fs")//go:embed templates/t[2-3].txtvar files embed.FSfunc main() {templates, _ := fs.ReadDir(files, "templates")//打印出文件名称for _, template := range templates {fmt.Printf("%q\n", template.Name()) }}
1.12. 在 http web 中的使用
package mainimport ("embed""net/http"
)//go:embed static
var static embed.FSfunc main() {http.ListenAndServe(":8080", http.FileServer(http.FS(static)))
}
http.FS
这个函数, 把 embed.FS
类型的 static 转换为 http.FileServer
函数可以识别的 http.FileSystem
类型。
1.13. 在模板中的应用
package mainimport ("embed""html/template""net/http"
)//go:embed templates
var tmpl embed.FSfunc main() {t, _ := template.ParseFS(tmpl, "templates/*.tmpl")http.HandleFunc("/", func(rw http.ResponseWriter, r *http.Request) {t.ExecuteTemplate(rw,"index.tmpl",map[string]string{"title":"Golang Embed 测试"})})http.ListenAndServe(":8080",nil)
}
template
包提供了 ParseFS
函数, 可以直接从一个 embed.FS
中加载模板, 然后用于 HTTP Web 中。模板文件夹的结构如下所示:
templates
└── index.tmpl
1.14. Gin 静态文件服务
package mainimport ("embed""github.com/gin-gonic/gin""net/http"
)//go:embed static
var static embed.FSfunc main() {r:=gin.Default()r.StaticFS("/",http.FS(static))r.Run(":8080")
}
在 Gin 中使用 embed
作为静态文件, 也是用过 http.FS
函数转化的。
1.15. Gin HTML 模板
package mainimport ("embed""github.com/gin-gonic/gin""html/template"
)//go:embed templates
var tmpl embed.FS//go:embed static
var static embed.FSfunc main() {r:=gin.Default()t, _ := template.ParseFS(tmpl, "templates/*.tmpl")r.SetHTMLTemplate(t)r.GET("/", func(ctx *gin.Context) {ctx.HTML(200,"index.tmpl",gin.H{"title":"Golang Embed 测试"})})r.Run(":8080")
和前面的模板例子一样, 也是通过 template.ParseFS
函数先加载 embed
中的模板, 然后通过 Gin 的 SetHTMLTemplate
设置后就可以使用了。
http.FS
函数是一个可以把embed.FS
转为http.FileSystem
的工具函数
1.16. embed 的使用实例-一个简单的静态 web 服务
以下搭建一个简单的静态文件 web 服务为例。在项目根目录下建立如下静态资源目录结构
|-static
|---js
|------util.js
|---img
|------logo.jpg
|---index.html
package mainimport ("embed" "io/fs" "log" "net/http" "os"
)func main() {useOS := len(os.Args) > 1 && os.Args[1] == "live" http.Handle("/", http.FileServer(getFileSystem(useOS))) http.ListenAndServe(":8888", nil)
}//go:embed static
var embededFiles embed.FSfunc getFileSystem(useOS bool) http.FileSystem {if useOS {log.Print("using live mode") return http.FS(os.DirFS("static")) } log.Print("using embed mode") fsys, err := fs.Sub(embededFiles, "static") if err != nil {panic(err) } return http.FS(fsys)}
以上代码, 分别执行 go run . live
和 go run .
然后在浏览器中运行 http://localhost:8888 默认显示 static
目录下的 index.html
文件内容。
当然, 运行 go run . live
和 go run .
的不同之处在于编译后的二进制程序文件在运行过程中是否依赖 static
目录中的静态文件资源。
以下为验证步骤:
首先, 使用编译到二进制文件的方式。
若文件内容改变, 输出依然是改变前的内容, 说明 embed
嵌入的文件内容在编译后不再依赖于原有静态文件了。
- 运行
go run .
- 修改
index.html
文件内容为Hello China
- 浏览器输入
http://localhost:8888
查看输出。输出内容为修改之前的Hello World
其次, 使用普通的文件方式。
若文件内容改变, 输出的内容也改变, 说明编译后依然依赖于原有静态文件。
go run . live
- 修改
index.html
文件内容为delete
- 浏览器输入
http://localhost:8888
查看输出。输出修改后的内容:Hello China
1.17. embed 使用中注意事项
在使用
//go:embed
指令的文件都需要导入embed
包。
例如, 以下例子没有导入 embed
包, 则不会正常运行 。
package mainimport ("fmt"
)//go:embed file.txt
var s stringfunc main() {fmt.Print(s)
}
//go:embed
指令只能用在包一级的变量中, 不能用在函数或方法级别, 像以下程序将会报错, 因为第 10 行的变量作用于属于函数级别:
package mainimport (_ "embed" "fmt"
)func main() {//go:embed file.txtvar s string fmt.Print(s)
}
当包含目录时, 它不会包含以 “.” 或 “_” 开头的文件。
但是如果使用通配符, 比如 dir/*
, 它将包含所有匹配的文件, 即使它们以 “.” 或 “_” 开头。请记住, 在您希望在 Web 服务器中嵌入文件但不允许用户查看所有文件的列表的情况下, 包含 Mac OS 的 .DS_Store
文件可能是一个安全问题。出于安全原因, Go 在嵌入时也不会包含符号链接或上一层目录。
Golang 1.16 新特性-embed 包及其使用相关推荐
- go每日新闻(2021-02-02)——Go1.16 新特性:一文快速上手 Go embed
每日一谚:The Go analogue: goroutines connected by channels just like unix pipes style. go中文网每日资讯–2021-02 ...
- JDK 16 新特性,正式发布!程序员:追不上了...
点击"开发者技术前线",选择"星标????" 让一部分开发者看到未来 地址:https://blog.csdn.net/csdnnews/article/det ...
- Java 16 新特性:record类
以前我们定义类都是用class关键词,但从Java 16开始,我们将多一个关键词record,它也可以用来定义类.record关键词的引入,主要是为了提供一种更为简洁.紧凑的final类的定义方式. ...
- Java 16 新特性介绍
本文要点 Java 16 和即将发布的 Java 17 引入了大量特性和语言增强,有助于提高开发人员的生产力和应用程序性能 Java 16 Stream API 为常用的终端操作提供了很多新方法,有助 ...
- golang1.16新特性速览
目录 语言內建的资源嵌入支持 支持arm64 go modules的新特性 GO111MODULE现在默认为on go build不再更改mod相关文件 go install的变化 新的GOVCS环境 ...
- 快速了解 Java 9 - 16 新特性,助你脱离内卷
点击上方 "编程技术圈"关注, 星标或置顶一起成长 后台回复"大礼包"有惊喜礼包! 每日英文 Each fall you take, makes you str ...
- 卷不动了?300 秒快速了解 Java 9 - 16 新特性,助你脱离内卷
点击下方"IT牧场",选择"设为星标" 来源 | https://juejin.cn/post/6964543834747322405 JAVA 这几年的更新实 ...
- Java 8 golang 1.8_Java8 新特性(一) - Lambda
Java8 新特性(一) - Lambda 近些日子一直在使用和研究 golang,很长时间没有关心 java 相关的知识,前些天看到 java9 已经正式发布,意识到自己的 java 知识已经落后很 ...
- Go1.16 新特性:一文快速上手 Go embed
大家好,我是正在沉迷学习煎鱼的煎鱼. 在以前,很多从其他语言转过来 Go 语言的同学会问到,或是踩到一个坑.就是以为 Go 语言所打包的二进制文件中会包含配置文件的联同编译和打包. 结果往往一把二进制 ...
- JDK 16 新特性,正式发布!程序员:追不上了……
感谢关注趣学程序!公众号内部回复666获取热门教程 来源:CSDN资讯 blog.csdn.net/csdnnews/article/details/110483909 是的,你确实没有看错,JDK1 ...
最新文章
- 【转载】Linux系统与性能监控
- 主板噪音测试软件,工作噪音测试 - 三英战吕布?四款300元热门电源横评 - 超能网...
- 重新精读《Java 编程思想》系列之组合与继承
- rawquery 没扎到返回什么_当mysql_query返回false时
- php把字符串转为utf-8
- windowskb2685811补丁_KB898461补丁
- 大型ERP等数据库系统常见几种设计------(转)
- 如何修改7 服务器配置,centos7修改服务器配置
- sublime ctrl b突然不能用解决方法
- 笔记本linux版刚买回来怎么检查,新电脑买回来要怎么做
- 计算机组成原理(微课版)谭志虎pdf资源
- 使用c++实现一个FTP客户端(一)
- 梦想CAD控件 2021.12.06更新,网页浏览编辑CAD,CAD插件
- Flex TLF 相关知识
- Week 3: 边下边播完整性校验作业
- 瑞芯微PX30芯片参数和处理器介绍
- mac Error: ENOENT: no such file or directory, stat ‘/.VolumeIcon.icns
- 《A CMOS Time-to-Digital Converter With BetterThan 10ps Single-Shot Precision》论文阅读
- mac 配置mysql
- 华为路由器:清除配置
热门文章
- Android编译时冲突报错的完美解决方案
- 【贪玩巴斯】数字图像处理基础课堂笔记(四)——「Matlab中的代码优化问题、meshgrid函数和交互式I/O」 2021-10-11
- 浅谈智慧校园建设中存在的问题及解决方案
- 推荐16个高清图片网站,可做网站背景
- 论文笔记:Stacked Hourglass Networks for Human Pose Estimation
- 131多机型解码擦除工具
- ADS1115驱动程序
- U-BLOX GPS 模块及GPRMC指令解析
- mysql查询这一周数据库_MYSQL查询一周,一月内的数据
- JS获取下个月的第一天和最后一天