什么神奇問題 ?
Go 在文件 IO 的場景有個神奇的事情。打開一個文件的時候,返回的竟然不是 interface ,而是一個 os.File 結構體的指針。
funcOpen(namestring)(*File,error){ returnOpenFile(name,O_RDONLY,0) }
劃重點:這個意味著,Go 的文件系統的概念和 OS 的文件系統的概念直接關聯起來。你必須傳入一個文件路徑,并且必須真的要去打開一個操作系統的文件。
不用接口,而是跟具體類型強相關的話,會導致后續的擴展性不好。比如,全都是 os 包的使用,那么將操作強綁定在 OS 文件系統上。
最常見的,在單測的時候用的這種方式的話,就真的要在操作系統上打開文件做操作。Go 的設計者對此一直耿耿于懷,但是也很無奈。因為用戶已經用上了,Go 的承諾是往前兼容,直接修改原有語義和接口肯定不行。
怎么辦?
Go 1.16 給了我們答案。Go 給了我們一個 io.FS 的封裝。Go 的意圖是在自己的語言層面再做一層 FS 的抽象,這樣就能和 OS 的 FS 解耦開來。io.FS 可以是任何奇形怪狀的 FS ,只要你實現了規定好的 FS 接口。下一步來看下 Go 1.16 帶來的幾個核心改動。
有人說 Go 都 1.19 了,還看 1.16 ?
因為 Go 的 io/fs 是在 Go 1.16 引入的。在 io 方面有比較大的一個變化。
Go 1.16 關于 io 有哪些改變 ?
新增了一個 io/fs 的包,抽象了一個 FS 出來。
embed 的 package 用了這個抽象。
規整 io/ioutil 里面的內容。
接下來我們一個個看下。
io.FS 的抽象
1Go 為什么要抽象 FS ?
前面已經提到,Go 的文件系統的概念和 OS 的文件系統的概念直接關聯起來。這個給擴展性帶來了不方便。最重要的,Go 已經發現有和 OS 不同的文件系統的需求了,就是 embed FS 。
embed 是 Go 提供的一個打包文件到二進制的功能,也是類似文件系統的一種需求。但是卻不是直接位于 OS 上的文件系統(vfs 那套東西)。
所以在 Go 1.16 順勢就一起上了。引入了 io.FS 的定義,并且 embed 就直接用上了這層抽象。
![[fs 封裝層次.png]]
2來看下 FS 接口的定義
Go 的實現者們很強,推薦的是小接口。也就是最小化、原子化的接口語義。從 io/fs 的定義就能看到很強的功力。
//文件系統的接口 typeFSinterface{ Open(namestring)(File,error) } //文件的接口 typeFileinterface{ Stat()(FileInfo,error) Read([]byte)(int,error) Close()error }
這,就是最簡單的 FS 。 這個就是文件系統極簡的樣子,只需要有一個 Open 方法,返回一個文件即可。
也就是說,Go 理解的文件系統,只要能實現一個 Open 方法,返回一個 File 的 interface ,這個 File 只需要實現 Stat,Read,Close 方法即可。
有沒有發現,OS 的 FS 已經滿足了條件。所以,Go 的 FS 可以是 OS 的 FS ,自然也可以是其他的實現。
Go 在此 io.FS 的基礎上,再去擴展接口,增加文件系統的功能。比如,加個 ReadDir 就是一個有讀目錄的文件系統 ReadDirFS :
typeReadDirFSinterface{ FS //讀目錄 ReadDir(namestring)([]DirEntry,error) }
加個 Glob 方法,就成為一個具備路徑通配符查詢的文件系統:
typeGlobFSinterface{ FS //路徑通配符的功能 Glob(patternstring)([]string,error) }
加個 Stat ,就變成一個路徑查詢的文件系統:
typeStatFSinterface{ FS //查詢某個路徑的文件信息 Stat(namestring)(FileInfo,error) }
這些非常經典的文件系統的定義 Go 在 io/fs 里面已經做好了。
3io.FS 怎么使用呢?
我們的目標是實現一個 Go 的 FS ,這個定義已經在 io.FS 有了。我們只需要寫一個結構體,實現它的方法,那么你就可以說這是一個 FS 了。
這里其實就可以有非常多的想象空間,比如,可以是 OS 的 FS,也可以是 memory FS ,hash FS 等等。網上有不少例子。但其實標準庫已經有一個最好的例子,那就是 embed FS 。
我們來看下 embed 怎么實現一個內嵌的文件系統。embed 的實現在 embed/embed.go 這個文件中,非常精簡。
首先,在 embed package 里定義了一個結構體 FS ,這個結構體將是 io.FS 的具體實現。
//作為具體FS的實現 typeFSstruct{ files*[]file } //代表一個內嵌文件 typefilestruct{ namestring datastring//文件的數據全在內存里 hash[16]byte//truncatedSHA256hash }
embed 里面的 FS 結構體只需要實現 Open 這個方法即可:
//Open的具體實現 func(fFS)Open(namestring)(fs.File,error){ //通過名字匹配查找到file對象 file:=f.lookup(name) //如果沒找到 iffile==nil{ returnnil,&fs.PathError{Op:"open",Path:name,Err:fs.ErrNotExist} } //如果是目錄結構 iffile.IsDir(){ return&openDir{file,f.readDir(name),0},nil } //找到了就封裝成openFile結構體 return&openFile{file,0},nil }
上面的 Open ,如果是文件的化,返回的是一個 openFile 的結構體 ,作為 io.File 接口的具體實現:
//代表一個文件的實現 typeopenFilestruct{ f*file//thefileitself offsetint64//currentreadoffset } func(f*openFile)Close()error{returnnil} func(f*openFile)Stat()(fs.FileInfo,error){returnf.f,nil} func(f*openFile)Read(b[]byte)(int,error){ //判斷偏移是否符合預期 iff.offset>=int64(len(f.f.data)){ return0,io.EOF } iff.offset0?{ ????????return?0,?&fs.PathError{Op:?"read",?Path:?f.f.name,?Err:?fs.ErrInvalid} ????} ????//?從內存拷貝數據 ????n?:=?copy(b,?f.f.data[f.offset:]) ????f.offset?+=?int64(n) ????return?n,?nil }
如上,只需要實現 Read,Stat,Close 方法即可。這就是一個完整的、Go 層面的 FS 的實現。
你可以如下使用 embed 文件系統:
//go:embedhello.txt varfembed.FS funcmain(){ //打開文件 file,err:=f.Open("hello.txt") //... //讀文件 n,err=file.Read(/*buffer*/) }
上面的例子,編譯的時候會把當前目錄下的一個 hello.txt 文件打包到二進制文件。程序啟動的時候可以把它讀出來。
注意:f 這個變量,編譯器會安排填充好。進程啟動時它是有值的。
Go 1.16 關于 IO 其他的改動
除了上面提到的 io/fs 和 embed fs ,Go 對之前的 io 的一些結構也做了更準確的調整分類。把之前大雜燴的 io/ioutil 里面的東西拆出來了。移到對應的 io 包和 os 包。為了兼容性,ioutil 包并沒有直接刪除,而是導入。比如:
Discard 移到了 io 庫實現
ReadAll 移到了 io 庫實現
NopCloser 移到了 io 庫實現
ReadFile 移到 os 庫實現
WriteFile 移到 os 庫實現
基本上 ioutil 這個 package 是被掏空了。Go 1.16 只是為了兼容性還沒刪。
Go 的 FS 封裝有啥用呢 ?
好處其實很多,最明顯的兩個:
單測方便了。
有類似 embed FS 這種非 OS 文件系統的需求,可以有方法擴展了。
總結
Go 在自己的層面封裝出一個 io.FS 的抽象,意圖和 OS 的 FS 解耦。這樣可以給程序員帶來更多的想象空間 ;
embed FS 具備典型的 FS 的界面,但是它并不是直接位于 OS 的文件系統。所以它非常適合作為首個用 io.FS 的實踐;
以后盡量用 io.FS 來管理的文件,這樣可以做到和 OS 解耦,方便做單測;
ioutil 可以少用,它的功能已經被移到更明確的 package 里實現了;
-
接口
+關注
關注
33文章
7737瀏覽量
148699 -
操作系統
+關注
關注
37文章
6318瀏覽量
121972 -
文件系統
+關注
關注
0文章
272瀏覽量
19707
原文標題:Go 眼中的文件系統是什么? io.FS
文章出處:【微信號:LinuxHub,微信公眾號:Linux愛好者】歡迎添加關注!文章轉載請注明出處。
發布評論請先 登錄
相關推薦
評論