1. 程式人生 > >Go檔案操作

Go檔案操作

[TOC] 對於檔案,我們並不陌生,檔案是資料來源(儲存資料的地方)的一種,比如大家經常使用的word文件,txt檔案,Excel檔案...等等都是檔案。檔案最主要的作用就是儲存資料,它既可以儲存一張圖片,也可以儲存視訊,聲音...... 檔案在程式中是以流的形式來操作的。 ![](https://img2020.cnblogs.com/blog/720430/202006/720430-20200605222309306-1647050472.png) **流**:資料在資料來源(檔案)和程式(記憶體)之間經歷的路徑 輸出流:資料從程式(記憶體)到資料來源(檔案)的路徑 輸入流:資料從資料來源(檔案)到程式(記憶體)的路徑 輸入與輸出都是相對於記憶體而言的,從記憶體向外流就是輸出,從外部向記憶體流就是輸入 在Go中,我們操作檔案的方法在os包中,會經常使用到os.File結構體 [Go語言標準庫文件](https://studygolang.com/pkgdoc) ![](https://img2020.cnblogs.com/blog/720430/202006/720430-20200605222403814-1175317630.png) ![](https://img2020.cnblogs.com/blog/720430/202006/720430-20200605222435222-1135705862.png) ![](https://img2020.cnblogs.com/blog/720430/202006/720430-20200605222524234-1665009916.png) ### 示例1: 開啟和關閉檔案 ```go package main import ( "fmt" "os" ) func main() { //開啟檔案(/Users/xxx/Go/src/file.txt) //概念說明:file的叫法 //1.file 叫 file物件 //2.file 叫 file指標 //3.file 叫 file檔案控制代碼 file, err := os.Open("/Users/itbsl/Go/src/file.txt") if err != nil { fmt.Println("檔案開啟失敗,原因是:", err) //os.Exit(0) } defer func() { //檔案及時關閉 err = file.Close() if err != nil { fmt.Println("檔案關閉失敗,原因是", err) } }() } ``` ### 示例2: 開啟檔案並讀取內容 使用Read()函式按照位元組讀 ```go package main import ( "fmt" "io" "os" ) func main() { file, err := os.Open("./test.txt") if err != nil { fmt.Printf("open file failed, err:%v\n", err) return } defer func() { err = file.Close() if err != nil { fmt.Printf("close file failed, err:%v\n", err) } }() var content []byte var tmp = make([]byte, 128) for { n, err := file.Read(tmp) //為什麼是tmp[:n]而不是tmp[:]? //因為當讀取到最後一行的內容長度不足tmp的長度的時候 //新讀取的內容只會覆蓋前半部分上次讀取到的tmp的內容, //後半部分還是上一次讀取的內容,如果用tmp[:]就會導致 //後半部分久內容又會被重新賦值一次,這其實是錯誤的 content = append(content, tmp[:n]...) if err == io.EOF {//讀到檔案末尾 break } } fmt.Printf("讀取出來的內容為:\n") fmt.Printf("%q\n", string(content)) } ``` ### 示例3: 一次性讀取檔案 讀取檔案內容並顯示在終端,將檔案內容一次性讀取到終端,適用於檔案不大的情況。 ```go package main import ( "fmt" "io/ioutil" ) func main() { //開啟檔案,檔案路徑相對於GOPATH開始,或者寫全路徑(/Users/xxx/Go/src/file.txt) file, err := ioutil.ReadFile("src/file.txt") if err != nil { fmt.Println("檔案開啟失敗,原因是:", err) } fmt.Printf("%s", string(file)) } ``` ### 示例4: 帶緩衝的Reader讀檔案 讀取檔案的內容並顯示在終端(帶緩衝區的方式),使用`os.Open`, `file.Close`,`bufio.NewReader`,`reader.ReadString`函式和方法。適合讀取大檔案 1.使用ReadBytes方法 程式碼1: ```go package main import ( "bufio" "fmt" "io" "log" "os" ) func main() { file, err := os.Open("./test.txt") if err != nil { log.Fatalf("open file failed, err: %v\n", err) } defer func() { err = file.Close() if err != nil { log.Fatalf("close file failed, err: %v\n", err) } }() //定義變數result用來儲存讀取結果 var result string //建立一個帶有緩衝區的reader reader := bufio.NewReader(file) for { buf, err := reader.ReadBytes('\n') if err != nil && err == io.EOF { //EOF代表檔案的末尾 //注意:為什麼要判斷err是否等於io.EOF? //因為存在這種情況,檔案有內容的最後那一行尾部沒有換行 //當使用ReadBytes或者ReadString方法按照'\n'換行讀取時,讀到尾部沒有換行的這種情況時就會報io.EOF錯誤 //此時buf是讀取到了內容的,如果忽略掉了,那麼最終的讀取結果會少了最後一行的內容 result += string(buf) break } result += string(buf) } fmt.Println(result) } ``` 程式碼2: ```go package main import ( "bufio" "fmt" "io" "log" "os" ) func main() { file, err := os.Open("./test.txt") if err != nil { log.Fatalf("open file failed, err: %v\n", err) } defer func() { err = file.Close() if err != nil { log.Fatalf("close file failed, err: %v\n", err) } }() //定義變數result用來儲存讀取結果 var result string //建立一個帶有緩衝區的reader reader := bufio.NewReader(file) for { buf, err := reader.ReadBytes('\n') if err != nil { if err == io.EOF { //EOF代表檔案的末尾 //注意:為什麼要判斷err是否等於io.EOF? //因為存在這種情況,檔案有內容的最後那一行尾部沒有換行 //當使用ReadBytes或者ReadString方法按照'\n'換行讀取時,讀到尾部沒有換行的這種情況時就會報io.EOF錯誤 //此時buf是讀取到了內容的,如果忽略掉了,那麼最終的讀取結果會少了最後一行的內容 result += string(buf) break } else { log.Fatalf("ReadBytes failed, err: %v\n", err) } } result += string(buf) } fmt.Println(result) } ``` 2.ReadString方法 ```go package main import ( "bufio" "fmt" "io" "os" ) func main() { //開啟檔案 file, err := os.Open("./files/test.txt") if err != nil { fmt.Println("檔案開啟失敗,原因是:", err) return } //當函式退出時,要及時的關閉file defer func() { //檔案及時關閉 err = file.Close() if err != nil { fmt.Println("檔案關閉失敗,原因是", err) } }() //建立一個 *Reader,是帶緩衝的 reader := bufio.NewReader(file) var result string //迴圈讀取檔案內容 for { str, err := reader.ReadString('\n') //讀到一個換行就結束 result += str if err == io.EOF {//io.EOF代表檔案的末尾 //注意:如果檔案最後一行文字沒有換行,則會一直讀取到檔案末尾, //所以即使是判斷讀到了檔案末尾,也要把讀取的內容輸出一下 break } } fmt.Println(result) } ``` ### 示例5: 建立檔案並寫入內容 ![](https://img2020.cnblogs.com/blog/720430/202006/720430-20200605222628825-589104591.png) 第二個引數:檔案代開模式(可以組合);第三個引數:許可權控制(如0755) ![](https://img2020.cnblogs.com/blog/720430/202006/720430-20200605222639678-1763642532.png) ```go package main import ( "fmt" "os" ) func main() { //1.建立檔案file.txt file, err := os.OpenFile("src/file.txt", os.O_WRONLY | os.O_CREATE, 0755) if err != nil { fmt.Println("檔案開啟/建立失敗,原因是:", err) return } defer func() { err = file.Close() if err != nil { fmt.Println("檔案關閉失敗,原因是:", err) } }() //寫入資料 var str = "暗黑西遊獅駝嶺,鬥戰勝佛孫悟空。\n" for i := 0; i < 5; i++ { file.WriteString(str) } } ``` ### 示例6: 寫檔案的四種方式 1.使用WriteAt()搭配Seek()方法實現寫檔案功能 ```go package main import ( "io" "log" "os" ) func main() { file, err := os.OpenFile("./test.txt", os.O_RDWR|os.O_CREATE, 0755) if err != nil { log.Fatalf("open file failed, err: %v\n", err) } defer func() { err = file.Close() if err != nil { log.Fatalf("close file failed, err: %v\n", err) } }() //Seek(): 修改檔案的讀寫指標位置. //引數1: 偏移量. 正:向檔案尾部偏移, 負:向檔案頭部偏移 //引數2: 偏移起始位置 // io.SeekStart: 檔案起始位置 // io.SeekCurrent: 檔案當前位置 // io.SeekEnd: 檔案結尾位置 //返回值:表示從檔案起始位置,到當前檔案讀寫指標位置的偏移量。 //WriteAt(): 在檔案指定偏移位置,寫入[]byte,通常搭配Seek() //引數1: 待寫入的資料 //引數2: 偏移量 //返回: 實際寫出的位元組數 for i := 0; i < 5; i++ { offset, _ := file.Seek(-3, io.SeekEnd) _, _ = file.WriteAt([]byte("你好"), offset) } } ``` **注意**: 由於使用的OpenFile函式開啟的檔案,所以在選擇開啟模式的時候不能選擇`os.O_APPEND`模式,因為該模式表示的是在檔案末尾追加,這與WriteAt在指定的位置寫是想衝突的,雖然我在測試的時候加上`os.O_APPEND`模式並沒有報錯,但是程式碼執行完之後發現,想要寫入的內容並沒有真正的寫入到檔案中。 寫入前 ![](https://img2020.cnblogs.com/blog/720430/202006/720430-20200605222701905-679738111.png) 寫入後 ![](https://img2020.cnblogs.com/blog/720430/202006/720430-20200605222711489-1916066275.png) 2.一次性寫檔案 ```go package main import ( "io/ioutil" "log" ) func main() { str := "hello樹先生" //如果檔案已存在,則會清空原來的內容,寫入新內容,如果檔案不存在,則會建立檔案並寫入內容 err := ioutil.WriteFile("./test.txt", []byte(str), 0755) if err != nil { log.Fatalf("寫入檔案錯誤,錯誤為:%v\n", err) } } ``` 3.使用帶緩衝的方式寫檔案 ```go package main import ( "bufio" "fmt" "os" ) func main() { //1.建立檔案file.txt file, err := os.OpenFile("src/file.txt", os.O_WRONLY | os.O_CREATE | os.O_TRUNC, 0755) defer func() { err = file.Close() if err != nil { fmt.Println("檔案關閉失敗,原因是:", err) } }() if err != nil { fmt.Println("檔案建立失敗,原因是:", err) return } //寫入資料 var str = "你好,世界\n" //寫入時,使用帶快取的*Writer writer := bufio.NewWriter(file) for i := 0; i < 5; i++ { writer.WriteString(str) } //因為writer是帶快取的,因此在呼叫writeString方法時,其實內容是先寫入到快取 //因此需要呼叫Flush方法,將快取資料寫入到檔案中,否則檔案中會丟失資料 writer.Flush() } ``` ### 示例7: 把一個檔案內容寫入到另一個檔案 ```go package main import ( "fmt" "io/ioutil" ) func main() { //開啟檔案,檔案路徑相對於GOPATH開始,或者寫全路徑(/Users/xxx/Go/src/file.txt) data, err := ioutil.ReadFile("src/1.txt") if err != nil { fmt.Println("檔案開啟失敗,原因是:", err) } err = ioutil.WriteFile("src/2.txt", data, 0755) if err != nil { fmt.Println("檔案寫入失敗,原因是:", err) } } ``` ### 示例8:使用bufio獲取使用者輸入 ```go package main import ( "bufio" "fmt" "os" ) func main() { var s string var reader = bufio.NewReader(os.Stdin) s, _ = reader.ReadString('\n') fmt.Printf("讀取到的內容為:%s\n", s) } ``` ### 示例9: 判斷檔案或目錄是否存在 Go判斷檔案或資料夾是否存在的方法為使用os.Stat()函式返回的錯誤值進行判斷: (1)如果返回的錯誤為nil,說明檔案或資料夾存在 (2)如果返回的型別使用os.IsNotExist()判斷為true,說明檔案或資料夾不存在 (3)如果返回的錯誤為其它型別,則不確定是否存在 ```go package main import ( "fmt" "os" ) func main() { isExist, err := isFileExists("src/sfile.txt") if err != nil { fmt.Println("發生錯誤:", err) } if isExist { fmt.Println("存在") } else { fmt.Println("不存在") } } //判斷檔案或者目錄是否存在 func isFileExists(path string) (bool, error) { _, err := os.Stat(path) if err == nil { return true, nil } if os.IsNotExist(err) { return false, nil } return false, err } ``` ### 示例10: 拷貝檔案、圖片音視訊 io.Copy方法 ```go package main import ( "fmt" "io" "os" ) func CopyFile(srcFileName string, dstFileName string) (int64, error) { //原始檔處理 srcFile, err := os.Open(srcFileName) defer func() { err = srcFile.Close() if err != nil { fmt.Println("原始檔關閉失敗,原因是:", err) } }() if err != nil { fmt.Println("原始檔開啟失敗,原因是:", err) return 0, err } //目標檔案處理 dstFile, err := os.OpenFile(dstFileName, os.O_CREATE | os.O_WRONLY, 0755) defer func() { err = dstFile.Close() if err != nil { fmt.Println("目標檔案關閉失敗,原因是:", err) } }() if err != nil { fmt.Println("目標檔案開啟失敗,原因是:", err) return 0, err } return io.Copy(dstFile, srcFile) } func main() { result, err := CopyFile("src/dst.jpeg", "src/哈哈.jpeg") if err == nil { fmt.Println("拷貝成功!拷貝的位元組數為: ", result) } } ``` 對於大檔案,我們還可以採用下面的方式 ```go package main import ( "io" "log" "os" ) func CopyFile(srcFileName string, dstFileName string) { //開啟原始檔 srcFile, err := os.Open(srcFileName) if err != nil { log.Fatalf("原始檔讀取失敗,原因是:%v\n", err) } defer func() { err = srcFile.Close() if err != nil { log.Fatalf("原始檔關閉失敗,原因是:%v\n", err) } }() //建立目標檔案,稍後會向這個目標檔案寫入拷貝內容 distFile, err := os.Create(dstFileName) if err != nil { log.Fatalf("目標檔案建立失敗,原因是:%v\n", err) } defer func() { err = distFile.Close() if err != nil { log.Fatalf("目標檔案關閉失敗,原因是:%v\n", err) } }() //定義指定長度的位元組切片,每次最多讀取指定長度 var tmp = make([]byte, 1024*4) //迴圈讀取並寫入 for { n, err := srcFile.Read(tmp) n, _ = distFile.Write(tmp[:n]) if err != nil { if err == io.EOF {//讀到了檔案末尾,並且寫入完畢,任務完成返回(關閉檔案的操作由defer來完成) return } else { log.Fatalf("拷貝過程中發生錯誤,錯誤原因為:%v\n", err) } } } } func main() { CopyFile("./worm.mp4", "./dist.mp4") } ``` ### 示例11: 遍歷目錄 #### 遍歷目錄 ```go package main //我們讀寫的檔案一般存放於目錄中.因此,有時需要指定到某一個目錄下,根據目錄儲存的狀況 //再進行檔案的特定操作.接下來我們看看目錄的基本操作方法. import ( "fmt" "log" "os" ) //開啟目錄 //開啟目錄我們也使用OpenFile函式,但要指定不同的引數來通知系統,要開啟的是一個目錄檔案. //func OpenFile(name string, flag int, perm FileMode) (file *File, err error) //引數1: name,表示要開啟的目錄名稱.使用絕對路徑較多 //引數2: flag,表示開啟檔案的讀寫模式 //引數3: perm,表示開啟許可權.但對於目錄來說有所不同,通常傳os.ModeDir. //返回值:由於是操作目錄,所以file是指向目錄的檔案指標.err中儲存錯誤資訊 //讀目錄內容 //這與讀檔案有所不同.目錄中存放的是檔名和子目錄名.所以使用Readdir函式 //func (f *File) Readdir(n int) (fi []FileInfo, err error) //如果n>0,Readdir函式會返回一個最多n個成員的切片。這時,如果Readdir返回一個空切片, //它會返回一個非nil的錯誤說明原因。如果到達了目錄f的結尾,返回值err會是io.EOF。 // //如果n<=0,Readdir函式返回目錄中剩餘所有檔案物件的FileInfo構成的切片。 //此時,如果Readdir呼叫成功(讀取所有內容直到結尾),它會返回該切片和nil的錯誤值。 //如果在到達結尾前遇到錯誤,會返回之前成功讀取的FileInfo構成的切片和該錯誤。 func main() { //不推薦,因為通過檢視ioutil.ReadDir()函式可知,官方使用的是os.Open()函式開啟的目錄 //file, err := os.OpenFile("./dir", os.O_RDWR, os.ModeDir) file, err := os.Open("./dir") if err != nil { log.Fatalf("檔案開啟失敗,原因是:%v\n", err) } defer func() { err = file.Close() if err != nil { log.Fatalf("檔案關閉失敗,原因是:%v\n", err) } }() //Readdir方法返回一個FileInfo介面型別的切片和一個error型別的錯誤 infos, err := file.Readdir(-1) for _, info := range infos { fmt.Printf("%v, %v\n", info.Name(), info.IsDir()) } } ``` #### 僅遍歷目錄,忽略檔案 方法1:使用os包 ```go package main import ( "fmt" "os" ) var dirNames = make([]string, 0, 50) var pathSeparator = string(os.PathSeparator) func traverseDir(filePath string) error { file, err := os.Open(filePath) if err != nil { return err } fileInfo, err := file.Readdir(0) if err != nil { return err } for _, value := range fileInfo { if value.IsDir() { dirNames = append(dirNames, value.Name()) err = traverseDir(filePath+pathSeparator+value.Name()) if err != nil { return err } } } return err } func main() { var filePath = "./dir" err := traverseDir(filePath) if err != nil { fmt.Println(err) } fmt.Println(dirNames) } ``` 方法2:使用ioutil包 ```go package main import ( "fmt" "io/ioutil" "os" ) var dirNames = make([]string, 0, 50) var pathSeparator = string(os.PathSeparator) func traverseDir(filePath string) error { fileInfos, err := ioutil.ReadDir(filePath) if err != nil { return err } for _, fileInfo :=range fileInfos { if fileInfo.IsDir() { dirNames = append(dirNames, fileInfo.Name()) err = traverseDir(filePath+pathSeparator+fileInfo.Name()) if err != nil { return err } } } return err } func main() { var filePath = "./dir" err := traverseDir(filePath) if err != nil { fmt.Println(err) } fmt.Println(dirNames) } ``` ### 示例12: 修改檔名 ```go package main import ( "fmt" "io/ioutil" "os" "strings" ) var pathSeparator = string(os.PathSeparator) //重新命名檔案 func renameFileName(filePath string, old string, new string) error { files, err := ioutil.ReadDir(filePath) if err != nil { return err } for _, fileInfo := range files { if !fileInfo.IsDir() { err = os.Rename(filePath + pathSeparator + fileInfo.Name(), filePath + pathSeparator + strings.Replace(fileInfo.Name(), old, new, -1), ) if err != nil { return err } } } return err } func main() { var filePath = "./dir" err := renameFileName(filePath, "f", "kkk") if err != nil { fmt.Printf("錯誤: %v\n", err) } } ``` ### 示例13:建立目錄 ```go package main import ( "fmt" "os" ) func main() { //Mkdir使用指定的許可權和名稱建立一個目錄。如果出錯,會返回*PathError底層型別的錯誤。 err := os.Mkdir("./foo", 0755) if os.IsExist(err) { fmt.Println("目錄已存在") return } //MkdirAll使用指定的許可權和名稱建立一個目錄,包括任何必要的上級目錄,並返回nil,否則返回錯誤。 //許可權位perm會應用在每一個被本函式建立的目錄上。如果path指定了一個已經存在的目錄,MkdirAll不做任何操作並返回nil。 err = os.MkdirAll("./foo/bar", 0755) if err != nil { fmt.Printf("%v\n", err) return } } ``` ### 示例14:刪除檔案 ```go package main import ( "fmt" "os" ) func main() { //Remove刪除name指定的檔案或目錄。如果出錯,會返回*PathError底層型別的錯誤。 //該方法不能刪除非空目錄,如果想刪除目錄以及目錄下的所有檔案,可以使用RemoveAll err := os.Remove("./def") if os.IsNotExist(err) { fmt.Println("您要刪除的檔案或目錄不存在") return } if err != nil { fmt.Println(err) } //RemoveAll刪除path指定的檔案,或目錄及它包含的任何下級物件。 //它會嘗試刪除所有東西,除非遇到錯誤並返回。 //如果path指定的物件不存在,RemoveAll會返回nil而不返回錯誤。 err = os.RemoveAll("./def") if err != nil { fmt.Println(err)