通過os包,可以擁有操控計(jì)算機(jī)操作系統(tǒng)的能力。這個(gè)代碼包提供的都是平臺(tái)不相關(guān)的API。無論是Linux、macOS、Windows、FreeBSD、OpenBSD、Plan9,os包都可以提供統(tǒng)一的使用接口。這樣就可以用同樣的方法來操縱不同的操作系統(tǒng),并得到相似的結(jié)果。
os包中的API主要可以幫助我們使用操作系統(tǒng)中的文件系統(tǒng)、權(quán)限系統(tǒng)、環(huán)境變量、系統(tǒng)進(jìn)程以及系統(tǒng)信號(hào)。其中,文件系統(tǒng)的API最豐富。不但可以用來創(chuàng)建和刪除文件以及目錄,還可以獲取到各種信息、修改內(nèi)容、修改訪問權(quán)限。等等。這里,最常用的數(shù)據(jù)類型就是:os.File。

從字面上看,os.File類型代表了操作系統(tǒng)中的文件,但是實(shí)際上,它代表的遠(yuǎn)不止于此。比如對(duì)于類Unix的操作系統(tǒng),包括Linux、macOS、FreeBSD等,其中的一切都可以被看作是文件。
除了文本文件、二進(jìn)制文件、壓縮文件、目錄這些常見的形式之外,還有符號(hào)鏈接、各種物理設(shè)備(包括內(nèi)置或外接的面向塊或者字符的設(shè)備)、命名管道,以及套接字(也就是socket),等等。所以能夠利用os.File類型操縱的東西有很多。不過接下來主要介紹os.File類型應(yīng)用于常規(guī)文件。
os.File類型擁有的都是指針方法,它的指針實(shí)現(xiàn)了很多io包中的接口。
對(duì)于io包中最核心的3個(gè)簡單接口io.Reader、io.Writer和io.Closer,*os.File類型都實(shí)現(xiàn)了它們。另外還順便實(shí)現(xiàn)了io包中的9個(gè)擴(kuò)展接口中的7個(gè)。沒有實(shí)現(xiàn)簡單接口io.ByteReader和io.RuneReader,所以也沒有實(shí)現(xiàn)上面這兩個(gè)的擴(kuò)展接口io.ByteScannser和io.RuneScanner。
總之,os.File類型及其指針類型的值,不但可以通過各種方式讀取和寫入某個(gè)文件中的內(nèi)容,還可以尋找并設(shè)定下一次讀取或?qū)懭霑r(shí)的起始索引位置,另外還可以隨時(shí)對(duì)文件進(jìn)行關(guān)閉。但是,它并不能專門的讀取文件中的下一個(gè)字節(jié)或Unicode字符,也不能進(jìn)行任何的讀回退操作。不過,單讀讀取下一個(gè)字節(jié)或字符的功能也可以通過其他方式來實(shí)現(xiàn)。比如用Read方法傳入適當(dāng)?shù)膮?shù)。
使用反射檢查接口的實(shí)現(xiàn)
下面的示例枚舉了io包中的所有接口,檢查*os.File是否實(shí)現(xiàn)了該接口:
package main
import (
    "bytes"
    "fmt"
    "io"
    "os"
    "reflect"
)
// ioTypes 代表了io代碼包中的所有接口的反射類型。
var ioTypes = []reflect.Type{
    reflect.TypeOf((*io.Reader)(nil)).Elem(),
    reflect.TypeOf((*io.Writer)(nil)).Elem(),
    reflect.TypeOf((*io.Closer)(nil)).Elem(),
    reflect.TypeOf((*io.ByteReader)(nil)).Elem(),
    reflect.TypeOf((*io.RuneReader)(nil)).Elem(),
    reflect.TypeOf((*io.ReaderAt)(nil)).Elem(),
    reflect.TypeOf((*io.Seeker)(nil)).Elem(),
    reflect.TypeOf((*io.WriterTo)(nil)).Elem(),
    reflect.TypeOf((*io.ByteWriter)(nil)).Elem(),
    reflect.TypeOf((*io.WriterAt)(nil)).Elem(),
    reflect.TypeOf((*io.ReaderFrom)(nil)).Elem(),
    reflect.TypeOf((*io.ByteScanner)(nil)).Elem(),
    reflect.TypeOf((*io.RuneScanner)(nil)).Elem(),
    reflect.TypeOf((*io.ReadSeeker)(nil)).Elem(),
    reflect.TypeOf((*io.ReadCloser)(nil)).Elem(),
    reflect.TypeOf((*io.WriteCloser)(nil)).Elem(),
    reflect.TypeOf((*io.WriteSeeker)(nil)).Elem(),
    reflect.TypeOf((*io.ReadWriter)(nil)).Elem(),
    reflect.TypeOf((*io.ReadWriteSeeker)(nil)).Elem(),
    reflect.TypeOf((*io.ReadWriteCloser)(nil)).Elem(),
}
func main() {
    var file os.File
    fileType := reflect.TypeOf(&file)
    var buf bytes.Buffer  // 存放沒有實(shí)現(xiàn)的那些接口信息,最后統(tǒng)一打印出來
    fmt.Fprintf(&buf, "Type %T not implements:\n", &file)
    fmt.Printf("Type %T implements:\n", &file)
    for _, t := range ioTypes {
        if fileType.Implements(t) {
            fmt.Println(t.String())
        } else {
            fmt.Fprintln(&buf, t.String())
        }
    }
    fmt.Println()
    fmt.Println(buf.String())
}一般要檢查接口是否實(shí)現(xiàn)了,不需要用到反射這么高級(jí)的用法。
要操作文件,首先要獲取一個(gè)os.File類型的指針值,簡稱File值。在os包中,有如下幾個(gè)函數(shù):
用于根據(jù)給定的路徑創(chuàng)建一個(gè)新的文件。它會(huì)返回一個(gè)File值和一個(gè)錯(cuò)誤值。可以在該函數(shù)返回的File值之上,對(duì)相應(yīng)的文件進(jìn)行讀操作和寫操作。使用這個(gè)函數(shù)創(chuàng)建的文件,對(duì)操作系統(tǒng)中的所有用戶來說都是可以讀和寫的。
注意,如果給定的路徑已經(jīng)存在一個(gè)文件了,那么該函數(shù)會(huì)先清空現(xiàn)有文件中的內(nèi)容,然后再把該文件的File值返回。就是覆蓋原有文件創(chuàng)建一個(gè)新的空文件。另外,如果有錯(cuò)誤,會(huì)通過第二個(gè)參數(shù)返回錯(cuò)誤值。比如,如果路徑不存在,那么會(huì)返回一個(gè)*os.PathErro類型的錯(cuò)誤值。
下面的示例,嘗試當(dāng)在前目錄下創(chuàng)建一個(gè)文件。還會(huì)把當(dāng)前目錄的名稱截掉最后一個(gè)字符,這應(yīng)該會(huì)是一個(gè)不存在的目錄,同樣嘗試創(chuàng)建一個(gè)文件,然后會(huì)返回一個(gè)預(yù)期的錯(cuò)誤:
package main
import (
    "fmt"
    "os"
    "path/filepath"
)
func main() {
    tempPath := os.TempDir()
    fmt.Println("系統(tǒng)的臨時(shí)文件夾:", tempPath)
    fileName := "test.txt"
    var paths []string
    dir, _ := os.Getwd()
    dirPath := filepath.Join(dir, fileName)  // 在當(dāng)前文件夾下創(chuàng)建一個(gè)文件
    paths = append(paths, dirPath)
    notExistsPath := filepath.Join(dir[:len(dir)-1], fileName)  // 這個(gè)文件夾路徑應(yīng)該不存在
    paths = append(paths, notExistsPath)
    for _, path := range paths {
        fmt.Println("創(chuàng)建文件:", path)
        _, err := os.Create(path)  // 返回的第一個(gè)參數(shù)是*File,就不要的
        if err != nil {
            var underlyingErr string
            if _, ok := err.(*os.PathError); ok {
                underlyingErr = "(path error)"
            }
            fmt.Fprintf(os.Stderr, "ERROR: %v %s\n", err, underlyingErr)
            continue
        }
        fmt.Println("創(chuàng)建文件成功.")
    }
}該函數(shù)在被調(diào)用的時(shí)候需要接受一個(gè)代表文件描述符的uintptr類型的值,以及一個(gè)用于表示文件名的字符串。如果給定的不是有效的文件描述符,那么會(huì)返回nil。否則,返回相應(yīng)文件的File值。這里不要被函數(shù)名稱誤導(dǎo),它的功能不是創(chuàng)建一個(gè)新的文件,而是依據(jù)一個(gè)已經(jīng)存在的文件的描述符,來新建一個(gè)包裝了該文件的File值。比如,可以像這樣拿到一個(gè)包裝了標(biāo)準(zhǔn)錯(cuò)誤輸出的File值:
file := os.NewFile(uintptr(syscall.Stderr), "/dev/stderr")然后,通過這個(gè)File值向標(biāo)準(zhǔn)錯(cuò)誤輸出寫入一些內(nèi)容,一般的效果就是打印出錯(cuò)誤信息:
if file != nil {
    file.WriteString("Test Stderr.\n")
}如果是一個(gè)已存在的文件,可以使用該文件的文件描述符作為函數(shù)的第一個(gè)參數(shù)返回File值。下面文件描述符的內(nèi)容里還有一個(gè)示例。
打開一個(gè)文件并返回包裝了該文件的File值。該函數(shù)只能以只讀模式打開文件。如果調(diào)用了File值的任何寫入方法,都會(huì)返回錯(cuò)誤:
package main
import (
    "fmt"
    "os"
    "path/filepath"
)
func main() {
    fileName := "test.txt"
    dir, _ := os.Getwd()
    dirPath := filepath.Join(dir, fileName)
    file, err := os.Open(dirPath)
    if err != nil {
        // 文件可能不存在,先創(chuàng)建一個(gè)文件
        fmt.Fprintf(os.Stderr, "ERROR: %v\n", err)
        return
    }
    _, err = file.WriteString(" ")  // 文件是只讀的,嘗試寫入會(huì)返回錯(cuò)誤
    var underlyingErr string
    if _, ok := err.(*os.PathError); ok {
        underlyingErr = "(path error)"
    }
    fmt.Fprintf(os.Stderr, "ERROR: %v %s\n", err, underlyingErr)
}實(shí)際上,上面說的只讀模式,正是應(yīng)用在File值所持有的文件描述符之上的。
文件描述符,是由通常很小的非負(fù)整數(shù)代表的。它一般會(huì)由I/O相關(guān)的系統(tǒng)調(diào)用返回,并作為某個(gè)文件的一個(gè)表示存在。
從操作系統(tǒng)的層面看,針對(duì)任何文件的I/O操作都需要用到這個(gè)文件描述符。只不過,Go語言中的一些數(shù)據(jù)類型,為我們隱匿掉了這個(gè)描述符。實(shí)際上,在調(diào)用os.Create函數(shù)、os.Open函數(shù)以及之后會(huì)提到的os.OpenFile函數(shù)時(shí),都會(huì)執(zhí)行同一個(gè)系統(tǒng)調(diào)用,并且在成功之后會(huì)得到這樣一個(gè)文件描述符。這個(gè)文件描述符將會(huì)被存儲(chǔ)在返回的File值中。os.File類型有一個(gè)Fd的指針方法,返回一個(gè)uintptr類型的值。這個(gè)值就代表了當(dāng)前的File值所持有的那個(gè)文件描述符。
不過在os包中,只有NewFile函數(shù)需要用到它。所以,如果操作的只是常規(guī)的文件或目錄,也無需特別在意。
文件描述符相關(guān)的示例:
package main
import (
    "fmt"
    "os"
    "path/filepath"
)
func main() {
    fileName := "test.txt"
    dir, _ := os.Getwd()
    dirPath := filepath.Join(dir, fileName)
    file1, err := os.Open(dirPath)
    if err != nil {
        // 文件可能不存在,先創(chuàng)建一個(gè)文件
        fmt.Fprintf(os.Stderr, "ERROR: %v\n", err)
        return
    }
    file2, _ := os.Open(dirPath)  // 雖然打開的是同一個(gè)文件,但卻是不同的文件描述符
    file3 := os.NewFile(file1.Fd(), dirPath)  // 可以通過文件描述符,獲取File值
    fmt.Println(file1.Fd(), file2.Fd(), file3.Fd())
}通過Fd方法獲取到的文件描述符可以通過os.NewFile函數(shù)返回File值。
這個(gè)函數(shù)其實(shí)是os.Create函數(shù)和os.Open函數(shù)的底層支持,它最靈活。這個(gè)函數(shù)有3個(gè)參數(shù):
func OpenFile(name string, flag int, perm FileMode) (*File, error) {
    testlog.Open(name)
    return openFileNolog(name, flag, perm)
}name參數(shù),是文件的路徑。
flag參數(shù),是需要施加在文件描述符上的模式,叫操作模式,Open函數(shù)是只讀的就是因?yàn)樵贠pen函數(shù)里調(diào)用OpenFile的時(shí)候指定了該參數(shù):
func Open(name string) (*File, error) {
    return OpenFile(name, O_RDONLY, 0)
}perm參數(shù),也是模式,叫權(quán)限模式。類型是os.FileMode,此類型是一個(gè)基于uint32類型的再定義類型:
type FileMode uint32這里的兩個(gè)模式:
關(guān)于操作模式和訪問權(quán)限的更多細(xì)節(jié),在后面繼續(xù)講。
打開文件并寫入內(nèi)容的操作示例:
package main
import (
    "fmt"
    "os"
    "path/filepath"
)
func main() {
    fileName := "test.txt"
    dir, _ := os.Getwd()
    dirPath := filepath.Join(dir, fileName)
    // O_WRONLY:只寫模式。O_CREATE:文件不存在就創(chuàng)建。O_TRUNC:打開并清空文件
    file, err := os.OpenFile(dirPath, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0666)
    if err != nil {
        fmt.Fprintf(os.Stderr, "ERROR: %v\n", err)
        return
    }
    n, err := file.WriteString("寫入操作")
    if err != nil {
        fmt.Fprintf(os.Stderr, "ERROR: %v\n", err)
    } else {
        fmt.Println("寫入字節(jié)數(shù)(bytes):", n)
    }
}針對(duì)File值的操作模式主要有:
在新建一個(gè)文件的時(shí)候,必須把這三個(gè)模式中的一個(gè)設(shè)定為此文件的操作模式。
另外,還可以再設(shè)置額外的操作模式,選項(xiàng)如下:
對(duì)于以上操作模式的使用,os.Open函數(shù)和os.Create函數(shù)都是現(xiàn)成的例子:
func Open(name string) (*File, error) {
    return OpenFile(name, O_RDONLY, 0)
}
func Create(name string) (*File, error) {
    return OpenFile(name, O_RDWR|O_CREATE|O_TRUNC, 0666)
}這里順便也是下面訪問權(quán)限的例子了。
這里可以看到,多個(gè)操作符是通過按位或操作符(|)組合起來的,常用的寫模式的組合還有:
os.O_WRONLY|os.O_CREATE|os.O_EXCL: 文件存在會(huì)報(bào)錯(cuò),不存在才新建os.O_WRONLY|os.O_CREATE|os.O_TRUNC: 無論是否有文件存在,都會(huì)得到一個(gè)空文件,然后可以寫入新內(nèi)容os.O_WRONLY|os.O_APPEND: 在文件內(nèi)容后面追加內(nèi)容os.OpenFile函數(shù)的第三個(gè)參數(shù)perm代表的是權(quán)限模式,類型是os.FileMode。實(shí)際上,os.FIleMode類型能夠代表的,不只權(quán)限模式,還可以代表文件模式,也可以稱之為文件種類。
os.FileMode是基于uint32類型的再定義類型,它包含了32個(gè)比特位。在這32個(gè)比特位中,每個(gè)比特位都有其特定的意義:
所有的常量都在源碼里有說明:
const (
    // The single letters are the abbreviations
    // used by the String method's formatting.
    ModeDir        FileMode = 1 << (32 - 1 - iota) // d: is a directory
    ModeAppend                                     // a: append-only
    ModeExclusive                                  // l: exclusive use
    ModeTemporary                                  // T: temporary file; Plan 9 only
    ModeSymlink                                    // L: symbolic link
    ModeDevice                                     // D: device file
    ModeNamedPipe                                  // p: named pipe (FIFO)
    ModeSocket                                     // S: Unix domain socket
    ModeSetuid                                     // u: setuid
    ModeSetgid                                     // g: setgid
    ModeCharDevice                                 // c: Unix character device, when ModeDevice is set
    ModeSticky                                     // t: sticky
    ModeIrregular                                  // ?: non-regular file; nothing else is known about this file
    // Mask for the type bits. For regular files, none will be set.
    ModeType = ModeDir | ModeSymlink | ModeNamedPipe | ModeSocket | ModeDevice | ModeIrregular
    ModePerm FileMode = 0777 // Unix permission bits
)可以像操作模式那樣用按位或操作符(|)組合起來。一般也就直接0666或0777就好了。
這篇還是偏重理論,主要講的打開文件的操作。在打開文件獲取到os.File類型后,由于它的指針已經(jīng)實(shí)現(xiàn)了各種io接口,之后的讀寫操作,就是通過調(diào)用*os.File實(shí)現(xiàn)的io接口來實(shí)現(xiàn)了。另外,io包還有一個(gè)ioutil子包,可以讀取整個(gè)文件。而逐行讀取文件的內(nèi)容,也需要一些具體的實(shí)現(xiàn)。
下面這篇有一些文件讀寫的示例:
https://blog.51cto.com/steed/2315597
創(chuàng)新互聯(lián)www.cdcxhl.cn,專業(yè)提供香港、美國云服務(wù)器,動(dòng)態(tài)BGP最優(yōu)骨干路由自動(dòng)選擇,持續(xù)穩(wěn)定高效的網(wǎng)絡(luò)助力業(yè)務(wù)部署。公司持有工信部辦法的idc、isp許可證, 機(jī)房獨(dú)有T級(jí)流量清洗系統(tǒng)配攻擊溯源,準(zhǔn)確進(jìn)行流量調(diào)度,確保服務(wù)器高可用性。佳節(jié)活動(dòng)現(xiàn)已開啟,新人活動(dòng)云服務(wù)器買多久送多久。
                名稱欄目:Go36-44,45-文件操作(os.File)-創(chuàng)新互聯(lián)
                
                標(biāo)題路徑:http://www.chinadenli.net/article44/ipdee.html
            
成都網(wǎng)站建設(shè)公司_創(chuàng)新互聯(lián),為您提供定制網(wǎng)站、網(wǎng)站收錄、品牌網(wǎng)站制作、建站公司、服務(wù)器托管、微信公眾號(hào)
聲明:本網(wǎng)站發(fā)布的內(nèi)容(圖片、視頻和文字)以用戶投稿、用戶轉(zhuǎn)載內(nèi)容為主,如果涉及侵權(quán)請(qǐng)盡快告知,我們將會(huì)在第一時(shí)間刪除。文章觀點(diǎn)不代表本網(wǎng)站立場,如需處理請(qǐng)聯(lián)系客服。電話:028-86922220;郵箱:631063699@qq.com。內(nèi)容未經(jīng)允許不得轉(zhuǎn)載,或轉(zhuǎn)載時(shí)需注明來源: 創(chuàng)新互聯(lián)
猜你還喜歡下面的內(nèi)容
