在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个字,感谢你坚持看到这里,这是对我最大的支持,若是有什么意见或者不足指出欢迎指出