這篇文章主要講解了“Golang文件操作的方法有哪些”,文中的講解內(nèi)容簡單清晰,易于學(xué)習(xí)與理解,下面請大家跟著小編的思路慢慢深入,一起來研究和學(xué)習(xí)“Golang文件操作的方法有哪些”吧!
十載專注成都網(wǎng)站制作,企業(yè)網(wǎng)站設(shè)計(jì),個(gè)人網(wǎng)站制作服務(wù),為大家分享網(wǎng)站制作知識、方案,網(wǎng)站設(shè)計(jì)流程、步驟,成功服務(wù)上千家企業(yè)。為您提供網(wǎng)站建設(shè),網(wǎng)站制作,網(wǎng)頁設(shè)計(jì)及定制高端網(wǎng)站建設(shè)服務(wù),專注于企業(yè)網(wǎng)站設(shè)計(jì),高端網(wǎng)頁制作,對門簾等多個(gè)方面,擁有豐富的網(wǎng)站運(yùn)維經(jīng)驗(yàn)。
最近做的一點(diǎn)事情,用到了golang中不少文件操作的相關(guān)內(nèi)容,創(chuàng)建,刪除,遍歷,壓縮之類的,這里整理整理,希望能掌握的系統(tǒng)一點(diǎn),把模糊的地方理清楚。
創(chuàng)建文件的時(shí)候,一定要注意權(quán)限問題,一般默認(rèn)的文件權(quán)限是 0666 關(guān)于權(quán)限的相關(guān)內(nèi)容,具體可以參考鳥叔p141 這里還是再回顧下,文件屬性 r w x r w x r w x,第一位是文件屬性,一般常用的 "-" 表示的是普通文件,"d"表示的是目錄,golang里面使用os.Create
創(chuàng)建文件的時(shí)候貌似只能使用0xxx的形式。比如0666就表示創(chuàng)建了一個(gè)普通文件,文件所有者的權(quán)限,文件所屬用戶組的權(quán)限,以及其他人對此文件的權(quán)限都是110表示可讀可寫,不可執(zhí)行。
文件刪除的時(shí)候,不管是普通文件還是目錄文件,都可以用err:=os.Remove(filename)
這樣的操作來執(zhí)行。當(dāng)然要是想移除整個(gè)文件夾,直接使用RemoveAll(path string)
操作即可??梢钥匆幌翿emoveAll函數(shù)的內(nèi)部實(shí)現(xiàn),整體上就是遍歷,遞歸的操作過程,其他的類似的文件操作都可以用類似的模板來實(shí)現(xiàn),下面以RemoveAll函數(shù)為模板,進(jìn)行一下具體的分析,注意考慮到各種情況:
func RemoveAll(path string) error { // Simple case: if Remove works, we're done. //先嘗試一下remove如果是普通文件 直接刪掉 報(bào)錯(cuò) 則可能是目錄中還有子文件 err := Remove(path) //沒錯(cuò)或者路徑不存在 直接返回 nil if err == nil || IsNotExist(err) { return nil } // Otherwise, is this a directory we need to recurse into? // 目錄里面還有文件 需要遞歸處理 // 注意Lstat和stat函數(shù)的區(qū)別,兩個(gè)都是返回文件的狀態(tài)信息 //Lstat多了處理Link文件的功能,會返回Linked文件的信息,而state直接返回的是Link文件所指向的文件的信息 dir, serr := Lstat(path) if serr != nil { if serr, ok := serr.(*PathError); ok && (IsNotExist(serr.Err) || serr.Err == syscall.ENOTDIR) { return nil } return serr } //不是目錄 if !dir.IsDir() { // Not a directory; return the error from Remove. return err } // Directory. fd, err := Open(path) if err != nil { if IsNotExist(err) { // Race. It was deleted between the Lstat and Open. // Return nil per RemoveAll's docs. return nil } return err } // Remove contents & return first error. err = nil //遞歸遍歷目錄中的文件 如果參數(shù)n<=0則將全部的信息存入到一個(gè)slice中返回 //如果參數(shù)n>0則至多返回n個(gè)元素的信息存入到slice當(dāng)中 //還有一個(gè)類似的函數(shù)是Readdir 這個(gè)返回的是 目錄中的內(nèi)容的Fileinfo信息 for { names, err1 := fd.Readdirnames(100) for _, name := range names { err1 := RemoveAll(path + string(PathSeparator) + name) if err == nil { err = err1 } } //遍歷到最后一個(gè)位置 if err1 == io.EOF { break } // If Readdirnames returned an error, use it. if err == nil { err = err1 } if len(names) == 0 { break } } // Close directory, because windows won't remove opened directory. fd.Close() //遞歸結(jié)束 當(dāng)前目錄下位空 刪除當(dāng)前目錄 // Remove directory. err1 := Remove(path) if err1 == nil || IsNotExist(err1) { return nil } if err == nil { err = err1 } return err }
這一部分較多的涉及I/O的相關(guān)操作,系統(tǒng)的介紹放在I/O那部分來整理,大體上向文件中讀寫內(nèi)容的時(shí)候有三種方式:
1、在使用f, err := os.Open(file_path)
打開文件之后直接使用 f.read() f.write()
結(jié)合自定義的buffer每次從文件中讀入/讀出固定的內(nèi)容
2、使用ioutl的readFile和writeFile方法
3、使用bufio采用帶有緩存的方式進(jìn)行讀寫,比如通過info:=bufio.NewReader(f)
將實(shí)現(xiàn)了io.Reader的接口的實(shí)例加載上來之后,就可以使用info.ReadLine()來每次實(shí)現(xiàn)一整行的讀取,直到err信息為io.EOF時(shí),讀取結(jié)束
這個(gè)blog對三種文件操作的讀入速度進(jìn)行了比較,貌似讀取大文件的時(shí)候采用ioutil的時(shí)候效率要高些。
每種方式都有不同的適用情況,下面是分別用三種方式進(jìn)行讀出操作的例子,對于寫入文件的操作,可以參考讀出操作來進(jìn)行:
package main import ( "bufio" "fmt" "io" "io/ioutil" "os" ) func check(e error) { if e != nil { panic(e) } } func main() { //查看當(dāng)前的工作目錄路徑 得到測試文件的絕對路徑 current_dir, _ := os.Getwd() fmt.Println(current_dir) file_path := current_dir + "/temp.txt" //方式一: //通過ioutil直接通過文件名來加載文件 //一次將整個(gè)文件加載進(jìn)來 粒度較大 err返回為nil的時(shí)候 文件會被成功加載 dat, err := ioutil.ReadFile(file_path) //若加載的是一個(gè)目錄 會返回[]os.FileInfo的信息 //ioutil.ReadDir() check(err) //the type of data is []uint fmt.Println(dat) //將文件內(nèi)容轉(zhuǎn)化為string輸出 fmt.Println(string(dat)) //方式二: //通過os.Open的方式得到 *File 類型的變量 //貌似是一個(gè)指向這個(gè)文件的指針 通過這個(gè)指針 可以對文件進(jìn)行更細(xì)粒度的操作 f, err := os.Open(file_path) check(err) //手工指定固定大小的buffer 每次通過buffer來 進(jìn)行對應(yīng)的操作 buffer1 := make([]byte, 5) //從文件f中讀取len(buffer1)的信息到buffer1中 返回值n1是讀取的byte的長度 n1, err := f.Read(buffer1) check(err) fmt.Printf("%d bytes: %s\n", n1, string(buffer1)) //通過f.seek進(jìn)行更精細(xì)的操作 第一個(gè)參數(shù)表示offset為6 第二個(gè)參數(shù)表示文件起始的相對位置 //之后再讀就從o2位置開始往后讀信息了 o2, err := f.Seek(6, 0) check(err) buffer2 := make([]byte, 2) //讀入了n2長度的信息到buffer2中 n2, err := f.Read(buffer2) check(err) fmt.Printf("%d bytes after %d position : %s\n", n2, o2, string(buffer2)) //通過io包種的函數(shù) 也可以實(shí)現(xiàn)類似的功能 o3, err := f.Seek(6, 0) check(err) buffer3 := make([]byte, 2) n3, err := io.ReadAtLeast(f, buffer3, len(buffer3)) check(err) fmt.Printf("%d bytes after %d position : %s\n", n3, o3, string(buffer3)) //方式三 //通過bufio包來進(jìn)行讀取 bufio中又許多比較有用的函數(shù) 比如一次讀入一整行的內(nèi)容 //調(diào)整文件指針的起始位置到最開始的地方 _, err = f.Seek(10, 0) check(err) r4 := bufio.NewReader(f) //讀出從頭開始的5個(gè)字節(jié) b4, err := r4.Peek(5) check(err) //fmt.Println(string(b4)) fmt.Printf("5 bytes : %s\n", string(b4)) //調(diào)整文件到另一個(gè)地方 _, err = f.Seek(0, 0) check(err) r5 := bufio.NewReader(f) //讀出從指針?biāo)肝恢瞄_始的5個(gè)字節(jié) b5, err := r5.Peek(5) check(err) //fmt.Println(string(b4)) fmt.Printf("5 bytes : %s\n", string(b5)) //測試bufio的其他函數(shù) for { //讀出內(nèi)容保存為string 每次讀到以'\n'為標(biāo)記的位置 line, err := r5.ReadString('\n') fmt.Print(line) if err == io.EOF { break } } //ReadLine() ReadByte() 的用法都是類似 一般都是當(dāng)err為io.EOF的時(shí)候 //讀入內(nèi)容就結(jié)束 //感覺實(shí)際用的時(shí)候 還是通過方式三比較好 粒度正合適 還有多種處理輸入的方式 f.Close() }
文件打包,文件解壓,文件遍歷,這些相關(guān)的操作基本上都可以參考RemoveAll的方式來進(jìn)行,就是遞歸加遍歷的方式。
下面是文件壓縮的一個(gè)實(shí)現(xiàn):
//將文件夾中的內(nèi)容打包成 .gz.tar 文件 package main import ( "archive/tar" "compress/gzip" "fmt" "io" "os" ) //將fi文件的內(nèi)容 寫入到 dir 目錄之下 壓縮到tar文件之中 func Filecompress(tw *tar.Writer, dir string, fi os.FileInfo) { //打開文件 open當(dāng)中是 目錄名稱/文件名稱 構(gòu)成的組合 filename := dir + "/" + fi.Name() fmt.Println("the last one:", filename) fr, err := os.Open(filename) fmt.Println(fr.Name()) if err != nil { panic(err) } defer fr.Close() hdr, err := tar.FileInfoHeader(fi, "") hdr.Name = fr.Name() if err = tw.WriteHeader(hdr); err != nil { panic(err) } //bad way // //信息頭部 生成tar文件的時(shí)候要先寫入tar結(jié)構(gòu)體 // h := new(tar.Header) // //fmt.Println(reflect.TypeOf(h)) // h.Name = fi.Name() // h.Size = fi.Size() // h.Mode = int64(fi.Mode()) // h.ModTime = fi.ModTime() // //將信息頭部的內(nèi)容寫入 // err = tw.WriteHeader(h) // if err != nil { // panic(err) // } //copy(dst Writer,src Reader) _, err = io.Copy(tw, fr) if err != nil { panic(err) } //打印文件名稱 fmt.Println("add the file: " + fi.Name()) } //將目錄中的內(nèi)容遞歸遍歷 寫入tar 文件中 func Dircompress(tw *tar.Writer, dir string) { fmt.Println(dir) //打開文件夾 dirhandle, err := os.Open(dir + "/") //fmt.Println(dir.Name()) //fmt.Println(reflect.TypeOf(dir)) if err != nil { panic(err) } defer dirhandle.Close() fis, err := dirhandle.Readdir(0) //fis的類型為 []os.FileInfo //也可以通過Readdirnames來讀入所有子文件的名稱 //但是這樣 再次判斷是否為文件的時(shí)候 需要通過Stat來得到文件的信息 //返回的就是os.File的類型 if err != nil { panic(err) } //遍歷文件列表 每一個(gè)文件到要寫入一個(gè)新的*tar.Header //var fi os.FileInfo for _, fi := range fis { fmt.Println(fi.Name()) if fi.IsDir() { newname := dir + "/" + fi.Name() fmt.Println("using dir") fmt.Println(newname) //這個(gè)樣直接continue就將所有文件寫入到了一起 沒有層級結(jié)構(gòu)了 //Filecompress(tw, dir, fi) Dircompress(tw, newname) } else { //如果是普通文件 直接寫入 dir 后面已經(jīng)有了 / Filecompress(tw, dir, fi) } } } //在tardir目錄中創(chuàng)建一個(gè).tar.gz文件 存放壓縮之后的文件 func Dirtotar(sourcedir string, tardir string, tarname string) { //file write 在tardir目錄下創(chuàng)建 fw, err := os.Create(tardir + "/" + tarname + ".tar.gz") //type of fw is *os.File // fmt.Println(reflect.TypeOf(fw)) if err != nil { panic(err) } defer fw.Close() //gzip writer gw := gzip.NewWriter(fw) defer gw.Close() //tar write tw := tar.NewWriter(gw) fmt.Println("源目錄:", sourcedir) Dircompress(tw, sourcedir) //通過控制寫入流 也可以控制 目錄結(jié)構(gòu) 比如將當(dāng)前目錄下的Dockerfile文件單獨(dú)寫在最外層 fileinfo, err := os.Stat("tarrepo" + "/" + "testDockerfile") fmt.Println("the file name:", fileinfo.Name()) if err != nil { panic(err) } //比如這里將Dockerfile放在 tar包中的最外層 會注冊到tar包中的 /tarrepo/testDockerfile 中 Filecompress(tw, "tarrepo", fileinfo) //Filecompress(tw, "systempdir/test_testwar_tar/", fileinfo) fmt.Println("tar.gz packaging OK") } func main() { // workdir, _ := os.Getwd() // fmt.Println(workdir) Dirtotar("testdir", "tarrepo", "testtar") }
之前可能也沒有注意 OpenFile函數(shù)與Open函數(shù)的區(qū)別 Openfile函數(shù)可以指定返回的文件描述符的權(quán)限,通過O_RDONLY、O_WRONLY、O_RDWR 等等來控制。而Open函數(shù)在其內(nèi)部是調(diào)用OpenFile函數(shù)的,默認(rèn)的情況是O_RDONLY權(quán)限,如果僅僅用Open函數(shù)返回文件描述符,之后再對文件進(jìn)行寫操作的話,就會返回 bad file descriptor 的錯(cuò)誤,這個(gè)還是應(yīng)該多留意一下的,細(xì)節(jié)問題要弄仔細(xì),本質(zhì)上來說是os中的文件描述符的問題。
refer to this :https://www.socketloop.com/tutorials/golang-copy-directory-including-sub-directories-files
感謝各位的閱讀,以上就是“Golang文件操作的方法有哪些”的內(nèi)容了,經(jīng)過本文的學(xué)習(xí)后,相信大家對Golang文件操作的方法有哪些這一問題有了更深刻的體會,具體使用情況還需要大家實(shí)踐驗(yàn)證。這里是創(chuàng)新互聯(lián),小編將為大家推送更多相關(guān)知識點(diǎn)的文章,歡迎關(guān)注!