Go語言核心36講(Go語言實戰與應用二十三)–學習筆記
- 2021 年 12 月 9 日
- 筆記
- 【016】Go語言核心36講
45 | 使用os包中的API (下)
我們在上一篇文章中。從「os.File類型都實現了哪些io包中的接口」這一問題出發,介紹了一系列的相關內容。今天我們繼續圍繞這一知識點進行擴展。
知識擴展
問題 1:可應用於File值的操作模式都有哪些?
針對File值的操作模式主要有隻讀模式、只寫模式和讀寫模式。
這些模式分別由常量os.O_RDONLY、os.O_WRONLY和os.O_RDWR代表。在我們新建或打開一個文件的時候,必須把這三個模式中的一個設定為此文件的操作模式。
除此之外,我們還可以為這裡的文件設置額外的操作模式,可選項如下所示。
- os.O_APPEND:當向文件中寫入內容時,把新內容追加到現有內容的後邊。
- os.O_CREATE:當給定路徑上的文件不存在時,創建一個新文件。
- os.O_EXCL:需要與os.O_CREATE一同使用,表示在給定的路徑上不能有已存在的文件。
- os.O_SYNC:在打開的文件之上實施同步 I/O。它會保證讀寫的內容總會與硬盤上的數據保持同步。
- os.O_TRUNC:如果文件已存在,並且是常規的文件,那麼就先清空其中已經存在的任何內容。
對於以上操作模式的使用,os.Create函數和os.Open函數都是現成的例子。
func Create(name string) (*File, error) {
return OpenFile(name, O_RDWR|O_CREATE|O_TRUNC, 0666)
}
os.Create函數在調用os.OpenFile函數的時候,給予的操作模式是os.O_RDWR、os.O_CREATE和os.O_TRUNC的組合。
這就基本上決定了前者的行為,即:如果參數name代表路徑之上的文件不存在,那麼就新建一個,否則,先清空現存文件中的全部內容。
並且,它返回的File值的讀取方法和寫入方法都是可用的。這裡需要注意,多個操作模式是通過按位或操作符|組合起來的。
func Open(name string) (*File, error) {
return OpenFile(name, O_RDONLY, 0)
}
我在前面說過,os.Open函數的功能是:以只讀模式打開已經存在的文件。其根源就是它在調用os.OpenFile函數的時候,只提供了一個單一的操作模式os.O_RDONLY。
以上,就是我對可應用於File值的操作模式的簡單解釋。在 demo88.go 文件中還有少許示例,可供你參考。
package main
import (
"fmt"
"io/ioutil"
"os"
"path/filepath"
)
type flagDesc struct {
flag int
desc string
}
func main() {
fileName1 := "something2.txt"
filePath1 := filepath.Join(os.TempDir(), fileName1)
fmt.Printf("The file path: %s\n", filePath1)
fmt.Println()
// 示例1。
contents0 := "OpenFile is the generalized open call."
flagDescList := []flagDesc{
{
os.O_WRONLY | os.O_CREATE | os.O_TRUNC,
"os.O_WRONLY|os.O_CREATE|os.O_TRUNC",
},
{
os.O_WRONLY,
"os.O_WRONLY",
},
{
os.O_WRONLY | os.O_APPEND,
"os.O_WRONLY|os.O_APPEND",
},
}
for i, v := range flagDescList {
fmt.Printf("Open the file with flag %s ...\n", v.desc)
file1a, err := os.OpenFile(filePath1, v.flag, 0666)
if err != nil {
fmt.Printf("error: %v\n", err)
continue
}
fmt.Printf("The file descriptor: %d\n", file1a.Fd())
contents1 := fmt.Sprintf("[%d]: %s ", i+1, contents0)
fmt.Printf("Write %q to the file ...\n", contents1)
n, err := file1a.WriteString(contents1)
if err != nil {
fmt.Printf("error: %v\n", err)
continue
}
fmt.Printf("The number of bytes written is %d.\n", n)
file1b, err := os.Open(filePath1)
fmt.Println("Read bytes from the file ...")
bytes, err := ioutil.ReadAll(file1b)
if err != nil {
fmt.Printf("error: %v\n", err)
continue
}
fmt.Printf("Read(%d): %q\n", len(bytes), bytes)
fmt.Println()
}
// 示例2。
fmt.Println("Try to create an existing file with flag os.O_TRUNC ...")
file2, err := os.OpenFile(filePath1, os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0666)
if err != nil {
fmt.Printf("error: %v\n", err)
return
}
fmt.Printf("The file descriptor: %d\n", file2.Fd())
fmt.Println("Try to create an existing file with flag os.O_EXCL ...")
_, err = os.OpenFile(filePath1, os.O_RDWR|os.O_CREATE|os.O_EXCL, 0666)
fmt.Printf("error: %v\n", err)
}
問題 2:怎樣設定常規文件的訪問權限?
我們已經知道,os.OpenFile函數的第三個參數perm代表的是權限模式,其類型是os.FileMode。但實際上,os.FileMode類型能夠代表的,可遠不只權限模式,它還可以代表文件模式(也可以稱之為文件種類)。
由於os.FileMode是基於uint32類型的再定義類型,所以它的每個值都包含了 32 個比特位。在這 32 個比特位當中,每個比特位都有其特定的含義。
比如,如果在其最高比特位上的二進制數是1,那麼該值表示的文件模式就等同於os.ModeDir,也就是說,相應的文件代表的是一個目錄。
又比如,如果其中的第 26 個比特位上的是1,那麼相應的值表示的文件模式就等同於os.ModeNamedPipe,也就是說,那個文件代表的是一個命名管道。
實際上,在一個os.FileMode類型的值(以下簡稱FileMode值)中,只有最低的 9 個比特位才用於表示文件的權限。當我們拿到一個此類型的值時,可以把它和os.ModePerm常量的值做按位與操作。
這個常量的值是0777,是一個八進制的無符號整數,其最低的 9 個比特位上都是1,而更高的 23 個比特位上都是0。
所以,經過這樣的按位與操作之後,我們即可得到這個FileMode值中所有用於表示文件權限的比特位,也就是該值所表示的權限模式。這將會與我們調用FileMode值的Perm方法所得到的結果值是一致。
在這 9 個用於表示文件權限的比特位中,每 3 個比特位為一組,共可分為 3 組。
從高到低,這 3 組分別表示的是文件所有者(也就是創建這個文件的那個用戶)、文件所有者所屬的用戶組,以及其他用戶對該文件的訪問權限。而對於每個組,其中的 3 個比特位從高到低分別表示讀權限、寫權限和執行權限。
如果在其中的某個比特位上的是1,那麼就意味着相應的權限開啟,否則,就表示相應的權限關閉。
因此,八進制整數0777就表示:操作系統中的所有用戶都對當前的文件有讀、寫和執行的權限,而八進制整數0666則表示:所有用戶都對當前文件有讀和寫的權限,但都沒有執行的權限。
我們在調用os.OpenFile函數的時候,可以根據以上說明設置它的第三個參數。但要注意,只有在新建文件的時候,這裡的第三個參數值才是有效的。在其他情況下,即使我們設置了此參數,也不會對目標文件產生任何的影響。
package main
import (
"fmt"
"os"
"path/filepath"
)
type argDesc struct {
action string
flag int
perm os.FileMode
}
func main() {
// 示例1。
fmt.Printf("The mode for dir:\n%32b\n", os.ModeDir)
fmt.Printf("The mode for named pipe:\n%32b\n", os.ModeNamedPipe)
fmt.Printf("The mode for all of the irregular files:\n%32b\n", os.ModeType)
fmt.Printf("The mode for permissions:\n%32b\n", os.ModePerm)
fmt.Println()
// 示例2。
fileName1 := "something3.txt"
filePath1 := filepath.Join(os.TempDir(), fileName1)
fmt.Printf("The file path: %s\n", filePath1)
argDescList := []argDesc{
{
"Create",
os.O_RDWR | os.O_CREATE,
0644,
},
{
"Reuse",
os.O_RDWR | os.O_TRUNC,
0666,
},
{
"Open",
os.O_RDWR | os.O_APPEND,
0777,
},
}
defer os.Remove(filePath1)
for _, v := range argDescList {
fmt.Printf("%s the file with perm %o ...\n", v.action, v.perm)
file1, err := os.OpenFile(filePath1, v.flag, v.perm)
if err != nil {
fmt.Printf("error: %v\n", err)
continue
}
info1, err := file1.Stat()
if err != nil {
fmt.Printf("error: %v\n", err)
continue
}
fmt.Printf("The file permissions: %o\n", info1.Mode().Perm())
}
}
總結
為了聚焦於os.File類型本身,我在這兩篇文章中主要講述了怎樣把 os.File 類型應用於常規的文件。該類型的指針類型實現了很多io包中的接口,因此它的具體功用也就可以不言自明了。
通過該類型的值,我們不但可以對文件進行各種讀取、寫入、關閉等操作,還可以設定下一次讀取或寫入時的起始索引位置。
在使用這個類型的值之前,我們必須先要創建它。所以,我為你重點介紹了幾個可以創建,並獲得此類型值的函數。
包括:os.Create、os.NewFile、os.Open和os.OpenFile。我們用什麼樣的方式創建File值,就決定了我們可以使用它來做什麼。
利用os.Create函數,我們可以在操作系統中創建一個全新的文件,或者清空一個現存文件中的全部內容並重用它。
在相應的File值之上,我們可以對該文件進行任何的讀寫操作。雖然os.NewFile函數並不是被用來創建新文件的,但是它能夠基於一個有效的文件描述符包裝出一個可用的File值。
os.Open函數的功能是打開一個已經存在的文件。但是,我們只能通過它返回的File值對相應的文件進行讀操作。
os.OpenFile是這些函數中最為靈活的一個,通過它,我們可以設定被打開文件的操作模式和權限模式。實際上,os.Create函數和os.Open函數都只是對它的簡單封裝而已。
在使用os.OpenFile函數的時候,我們必須要搞清楚操作模式和權限模式所代表的真正含義,以及設定它們的正確方式。
我在本文的擴展問題中分別對它們進行了較為詳細的解釋。同時,我在對應的示例文件中也編寫了一些代碼。
你需要認真地閱讀和理解這些代碼,並在運行它們的過程當中悟出這兩種模式的真諦。
我在本文中講述的東西對於os包來說,只是海面上的那部分冰山而已。這個代碼包囊括的知識眾多,而且延展性都很強。
如果你想完全理解它們,可能還需要去參看操作系統等方面的文檔和教程。由於篇幅原因,我在這裡只是做了一個引導,幫助你初識該包中的一些重要的程序實體,並給予你一個可以深入下去的切入點,希望你已經在路上了。
思考題
今天的思考題是:怎樣通過os包中的 API 創建和操縱一個系統進程?
筆記源碼
//github.com/MingsonZheng/go-core-demo
本作品採用知識共享署名-非商業性使用-相同方式共享 4.0 國際許可協議進行許可。
歡迎轉載、使用、重新發佈,但務必保留文章署名 鄭子銘 (包含鏈接: //www.cnblogs.com/MingsonZheng/ ),不得用於商業目的,基於本文修改後的作品務必以相同的許可發佈。