zoukankan      html  css  js  c++  java
  • 分析源码理解GO语言文件操作

    获取文件信息

    FileInfo接口

    os包中有一个FileInfo接口它包含了一个文件的基本信息,如下

    // 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方法返回

    os包中可以看到这两个函数的定义,记住里面的两个函数statNologlstatNolog

    // 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)
    }
    

    fileStat结构体

    同样在os包中定义了fileState结构体

    // 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文件中定义了如下方法

    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中还定义以下两个方法

    func (fs *fileStat) Name() string { return fs.name }
    func (fs *fileStat) IsDir() bool  { return fs.Mode().IsDir() }
    

    可以看到这个结构体刚好实现了FileInfo接口

    实际上,进入statNologlstatNolog可知,StatLsate函数就是返回了指向fileStat的指针

    // 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结构体存放文件相关信息,并做操作

    查看文件信息

    介绍了这么多,写一个golang读取文件信息的例子,(当前目录下的go.mod文件)

    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方法就是返回了fileStatsys属性,上面我们已经看过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返回的错误,然后调用IsExistIsNotExist来判断是否存在

    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)
    }
    

    创建目录

    os.MKdir()

    该函数用于创建一层目录,定义如下

    // 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)
    

    os.MKdirAll()

    该函数用于创建多层目录,定义如下

    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
    

    创建文件

    os.Creat()

    用该方法创建文本文件,如果文件已经存在,会将其覆盖,定义如下

    func Create(name string) (*File, error) {
    	return OpenFile(name, O_RDWR|O_CREATE|O_TRUNC, 0666)
    }
    

    可以发现,他就是通过打开文件的方式来创建文件,通过 O_TRUNC标志,来覆盖原文件.

    示例

    f, err := os.Create("test.txt")
    

    删除文件(目录)

    os.Remove()

    该方法用于删除文件或者空目录,定义如下

    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")
    

    os.RemoveAll()

    该方法用于删除目录下面所有子节点,定义如下

    // 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]))
    	}
    }
    

    写文件

    f.Write()

    该方法用字节切片的方式写入文件,定义如下

    // 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) // 一个汉字3个字节
    	}
    }
    

    注意, 写文件就不能通过Open函数来打开了,Open函数是只读的

    利用标志os.O_CREATE,会在文件不存在时创建

    利用标志os.O_APPEND,表示追加写入

    f.WriteString()

    该方法用字符串的方式写入文件,定义如下

    // 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.fileStateos.file这两个结构体有何不同,各自的使用场景又是什么?

    有答案的可以在评论区留言

    如果你看到这里你应该知道了如何使用golang来实现文件的相关操作,但是你们肯定和我一样,觉得golang的文件读写操作很麻烦.下面我们来了解一下文件操作的另外两种方法 ioutilbufio,下面内容部分参考了GO语言中文文档,但文档中的有些内容已经过时,我已经将它更新.

    ioutil

    正如其名字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))
      }
      

      不难发现ReadFileReadAll方法非常的像,只不过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

    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(' ')或ReadString(' ')代替,或者使用Scanner。

        ReadLine尝试返回一行数据,不包括行尾标志的字节。如果行太长超过了缓冲,返回值isPrefix会被设为true,并返回行的前面一部分。该行剩下的部分将在之后的调用中返回。返回值isPrefix会在返回该行最后一个片段时才设为false。返回切片是缓冲的子切片,只在下一次读取操作之前有效。ReadLine要么返回一个非nil的line,要么返回一个非nil的err,两个返回值至少一个非nil。

        返回的文本不包含行尾的标志字节(" "或" ")。如果输入流结束时没有行尾标志字节,方法不会出错,也不会指出这一情况。在调用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('
      ')
      		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("让我康康是哪位靓仔看到了这句话
    ")
    	writer.Write([]byte("原来是你!
    "))
    
    	writer.Flush()
    }
    

    注意:最后一定要flush一下,否则数据就不会写入文件,注意使用OpenFile函数打开文件并指定可写,否则将写不进去.

    到这里这篇文章就结束了,我写了一整天将近8000个字,感谢你坚持看到这里,这是对我最大的支持,如果有什么意见或者不足指出欢迎指出

  • 相关阅读:
    学习angularjs的ng-hide和ng-disabled
    Angularjs实现select的下拉列表
    练习ng-show和ng-hide的方法
    练习angularjs的ng-click的应用
    ng-include文件实现ng-repeat
    JQuery加载html网页
    指定时间内网站访问次数的监控
    AWK 技巧(取倒列,过滤行,匹配,不匹配,内置变量等)
    Linux下FastDFS分布式存储-总结及部署记录
    Linux下IP SAN共享存储操作记录
  • 原文地址:https://www.cnblogs.com/kainhuck/p/13149495.html
Copyright © 2011-2022 走看看