标题

  • 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 表示数据存储为二进制格式, 如果只使用 []bytestring 需要以 import (_ "embed") 的形式引入 embed 标准库
string 表示数据被编码成 utf8 编码的字符串, 因此不要用这个格式嵌入二进制文件比如图片, 引入 embed 的规则同 []byte
embed.FS 表示存储多个文件和目录的结构, []bytestring 只能存储单个文件

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.htmlt3.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 . livego run .

然后在浏览器中运行 http://localhost:8888 默认显示 static 目录下的 index.html 文件内容。

当然, 运行 go run . livego run . 的不同之处在于编译后的二进制程序文件在运行过程中是否依赖 static 目录中的静态文件资源。

以下为验证步骤:

首先, 使用编译到二进制文件的方式。

若文件内容改变, 输出依然是改变前的内容, 说明 embed 嵌入的文件内容在编译后不再依赖于原有静态文件了。

  1. 运行 go run .
  2. 修改 index.html 文件内容为 Hello China
  3. 浏览器输入 http://localhost:8888 查看输出。输出内容为修改之前的 Hello World

其次, 使用普通的文件方式。

若文件内容改变, 输出的内容也改变, 说明编译后依然依赖于原有静态文件。

  1. go run . live
  2. 修改 index.html 文件内容为 delete
  3. 浏览器输入 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 包及其使用相关推荐

  1. go每日新闻(2021-02-02)——Go1.16 新特性:一文快速上手 Go embed

    每日一谚:The Go analogue: goroutines connected by channels just like unix pipes style. go中文网每日资讯–2021-02 ...

  2. JDK 16 新特性,正式发布!程序员:追不上了...

    点击"开发者技术前线",选择"星标????" 让一部分开发者看到未来 地址:https://blog.csdn.net/csdnnews/article/det ...

  3. Java 16 新特性:record类

    以前我们定义类都是用class关键词,但从Java 16开始,我们将多一个关键词record,它也可以用来定义类.record关键词的引入,主要是为了提供一种更为简洁.紧凑的final类的定义方式. ...

  4. Java 16 新特性介绍

    本文要点 Java 16 和即将发布的 Java 17 引入了大量特性和语言增强,有助于提高开发人员的生产力和应用程序性能 Java 16 Stream API 为常用的终端操作提供了很多新方法,有助 ...

  5. golang1.16新特性速览

    目录 语言內建的资源嵌入支持 支持arm64 go modules的新特性 GO111MODULE现在默认为on go build不再更改mod相关文件 go install的变化 新的GOVCS环境 ...

  6. 快速了解 Java 9 - 16 新特性,助你脱离内卷

    点击上方 "编程技术圈"关注, 星标或置顶一起成长 后台回复"大礼包"有惊喜礼包! 每日英文 Each fall you take, makes you str ...

  7. 卷不动了?300 秒快速了解 Java 9 - 16 新特性,助你脱离内卷

    点击下方"IT牧场",选择"设为星标" 来源 | https://juejin.cn/post/6964543834747322405 JAVA 这几年的更新实 ...

  8. Java 8 golang 1.8_Java8 新特性(一) - Lambda

    Java8 新特性(一) - Lambda 近些日子一直在使用和研究 golang,很长时间没有关心 java 相关的知识,前些天看到 java9 已经正式发布,意识到自己的 java 知识已经落后很 ...

  9. Go1.16 新特性:一文快速上手 Go embed

    大家好,我是正在沉迷学习煎鱼的煎鱼. 在以前,很多从其他语言转过来 Go 语言的同学会问到,或是踩到一个坑.就是以为 Go 语言所打包的二进制文件中会包含配置文件的联同编译和打包. 结果往往一把二进制 ...

  10. JDK 16 新特性,正式发布!程序员:追不上了……

    感谢关注趣学程序!公众号内部回复666获取热门教程 来源:CSDN资讯 blog.csdn.net/csdnnews/article/details/110483909 是的,你确实没有看错,JDK1 ...

最新文章

  1. 【转载】Linux系统与性能监控
  2. 主板噪音测试软件,工作噪音测试 - 三英战吕布?四款300元热门电源横评 - 超能网...
  3. 重新精读《Java 编程思想》系列之组合与继承
  4. rawquery 没扎到返回什么_当mysql_query返回false时
  5. php把字符串转为utf-8
  6. windowskb2685811补丁_KB898461补丁
  7. 大型ERP等数据库系统常见几种设计------(转)
  8. 如何修改7 服务器配置,centos7修改服务器配置
  9. sublime ctrl b突然不能用解决方法
  10. 笔记本linux版刚买回来怎么检查,新电脑买回来要怎么做
  11. 计算机组成原理(微课版)谭志虎pdf资源
  12. 使用c++实现一个FTP客户端(一)
  13. 梦想CAD控件 2021.12.06更新,网页浏览编辑CAD,CAD插件
  14. Flex TLF 相关知识
  15. Week 3: 边下边播完整性校验作业
  16. 瑞芯微PX30芯片参数和处理器介绍
  17. mac Error: ENOENT: no such file or directory, stat ‘/.VolumeIcon.icns
  18. 《A CMOS Time-to-Digital Converter With BetterThan 10ps Single-Shot Precision》论文阅读
  19. mac 配置mysql
  20. 华为路由器:清除配置

热门文章

  1. Android编译时冲突报错的完美解决方案
  2. 【贪玩巴斯】数字图像处理基础课堂笔记(四)——「Matlab中的代码优化问题、meshgrid函数和交互式I/O」 2021-10-11
  3. 浅谈智慧校园建设中存在的问题及解决方案
  4. 推荐16个高清图片网站,可做网站背景
  5. 论文笔记:Stacked Hourglass Networks for Human Pose Estimation
  6. 131多机型解码擦除工具
  7. ADS1115驱动程序
  8. U-BLOX GPS 模块及GPRMC指令解析
  9. mysql查询这一周数据库_MYSQL查询一周,一月内的数据
  10. JS获取下个月的第一天和最后一天