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 建立和操縱一個系統程式?
筆記原始碼
https://github.com/MingsonZheng/go-core-demo
本作品採用知識共享署名-非商業性使用-相同方式共享 4.0 國際許可協議進行許可。
歡迎轉載、使用、重新發布,但務必保留文章署名 鄭子銘 (包含連結: http://www.cnblogs.com/MingsonZheng/ ),不得用於商業目的,基於本文修改後的作品務必以相同的許可釋出。