在os
包中有一個FileInfo
接口它包含了一個文件的基本信息,以下golang
// A FileInfo describes a file and is returned by Stat and Lstat. type FileInfo interface { Name() string // base name of the file Size() int64 // length in bytes for regular files; system-dependent for others Mode() FileMode // file mode bits ModTime() time.Time // modification time IsDir() bool // abbreviation for Mode().IsDir() Sys() interface{} // underlying data source (can return nil) }
從上面描述能夠看到FileInfo描述一個文件而且被Stat和Lstat方法返回windows
從os
包中能夠看到這兩個函數的定義,記住裏面的兩個函數statNolog
和lstatNolog
app
// Stat returns a FileInfo describing the named file. // If there is an error, it will be of type *PathError. func Stat(name string) (FileInfo, error) { testlog.Stat(name) return statNolog(name) } // Lstat returns a FileInfo describing the named file. // If the file is a symbolic link, the returned FileInfo // describes the symbolic link. Lstat makes no attempt to follow the link. // If there is an error, it will be of type *PathError. func Lstat(name string) (FileInfo, error) { testlog.Stat(name) return lstatNolog(name) }
一樣在os包中定義了fileState
結構體less
// A fileStat is the implementation of FileInfo returned by Stat and Lstat. type fileStat struct { name string size int64 mode FileMode modTime time.Time sys syscall.Stat_t }
在types_unix.go
文件中定義了以下方法dom
func (fs *fileStat) Size() int64 { return fs.size } func (fs *fileStat) Mode() FileMode { return fs.mode } func (fs *fileStat) ModTime() time.Time { return fs.modTime } func (fs *fileStat) Sys() interface{} { return &fs.sys }
在types.go
中還定義如下兩個方法ide
func (fs *fileStat) Name() string { return fs.name } func (fs *fileStat) IsDir() bool { return fs.Mode().IsDir() }
能夠看到這個結構體恰好實現了FileInfo
接口函數
實際上,進入statNolog
和lstatNolog
可知,Stat
和Lsate
函數就是返回了指向fileStat
的指針oop
// statNolog stats a file with no test logging. func statNolog(name string) (FileInfo, error) { var fs fileStat err := syscall.Stat(name, &fs.sys) if err != nil { return nil, &PathError{"stat", name, err} } fillFileStatFromSys(&fs, name) return &fs, nil } // lstatNolog lstats a file with no test logging. func lstatNolog(name string) (FileInfo, error) { var fs fileStat err := syscall.Lstat(name, &fs.sys) if err != nil { return nil, &PathError{"lstat", name, err} } fillFileStatFromSys(&fs, name) return &fs, nil }
因此本質上就是利用fileStat
結構體存放文件相關信息,並作操做ui
介紹了這麼多,寫一個golang讀取文件信息的例子,(當前目錄下的go.mod文件)this
package main import ( "fmt" "os" ) func main() { fileInfo, err := os.Stat("go.mod") if err != nil { println("讀取文件出錯") return } fmt.Println("文件名:", fileInfo.Name()) fmt.Println("文件大小:", fileInfo.Size()) fmt.Println("文件權限:", fileInfo.Mode()) fmt.Println("文件修改時間:", fileInfo.ModTime()) fmt.Println("文件是不是目錄:", fileInfo.IsDir()) }
輸出
文件名: go.mod 文件大小: 24 文件權限: -rw-r--r-- 文件修改時間: 2020-06-16 09:04:23.418186928 +0800 CST 文件是不是目錄: false
其實上面方法就是對應FileInfo
接口中的方法,可是少了一個Sys
方法,經過這個方法你能夠得到更加詳細的信息,這個比較特殊,若是你直接打印會獲得相似以下的輸出:
fmt.Println(fileInfo.Sys()) // &{2064 23337987 1 33188 1000 1000 0 0 24 4096 8 {1592269464 801520234} {1592269463 418186928} {1592269463 418186928} [0 0 0]}
其實Sys
方法就是返回了fileStat
的sys
屬性,上面咱們已經看過fileStat
的定義,能夠發現sys
屬性就是一個syscall.Stat_t
類型
這是Stat_t
的定義
type Stat_t struct { Dev uint64 Ino uint64 Nlink uint64 Mode uint32 Uid uint32 Gid uint32 X__pad0 int32 Rdev uint64 Size int64 Blksize int64 Blocks int64 Atim Timespec Mtim Timespec Ctim Timespec X__unused [3]int64 }
能夠經過反射來獲取上述屬性的信息,以獲取 Ctim 爲例:
package main import ( "fmt" "os" "reflect" ) func main() { fileInfo, err := os.Stat("go.mod") if err != nil { println("讀取文件出錯") return } Ctim := reflect.ValueOf(fileInfo.Sys()).Elem().FieldByName("Ctim").Field(0) fmt.Println(Ctim) // 1592269463 }
也許有人會奇怪,爲何fileStat
結構體裏沒有文件路徑相關信息,其實者不難理解,咱們既然可以打開一個文件,前提就是咱們知道這個文件放在哪裏.而我接下來要作的操做知識針對文件路徑,而不是文件自己.
針對路徑golang中有一個專門的包path
經常使用方法以下
方法名 | 功能 |
---|---|
filepath.IsAbs() | 判斷是否爲絕對路徑 |
filepath.Rel() | 獲取相對路徑 |
filepath.Abs() | 獲取絕對路徑 |
path.Join() | 拼接路徑 |
示例
package main import ( "fmt" "path" "path/filepath" ) func main() { path1 := "/home/kain/Documents/code/go_module/file_io/main.go" path2 := "./go.mod" path3 := "go.mod" fmt.Println(filepath.IsAbs(path1)) // true fmt.Println(filepath.IsAbs(path2)) // false fmt.Println(filepath.IsAbs(path3)) // false fmt.Println(filepath.Rel("/home/kain", path1)) // Documents/code/go_module/file_io/main.go <nil> fmt.Println(filepath.Abs(path1)) // /home/kain/Documents/code/go_module/file_io/main.go <nil> fmt.Println(filepath.Abs(path2)) // /home/kain/Documents/code/go_module/file_io/go.mod <nil> fmt.Println(filepath.Abs(path3)) // /home/kain/Documents/code/go_module/file_io/go.mod <nil> fmt.Println(path.Join(path1, ".")) // /home/kain/Documents/code/go_module/file_io/main.go fmt.Println(path.Join(path1, "..")) // /home/kain/Documents/code/go_module/file_io fmt.Println(path.Join("/home", path2)) // /home/go.mod }
golang中經過Stat
返回的錯誤,而後調用IsExist
或IsNotExist
來判斷是否存在
package main import "os" func main() { println(PathExist("test.txt")) // true println(PathExist("go.mod")) // false } func PathExist(path string) bool{ _, err := os.Stat(path) return os.IsNotExist(err) }
該函數用於建立一層目錄,定義以下
// Mkdir creates a new directory with the specified name and permission // bits (before umask). // If there is an error, it will be of type *PathError. func Mkdir(name string, perm FileMode) error { if runtime.GOOS == "windows" && isWindowsNulName(name) { return &PathError{"mkdir", name, syscall.ENOTDIR} } e := syscall.Mkdir(fixLongPath(name), syscallMode(perm)) if e != nil { return &PathError{"mkdir", name, e} } // mkdir(2) itself won't handle the sticky bit on *BSD and Solaris if !supportsCreateWithStickyBit && perm&ModeSticky != 0 { e = setStickyBit(name) if e != nil { Remove(name) return e } } return nil }
示例
err := os.MKdir("demo", 0777)
該函數用於建立多層目錄,定義以下
func MkdirAll(path string, perm FileMode) error { // Fast path: if we can tell whether path is a directory or file, stop with success or error. dir, err := Stat(path) if err == nil { if dir.IsDir() { return nil } return &PathError{"mkdir", path, syscall.ENOTDIR} } // Slow path: make sure parent exists and then call Mkdir for path. i := len(path) for i > 0 && IsPathSeparator(path[i-1]) { // Skip trailing path separator. i-- } j := i for j > 0 && !IsPathSeparator(path[j-1]) { // Scan backward over element. j-- } if j > 1 { // Create parent. err = MkdirAll(fixRootDirectory(path[:j-1]), perm) if err != nil { return err } } // Parent now exists; invoke Mkdir and use its result. err = Mkdir(path, perm) if err != nil { // Handle arguments like "foo/." by // double-checking that directory doesn't exist. dir, err1 := Lstat(path) if err1 == nil && dir.IsDir() { return nil } return err } return nil }
示例
err := os.MkdirAll("dir1/dir2/dir3", os.ModePerm) // os.ModePerm = 0777
用該方法建立文本文件,若是文件已經存在,會將其覆蓋,定義以下
func Create(name string) (*File, error) { return OpenFile(name, O_RDWR|O_CREATE|O_TRUNC, 0666) }
能夠發現,他就是經過打開文件的方式來建立文件,經過 O_TRUNC
標誌,來覆蓋原文件.
示例
f, err := os.Create("test.txt")
該方法用於刪除文件或者空目錄,定義以下
func Remove(name string) error { // System call interface forces us to know // whether name is a file or directory. // Try both: it is cheaper on average than // doing a Stat plus the right one. e := syscall.Unlink(name) if e == nil { return nil } e1 := syscall.Rmdir(name) if e1 == nil { return nil } // Both failed: figure out which error to return. // OS X and Linux differ on whether unlink(dir) // returns EISDIR, so can't use that. However, // both agree that rmdir(file) returns ENOTDIR, // so we can use that to decide which error is real. // Rmdir might also return ENOTDIR if given a bad // file path, like /etc/passwd/foo, but in that case, // both errors will be ENOTDIR, so it's okay to // use the error from unlink. if e1 != syscall.ENOTDIR { e = e1 } return &PathError{"remove", name, e} }
示例
err := os.Remove("demo")
該方法用於刪除目錄下面全部子節點,定義以下
// RemoveAll removes path and any children it contains. // It removes everything it can but returns the first error // it encounters. If the path does not exist, RemoveAll // returns nil (no error). // If there is an error, it will be of type *PathError. func RemoveAll(path string) error { return removeAll(path) }
removeAll()
func removeAll(path string) error { if path == "" { // fail silently to retain compatibility with previous behavior // of RemoveAll. See issue 28830. return nil } // The rmdir system call does not permit removing ".", // so we don't permit it either. if endsWithDot(path) { return &PathError{"RemoveAll", path, syscall.EINVAL} } // Simple case: if Remove works, we're done. err := Remove(path) if err == nil || IsNotExist(err) { return nil } // RemoveAll recurses by deleting the path base from // its parent directory parentDir, base := splitPath(path) parent, err := Open(parentDir) if IsNotExist(err) { // If parent does not exist, base cannot exist. Fail silently return nil } if err != nil { return err } defer parent.Close() if err := removeAllFrom(parent, base); err != nil { if pathErr, ok := err.(*PathError); ok { pathErr.Path = parentDir + string(PathSeparator) + pathErr.Path err = pathErr } return err } return nil }
示例
err := os.RemoveAll("dir1")
對文件進行讀寫操做確定要先打開文件
前面在建立文件時咱們已經瞭解過了文件的打開,使用函數os.OpenFile()
來實現,定義以下
// OpenFile is the generalized open call; most users will use Open // or Create instead. It opens the named file with specified flag // (O_RDONLY etc.). If the file does not exist, and the O_CREATE flag // is passed, it is created with mode perm (before umask). If successful, // methods on the returned File can be used for I/O. // If there is an error, it will be of type *PathError. func OpenFile(name string, flag int, perm FileMode) (*File, error) { testlog.Open(name) f, err := openFileNolog(name, flag, perm) if err != nil { return nil, err } f.appendMode = flag&O_APPEND != 0 return f, nil }
它的內部是調用openFileNolog
函數來實現,有興趣的同窗能夠去了解一下
它接收的三個參數分別是文件路徑,標誌和權限,
標誌就是打開方式,能夠一種或多種,golang中的標誌以下
// Flags to OpenFile wrapping those of the underlying system. Not all // flags may be implemented on a given system. const ( // Exactly one of O_RDONLY, O_WRONLY, or O_RDWR must be specified. O_RDONLY int = syscall.O_RDONLY // open the file read-only. O_WRONLY int = syscall.O_WRONLY // open the file write-only. O_RDWR int = syscall.O_RDWR // open the file read-write. // The remaining values may be or'ed in to control behavior. O_APPEND int = syscall.O_APPEND // append data to the file when writing. O_CREATE int = syscall.O_CREAT // create a new file if none exists. O_EXCL int = syscall.O_EXCL // used with O_CREATE, file must not exist. O_SYNC int = syscall.O_SYNC // open for synchronous I/O. O_TRUNC int = syscall.O_TRUNC // truncate regular writable file when opened. )
權限只在文件不存在時建立文件須要,若是隻是普通打開文件,權限填0便可,再若是知識簡單獲得只讀一個已存在的文件,可使用os.Open()
函數來實現,這是它的定義
// Open opens the named file for reading. If successful, methods on // the returned file can be used for reading; the associated file // descriptor has mode O_RDONLY. // If there is an error, it will be of type *PathError. func Open(name string) (*File, error) { return OpenFile(name, O_RDONLY, 0) }
不知道大家有沒有發現,上述介紹的幾個函數的第一個返回值是os.File
類型的指針,這到底是什麼呢?
讓咱們來看看它的定義
// File represents an open file descriptor. type File struct { *file // os specific }
它又指向了file
類型指針,那就看看這個file
吧
// file is the real representation of *File. // The extra level of indirection ensures that no clients of os // can overwrite this data, which could cause the finalizer // to close the wrong file descriptor. type file struct { pfd poll.FD name string dirinfo *dirInfo // nil unless directory being read nonblock bool // whether we set nonblocking mode stdoutOrErr bool // whether this is stdout or stderr appendMode bool // whether file is opened for appending }
那個FD
是文件描述符(file descriptor),有興趣的同窗能夠展開看看file
結構體的內部
下面咱們來看看File
結構體都支持哪些操做(這些方法能夠在file.go, stat_unix.go, file_unix.g, file_posix.go, dir.go
中找到)
f.Fd() f.Stat() f.Name() f.Close() f.Write() f.Chdir() f.Chmod() f.Chown() f.Read() f.ReadAt() f.Readdir() f.Readdirnames() f.Seek() f.SetDeadline() f.SetReadDeadline() f.Sync() f.SyscallConn() f.Truncate() f.WriteAt() f.WriteString()
雖然方法不少,可是經常使用的也就幾個,無非是讀寫和關閉.下面我將介紹幾個經常使用的函數,其他的大家有興趣就去了解吧
f.Close()
關閉一個打開的文件,方法定義以下,
func (f *File) Close() error { if f == nil { return ErrInvalid } return f.file.close() }
調用f.file.close()
來關閉文件,這裏就不深刻了
f.Read()
讀取文件內容,方法定義以下
// Read reads up to len(b) bytes from the File. // It returns the number of bytes read and any error encountered. // At end of file, Read returns 0, io.EOF. func (f *File) Read(b []byte) (n int, err error) { if err := f.checkValid("read"); err != nil { return 0, err } n, e := f.read(b) return n, f.wrapErr("read", e) }
n表明實際讀取的字節數,其本質是調用read函數來實現讀取,繼續深刻發現 read
函數調用了f.pfd.Read()
,該函數最後調用了fd.eofError()
方法,在該方法內部,若是讀取到最末尾,返回io.EOF
錯誤.
示例
package main import ( "fmt" "io" "os" ) func main() { var f *os.File var err error var n int // 存放讀取字節數 // 初始化一個切片用於存放讀取的數據 bs := make([]byte, 1024*8, 1024*8) f,err = os.Open("test.txt") if err != nil { fmt.Println("文件打開失敗") return } // 注意關閉文件 defer f.Close() for { n, err = f.Read(bs) if n == 0 || err == io.EOF{ fmt.Println("文件讀取結束") return } fmt.Println(string(bs[:n])) } }
該方法用字節切片的方式寫入文件,定義以下
// Write writes len(b) bytes to the File. // It returns the number of bytes written and an error, if any. // Write returns a non-nil error when n != len(b). func (f *File) Write(b []byte) (n int, err error) { if err := f.checkValid("write"); err != nil { return 0, err } n, e := f.write(b) if n < 0 { n = 0 } if n != len(b) { err = io.ErrShortWrite } epipecheck(f, e) if e != nil { err = f.wrapErr("write", e) } return n, err }
示例
package main import ( "fmt" "os" ) func main() { var f *os.File var err error var n int // 存放寫入字節數 f, err = os.OpenFile("test.txt", os.O_RDWR|os.O_CREATE|os.O_APPEND, os.ModePerm) if err != nil { fmt.Println("文件打開失敗") return } // 別忘了關閉文件 defer f.Close() n, err = f.Write([]byte("123abc你好")) if err != nil { fmt.Println("寫入文件失敗") return }else{ fmt.Printf("成功寫入%d個字節\n", n) // 一個漢字3個字節 } }
注意, 寫文件就不能經過Open
函數來打開了,Open
函數是隻讀的
利用標誌os.O_CREATE
,會在文件不存在時建立
利用標誌os.O_APPEND
,表示追加寫入
該方法用字符串的方式寫入文件,定義以下
// WriteString is like Write, but writes the contents of string s rather than // a slice of bytes. func (f *File) WriteString(s string) (n int, err error) { return f.Write([]byte(s)) }
能夠看到他的本質就是給你轉換成字節切片,這個示例我就不演示了,相信你們都能看懂
留個思考題:請你們閱讀相關源碼並說明os.fileState
和os.file
這兩個結構體有何不一樣,各自的使用場景又是什麼?
有答案的能夠在評論區留言
若是你看到這裏你應該知道了如何使用golang來實現文件的相關操做,可是大家確定和我同樣,以爲golang的文件讀寫操做很麻煩.下面咱們來了解一下文件操做的另外兩種方法 ioutil
和bufio
,下面內容部分參考了GO語言中文文檔,但文檔中的有些內容已通過時,我已經將它更新.
正如其名字ioutil這是一個讀寫操做包,這裏舉例一些經常使用的方法
func ReadFile(filename string) ([]byte, error)
ReadFile 從filename指定的文件中讀取數據並返回文件的內容。成功的調用返回的err爲nil而非EOF。由於本函數定義爲讀取整個文件,它不會將讀取返回的EOF視爲應報告的錯誤。
定義:
func ReadFile(filename string) ([]byte, error) { f, err := os.Open(filename) if err != nil { return nil, err } defer f.Close() // It's a good but not certain bet that FileInfo will tell us exactly how much to // read, so let's try it but be prepared for the answer to be wrong. var n int64 = bytes.MinRead if fi, err := f.Stat(); err == nil { // As initial capacity for readAll, use Size + a little extra in case Size // is zero, and to avoid another allocation after Read has filled the // buffer. The readAll call will read into its allocated internal buffer // cheaply. If the size was wrong, we'll either waste some space off the end // or reallocate as needed, but in the overwhelmingly common case we'll get // it just right. if size := fi.Size() + bytes.MinRead; size > n { n = size } } return readAll(f, n) }
從源碼能夠看出,ReadFile函數本質也是利用os.File結構體,並調用readAll
函數處理,這裏就不深刻了,感興趣的同窗能夠去看看,
例子:
package main import ( "fmt" "io/ioutil" ) func main() { bs, err := ioutil.ReadFile("test.txt") if err != nil { fmt.Println("文件打開出錯") return } println(string(bs)) }
func WriteFile(filename string, data []byte, perm os.FileMode) error
函數向filename指定的文件中寫入數據。若是文件不存在將按給出的權限建立文件,不然在寫入數據以前清空文件。
定義:
// WriteFile writes data to a file named by filename. // If the file does not exist, WriteFile creates it with permissions perm // (before umask); otherwise WriteFile truncates it before writing. func WriteFile(filename string, data []byte, perm os.FileMode) error { f, err := os.OpenFile(filename, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, perm) if err != nil { return err } _, err = f.Write(data) if err1 := f.Close(); err == nil { err = err1 } return err }
你沒看錯這就是這個函數的源碼,是否是很是眼熟,這和咱們以前寫的代碼十分類似.
例子:
package main import ( "io/ioutil" "os" ) func main() { err := ioutil.WriteFile("hello.txt", []byte("hello世界"),os.ModePerm) if err != nil { println("文件打開失敗") } }
func ReadDir(dirname string) ([]os.FileInfo, error)
返回dirname指定的目錄的目錄信息的有序列表。
定義:
// ReadDir reads the directory named by dirname and returns // a list of directory entries sorted by filename. func ReadDir(dirname string) ([]os.FileInfo, error) { f, err := os.Open(dirname) if err != nil { return nil, err } list, err := f.Readdir(-1) f.Close() if err != nil { return nil, err } sort.Slice(list, func(i, j int) bool { return list[i].Name() < list[j].Name() }) return list, nil }
這個函數的底層也是利用os.File
結構體,而後調用os.File的Readdir方法,並對結構進行排序輸出
例子:
package main import ( "fmt" "io/ioutil" ) func main() { fs, err := ioutil.ReadDir("dir1") if err != nil { println("文件打開失敗") } for i, v := range fs{ fmt.Println(i, v.Name(), v.IsDir(), v.Size(), v.ModTime()) } }
func ReadAll(r io.Reader) ([]byte, error)
ReadAll讀取數據直到EOF或遇到error,返回讀取的數據和遇到的錯誤。成功的調用返回的err爲nil而非EOF。由於本函數定義爲讀取r直到EOF,它不會將讀取返回的EOF視爲應報告的錯誤。
定義:
// ReadAll reads from r until an error or EOF and returns the data it read. // A successful call returns err == nil, not err == EOF. Because ReadAll is // defined to read from src until EOF, it does not treat an EOF from Read // as an error to be reported. func ReadAll(r io.Reader) ([]byte, error) { return readAll(r, bytes.MinRead) }
它接收一個參數類型爲io.Reader
,經過讀源碼能夠看出它就是一個實現了Read
函數的接口
type Reader interface { Read(p []byte) (n int, err error) }
咱們已經知道os.File
就是實現了Read
方法,因此咱們能夠將一個os.File
結構體傳進去,
例子
package main import ( "io/ioutil" "os" ) func main() { var bs []byte var err error var f *os.File f, err = os.Open("test.txt") if err != nil { println("打開文件失敗") return } defer f.Close() bs, err = ioutil.ReadAll(f) if err != nil { println("文件讀取失敗") return } println(string(bs)) }
不難發現ReadFile
和ReadAll
方法很是的像,只不過ReadFile
方法內部對文件進打開操做,而ReadAll
是在外面手動打開文件而後傳進去
func TempDir(dir, pattern string) (name string, err error)
在dir目錄裏建立一個新的臨時文件夾(其實本質就是普通文件,用做臨時存儲場景,須要手動刪除),該文件夾的命名規則以下,使用將pattern
用*
分開,取最後一個作後綴,前面的作前綴,中間拼接隨機數字,該操做可在其源碼中看到,以下
func TempDir(dir, pattern string) (name string, err error) { if dir == "" { dir = os.TempDir() } prefix, suffix := prefixAndSuffix(pattern) nconflict := 0 for i := 0; i < 10000; i++ { try := filepath.Join(dir, prefix+nextRandom()+suffix) err = os.Mkdir(try, 0700) if os.IsExist(err) { if nconflict++; nconflict > 10 { randmu.Lock() rand = reseed() randmu.Unlock() } continue } if os.IsNotExist(err) { if _, err := os.Stat(dir); os.IsNotExist(err) { return "", err } } if err == nil { name = try } break } return }
其中prefixAndSuffix
函數定義以下
func prefixAndSuffix(pattern string) (prefix, suffix string) { if pos := strings.LastIndex(pattern, "*"); pos != -1 { prefix, suffix = pattern[:pos], pattern[pos+1:] } else { prefix = pattern } return }
舉個例子
package main import "io/ioutil" func main() { name, err := ioutil.TempDir("dir1", "kain*huck") if err != nil { println("文件夾建立失敗") return } println(name) // dir1/kain037615429huck }
func TempFile(dir, pattern string) (f *os.File, err error)
在dir目錄下建立一個新的臨時文件,文件名是經過使用pattern並在末尾添加一個隨機字符串生成的。若是pattern包含一個「*」,隨機字符串將替換最後一個「*」。
定義:
func TempFile(dir, pattern string) (f *os.File, err error) { if dir == "" { dir = os.TempDir() } prefix, suffix := prefixAndSuffix(pattern) nconflict := 0 for i := 0; i < 10000; i++ { name := filepath.Join(dir, prefix+nextRandom()+suffix) f, err = os.OpenFile(name, os.O_RDWR|os.O_CREATE|os.O_EXCL, 0600) if os.IsExist(err) { if nconflict++; nconflict > 10 { randmu.Lock() rand = reseed() randmu.Unlock() } continue } break } return }
package main import "io/ioutil" func main() { f, err := ioutil.TempFile("dir1", "kain*huck") if err != nil { println("文件建立失敗") return } println(f.Name()) // dir1/kain115329465huck }
bufio包實現了有緩衝的I/O。它包裝一個io.Reader或io.Writer接口對象,建立另外一個也實現了該接口,且同時還提供了緩衝和一些文本I/O的幫助函數的對象。
bufio.Reader
定義:
// Reader implements buffering for an io.Reader object. type Reader struct { buf []byte rd io.Reader // reader provided by the client r, w int // buf read and write positions err error lastByte int // last byte read for UnreadByte; -1 means invalid lastRuneSize int // size of last rune read for UnreadRune; -1 means invalid }
經常使用方法
func NewReader(rd io.Reader) *Reader
定義:
// NewReader returns a new Reader whose buffer has the default size. func NewReader(rd io.Reader) *Reader { return NewReaderSize(rd, defaultBufSize) }
NewReader調用NewReaderSize
建立一個具備默認大小緩衝的Reader指針
defauleBufSize
const ( defaultBufSize = 4096 )
func NewReaderSize(rd io.Reader, size int) *Reader
定義:
// NewReaderSize returns a new Reader whose buffer has at least the specified // size. If the argument io.Reader is already a Reader with large enough // size, it returns the underlying Reader. func NewReaderSize(rd io.Reader, size int) *Reader { // Is it already a Reader? b, ok := rd.(*Reader) if ok && len(b.buf) >= size { return b } if size < minReadBufferSize { size = minReadBufferSize } r := new(Reader) r.reset(make([]byte, size), rd) return r }
該函數就是上述NewReader
中被調用的函數,他可讓咱們手動的提供緩衝大小,從代碼能夠看到,所謂緩衝就是一個byte切片,該切片會被賦值給Reader
結構體的buf
,
reset
func (b *Reader) reset(buf []byte, r io.Reader) { *b = Reader{ buf: buf, rd: r, lastByte: -1, lastRuneSize: -1, } }
func (b *Reader) Reset(r io.Reader)
Reset丟棄緩衝中的數據,清除任何錯誤,將b重設爲其下層從r讀取數據。
定義:
// Reset discards any buffered data, resets all state, and switches // the buffered reader to read from r. func (b *Reader) Reset(r io.Reader) { b.reset(b.buf, r) }
其內部實現很是簡單,就是調用函數reset
(這個函數在上面已經看過了),將當前的Reader
中的rd
屬性(io.Reader)從新重設爲r
func (b *Reader) Buffered() int
Buffered返回緩衝中現有的可讀取的字節數。
定義以下
// Buffered returns the number of bytes that can be read from the current buffer. func (b *Reader) Buffered() int { return b.w - b.r }
這個就不解釋了
func (b *Reader) Peek(n int) ([]byte, error)
Peek返回輸入流的下n個字節,而不會移動讀取位置。返回的[]byte只在下一次調用讀取操做前合法。若是Peek返回的切片長度比n小,它也會返會一個錯誤說明緣由。若是n比緩衝尺寸還大,返回的錯誤將是ErrBufferFull。
定義:
func (b *Reader) Peek(n int) ([]byte, error) { if n < 0 { return nil, ErrNegativeCount } b.lastByte = -1 b.lastRuneSize = -1 for b.w-b.r < n && b.w-b.r < len(b.buf) && b.err == nil { b.fill() // b.w-b.r < len(b.buf) => buffer is not full } if n > len(b.buf) { return b.buf[b.r:b.w], ErrBufferFull } // 0 <= n <= len(b.buf) var err error if avail := b.w - b.r; avail < n { // not enough data in buffer n = avail err = b.readErr() if err == nil { err = ErrBufferFull } } return b.buf[b.r : b.r+n], err }
別看前面這麼多,那都是一些特殊狀況的判斷處理,看最後一句得知其實該函數就是返回了緩衝區的切
func (b *Reader) Read(p []byte) (n int, err error)
Read讀取數據寫入p。本方法返回寫入p的字節數。本方法一次調用最多會調用下層Reader接口一次Read方法,所以返回值n可能小於len(p)。讀取到達結尾時,返回值n將爲0而err將爲io.EOF。
定義:
func (b *Reader) Read(p []byte) (n int, err error) { n = len(p) if n == 0 { if b.Buffered() > 0 { return 0, nil } return 0, b.readErr() } if b.r == b.w { if b.err != nil { return 0, b.readErr() } if len(p) >= len(b.buf) { // Large read, empty buffer. // Read directly into p to avoid copy. n, b.err = b.rd.Read(p) if n < 0 { panic(errNegativeRead) } if n > 0 { b.lastByte = int(p[n-1]) b.lastRuneSize = -1 } return n, b.readErr() } // One read. // Do not use b.fill, which will loop. b.r = 0 b.w = 0 n, b.err = b.rd.Read(b.buf) if n < 0 { panic(errNegativeRead) } if n == 0 { return 0, b.readErr() } b.w += n } // copy as much as we can n = copy(p, b.buf[b.r:b.w]) b.r += n b.lastByte = int(b.buf[b.r-1]) b.lastRuneSize = -1 return n, nil }
從中能夠看到n, b.err = b.rd.Read(p)
,說明這內部是調用io.Read方法.而後將buf中的可用內容儘量的複製給p.
鑑於篇幅過長,如下其餘經常使用方法就留給讀者本身去分析吧,
func (b *Reader) ReadByte() (c byte, err error)
ReadByte讀取並返回一個字節。若是沒有可用的數據,會返回錯誤。
func (b *Reader) ReadRune() (r rune, size int, err error)
ReadRune讀取一個utf-8編碼的unicode碼值,返回該碼值、其編碼長度和可能的錯誤。若是utf-8編碼非法,讀取位置只移動1字節,返回U+FFFD,返回值size爲1而err爲nil。若是沒有可用的數據,會返回錯誤。
func (b *Reader) ReadLine() (line []byte, isPrefix bool, err error)
ReadLine是一個低水平的行數據讀取原語。大多數調用者應使用ReadBytes('\n')或ReadString('\n')代替,或者使用Scanner。
ReadLine嘗試返回一行數據,不包括行尾標誌的字節。若是行太長超過了緩衝,返回值isPrefix會被設爲true,並返回行的前面一部分。該行剩下的部分將在以後的調用中返回。返回值isPrefix會在返回該行最後一個片斷時才設爲false。返回切片是緩衝的子切片,只在下一次讀取操做以前有效。ReadLine要麼返回一個非nil的line,要麼返回一個非nil的err,兩個返回值至少一個非nil。
返回的文本不包含行尾的標誌字節("\r\n"或"\n")。若是輸入流結束時沒有行尾標誌字節,方法不會出錯,也不會指出這一狀況。在調用ReadLine以後調用UnreadByte會老是吐出最後一個讀取的字節(極可能是該行的行尾標誌字節),即便該字節不是ReadLine返回值的一部分。
func (b *Reader) ReadString(delim byte) (string, error)
ReadString讀取直到第一次遇到delim字節,返回一個包含已讀取的數據和delim字節的字符串。若是ReadString方法在讀取到delim以前遇到了錯誤,它會返回在錯誤以前讀取的數據以及該錯誤(通常是io.EOF)。當且僅當ReadString方法返回的切片不以delim結尾時,會返回一個非nil的錯誤。
func (b *Reader) ReadSlice(delim byte) (line []byte, err error)
ReadSlice讀取直到第一次遇到delim字節,返回緩衝裏的包含已讀取的數據和delim字節的切片。該返回值只在下一次讀取操做以前合法。若是ReadSlice放在在讀取到delim以前遇到了錯誤,它會返回在錯誤以前讀取的數據在緩衝中的切片以及該錯誤(通常是io.EOF)。若是在讀取到delim以前緩衝就被寫滿了,ReadSlice失敗並返回ErrBufferFull。由於ReadSlice的返回值會被下一次I/O操做重寫,調用者應儘可能使用ReadBytes或ReadString替代本法功法。當且僅當ReadBytes方法返回的切片不以delim結尾時,會返回一個非nil的錯誤。
func (b *Reader) ReadBytes(delim byte) ([]byte, error)
ReadBytes讀取直到第一次遇到delim字節,返回一個包含已讀取的數據和delim字節的切片。若是ReadBytes方法在讀取到delim以前遇到了錯誤,它會返回在錯誤以前讀取的數據以及該錯誤(通常是io.EOF)。當且僅當ReadBytes方法返回的切片不以delim結尾時,會返回一個非nil的錯誤。
例子1:
package main import ( "bufio" "fmt" "os" "io" ) func main() { f,err := os.Open("test.txt") if err != nil { panic(err) } defer f.Close() reader := bufio.NewReader(f) p := make([]byte, 12) n, err :=reader.Read(p) if err != nil && err != io.EOF { panic(err) } println(n) fmt.Println(string(p)) }
例子2:
package main import ( "bufio" "fmt" "io" "os" ) func main() { f,err := os.Open("test.txt") if err != nil { panic(err) } defer f.Close() reader := bufio.NewReader(f) for{ str, err := reader.ReadString('\n') if err == io.EOF{ fmt.Println(str) break } if err != nil{ panic(err) } fmt.Print(str) } }
bufio.Writer
bufio.Writer
其實和bufio.Reader
操做很對應.
定義:
// Writer implements buffering for an io.Writer object. // If an error occurs writing to a Writer, no more data will be // accepted and all subsequent writes, and Flush, will return the error. // After all data has been written, the client should call the // Flush method to guarantee all data has been forwarded to // the underlying io.Writer. type Writer struct { err error buf []byte n int wr io.Writer }
下面列舉幾個最最經常使用的方法
func NewWriter(w io.Writer) *Writer
NewWriter建立一個具備默認大小緩衝、寫入w的*Writer。
定義:
// NewWriter returns a new Writer whose buffer has the default size. func NewWriter(w io.Writer) *Writer { return NewWriterSize(w, defaultBufSize) }
該方法調用NewWriterSize
,返回一個帶有默認緩衝區的Writer
指針
defaultBufSize
const ( defaultBufSize = 4096 )
func NewWriterSize(w io.Writer, size int) *Writer
NewWriterSize建立一個具備最少有size尺寸的緩衝、寫入w的Writer。若是參數w已是一個具備足夠大緩衝的Writer類型值,會返回w。
// NewWriterSize returns a new Writer whose buffer has at least the specified // size. If the argument io.Writer is already a Writer with large enough // size, it returns the underlying Writer. func NewWriterSize(w io.Writer, size int) *Writer { // Is it already a Writer? b, ok := w.(*Writer) if ok && len(b.buf) >= size { return b } if size <= 0 { size = defaultBufSize } return &Writer{ buf: make([]byte, size), wr: w, } }
這個函數的實現過程和NewReaderSize
一模一樣,只不過NewReaderSize
中調用了reset
方法,這裏直接初始化指針對象
func (b *Writer) Reset(w io.Writer)
Reset丟棄緩衝中的數據,清除任何錯誤,將b重設爲將其輸出寫入w。
定義:
// Reset discards any unflushed buffered data, clears any error, and // resets b to write its output to w. func (b *Writer) Reset(w io.Writer) { b.err = nil b.n = 0 b.wr = w }
這個方法就很少作解釋了,一眼就明白
func (b *Writer) Buffered()
Buffered返回緩衝中已使用的字節數。
定義:
// Buffered returns the number of bytes that have been written into the current buffer. func (b *Writer) Buffered() int { return b.n }
這個方法就更不用解釋了
func (b *Writer) Available() int
Available返回緩衝中還有多少字節未使用。
定義:
// Available returns how many bytes are unused in the buffer. func (b *Writer) Available() int { return len(b.buf) - b.n }
不解釋
func (b *Writer) Write(p []byte) (nn int, err error)
Write將p的內容寫入緩衝。返回寫入的字節數。若是返回值nn < len(p),還會返回一個錯誤說明緣由。
定義:
func (b *Writer) Write(p []byte) (nn int, err error) { for len(p) > b.Available() && b.err == nil { var n int if b.Buffered() == 0 { // Large write, empty buffer. // Write directly from p to avoid copy. n, b.err = b.wr.Write(p) } else { n = copy(b.buf[b.n:], p) b.n += n b.Flush() } nn += n p = p[n:] } if b.err != nil { return nn, b.err } n := copy(b.buf[b.n:], p) b.n += n nn += n return nn, nil }
從n, b.err = b.wr.Write(p)
可得這是經過調用io.Writer
接口中的Write
方法,
n := copy(b.buf[b.n:], p)
這一句和前面Read方法恰好相反,這是將p複製給buf的可用部分
func (b *Writer) WriteString(s string) (int, error)
WriteString寫入一個字符串。返回寫入的字節數。若是返回值nn < len(s),還會返回一個錯誤說明緣由。
定義:
func (b *Writer) WriteString(s string) (int, error) { nn := 0 for len(s) > b.Available() && b.err == nil { n := copy(b.buf[b.n:], s) b.n += n nn += n s = s[n:] b.Flush() } if b.err != nil { return nn, b.err } n := copy(b.buf[b.n:], s) b.n += n nn += n return nn, nil }
關鍵語句n := copy(b.buf[b.n:], s)
不解釋
func (b *Writer) Flush() error
Flush方法將緩衝中的數據寫入下層的io.Writer接口。
定義:
func (b *Writer) Flush() error { if b.err != nil { return b.err } if b.n == 0 { return nil } n, err := b.wr.Write(b.buf[0:b.n]) if n < b.n && err == nil { err = io.ErrShortWrite } if err != nil { if n > 0 && n < b.n { copy(b.buf[0:b.n-n], b.buf[n:b.n]) } b.n -= n b.err = err return err } b.n = 0 return nil }
解釋一下,若是b有錯誤就直接返回錯誤,若是b中沒有寫入值,則不作任何處理,不然就將緩衝中的n個值寫進底層io.Writer中,若是寫入的值數量小於緩衝取的值而且沒有出錯,則將err定義爲io.ErrShortWrite
錯誤,若是err不爲空,而且寫進底層io.Writer
中的數據小於緩衝中原有的數據時,將剩餘的數據寫進緩衝區開頭,將緩衝區已用空間設置爲原來大小減去寫人底層大小.若是err爲空則表明成功將全部緩衝數據寫入io.Writer
將已用空間置爲0
其餘bufio
中的方法就留給讀者本身去探索吧!
舉個例子:
package main import ( "bufio" "os" ) func main() { f, err := os.OpenFile("test.txt", os.O_RDWR|os.O_CREATE|os.O_APPEND, os.ModePerm) if err != nil { panic(err) } defer f.Close() writer := bufio.NewWriter(f) writer.WriteString("讓我康康是哪位靚仔看到了這句話\n") writer.Write([]byte("原來是你!\n")) writer.Flush() }
注意:最後必定要flush一下,不然數據就不會寫入文件,注意使用OpenFile
函數打開文件並指定可寫,不然將寫不進去.
到這裏這篇文章就結束了,我寫了一成天將近8000個字,感謝你堅持看到這裏,這是對我最大的支持,若是有什麼意見或者不足指出歡迎指出