go:embed 在 Go 開發中的應用與最佳實踐
背景
在使用 Go 開發命令行工具或桌面軟件時,將配置文件、模板,甚至整個前端應用直接嵌入到 Go 二進制文件中是一種提高應用部署效率和簡化操作的有效方法。這種方法可以減少外部依賴,讓應用在沒有額外資源文件的情況下也能獨立運行,特別適合需要便捷分發和部署的場景。自 Go 1.16 版本起,Go 語言官方引入了 //go:embed 指令,使得嵌入靜態資源變得異常簡單而直接。這一新特性大大簡化了開發流程,使開發者能夠更加專注於核心功能的實現。
go:embed
介紹
go:embed
是一個編譯器指令,能夠在程序編譯時期將任意文件和目錄嵌入到 Go 的二進制文件中。通過這種方式,開發者可以在不依賴外部文件的情況下,直接在代碼中訪問這些資源,從而實現了高效、靈活的文件管理。
特點
-
文件嵌入類型:對於單個文件,go:embed 支持將其嵌入爲字符串 (string) 或字節切片 ([]byte);對於多個文件或目錄,支持將其嵌入爲新的文件系統 (embed.FS)。
-
靈活性:即使嵌入文件的變量未被顯式使用,只要導入了 "embed" 包,文件仍然會被嵌入。
-
語法要求:go:embed 指令必須緊跟在要嵌入文件的變量聲明之前,並且只能嵌入爲 string、[]byte 或 embed.FS 三種類型的變量,其他別名或自定義類型(如 type S string)則不支持。
使用方法
go:embed
的使用非常簡單,以下是幾種常見的用法:
import "embed"
//go:embed mobile.txt
var mobile string
//go:embed hello.txt
var contentBytes []byte
//go:embed hello.txt
var fileFS embed.FS
var data, _ = fileFS.ReadFile("hello.txt")
如上所示,直接在註釋中使用 go:embed 指令,就可以將文件嵌入到程序中。
嵌入爲字符串
將文件內容嵌入爲字符串的方式適合處理文本數據,如配置文件、模板或其他小型文件。
package main
import (
_ "embed"
"fmt"
)
//go:embed pfinalclub.txt
var pfinal string
func main() {
fmt.Println(pfinal)
}
嵌入爲 byte slice
將文件內容嵌入爲字節切片適合處理二進制數據,如圖片、字體或其他非文本數據。
package main
import (
_ "embed"
"fmt"
)
//go:embed pfinalclub.txt
var pfinal []byte
func main() {
fmt.Println(pfinal)
}
嵌入爲 embed.FS
將多個文件或整個目錄嵌入爲文件系統 (embed.FS) 適合需要訪問多個文件的場景,如嵌入靜態網站的所有資源。
package main
import (
_ "embed"
"embed"
"fmt"
)
//go:embed pfinalclub.txt
var pfinal embed.FS
func main() {
data, _ := pfinal.ReadFile("pfinalclub.txt")
fmt.Println(string(data))
}
多文件嵌入
有時候, 要嵌入多個文件, go:embed 支持同一個變量上多個 go:embed 指令 (嵌入爲 string 或者 byte slice 是不能有多個 go:embed 指令的):
//go:embed pfinal.txt
//go:embed pfinalclub.txt
var pfinal embed.FS
func main() {
data, _ := f.ReadFile("pfinal.txt")
fmt.Println(string(data))
data, _ = f.ReadFile("pfinalclub.txt")
fmt.Println(string(data))
}
根據不同的需求,可以選擇最適合的嵌入方式:
-
對於第 1 種用法,將文件嵌入到 string 中,適合嵌入單個文件(如配置數據、模板文件或一段文本)。
-
對於第 2 種用法,將文件嵌入到 []byte 中,適合嵌入單個文件(如二進制文件:圖片、字體或其他非文本數據)。
-
對於第 3 種用法,將文件嵌入到 embed.FS 中,適合嵌入多個文件或整個目錄(embed.FS 是一個只讀的虛擬文件系統)。
注意事項
只讀限制
嵌入的內容在運行時是隻讀的,即在編譯期嵌入文件的內容是什麼,運行時讀取到的內容也將保持不變。embed.FS 提供了文件系統相關的基本操作,如打開和讀取文件,但不支持寫操作,這使得其在多線程環境中是安全的,多個 goroutine 可以併發訪問 embed.FS 實例而不會產生競爭條件。
type FS
func (f FS) Open(name string) (fs.File, error)
func (f FS) ReadDir(name string) ([]fs.DirEntry, error)
func (f FS) ReadFile(name string) ([]byte, error)
文件模式匹配
在 go:embed
指令中,可以只寫文件夾名,此時該文件夾中除了以 . 和 _ 開頭的文件和文件夾外,其他文件都會被嵌入,並且子文件夾也會被遞歸嵌入,形成一個完整的文件系統。
如果需要嵌入以 . 和 _ 開頭的文件或文件夾(如 .hello.txt),則需要使用通配符 ,如 go:embed p/。但請注意,* 不具有遞歸性,子文件夾下的 . 和 _ 開頭的文件不會被嵌入,除非在子文件夾中單獨使用通配符進行嵌入。
//go:embed pfinal/*
var pfinal embed.FS
func main() {
data, _ := f.ReadFile("pfinal/.pfinal.txt")
fmt.Println(string(data))
data, _ = f.ReadFile("pfinal/q/.pf.txt") // 沒有嵌入 pfinal/q/.hi.txt
fmt.Println(string(data))
}
此外,嵌入模式不支持絕對路徑,也不支持路徑中包含 . 和 ..。如果想嵌入當前 Go 源文件所在路徑的文件,可以使用通配符 *:
package main
import (
"embed"
"fmt"
)
//go:embed "he llo.txt" `hello-2.txt`
var f embed.FS
func main() {
data, _ := f.ReadFile("he llo.txt")
fmt.Println(string(data))
}
參考
/**
* @Author: PFinal南丞
* @Author: lampxiezi@163.com
* @Date: 2023/11/9
* @Desc:示例僞代碼
* @Project: pf_tools
*/
const (
IntLen = 4
CharLen = 1
PhoneIndexLength = 9
CHUNK = 100
PhoneDat = "phone.dat"
)
//go:embed phone.dat
var fsContent embed.FS
type PhoneRecord struct {
PhoneNum string
Province string
City string
ZipCode string
AreaZone string
CardType string
}
var content []byte
func init() {
var err error
content, err = fsContent.ReadFile(PhoneDat)
if err != nil {
panic(err)
}
}
....
通過這些示例,可以看到 go:embed 指令在嵌入靜態資源方面的強大功能和靈活性。開發者可以根據實際需求,選擇最適合的嵌入方式,提高程序的可移植性和部署效率。
本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源:https://mp.weixin.qq.com/s/IGv5J0lBjJ6h-eI5ME0nag