分析源碼理解GO語言文件操做

獲取文件信息

FileInfo接口

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包中能夠看到這兩個函數的定義,記住裏面的兩個函數statNologlstatNologapp

// 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結構體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接口函數

實際上,進入statNologlstatNolog可知,StatLsate函數就是返回了指向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方法就是返回了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", 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('\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個字,感謝你堅持看到這裏,這是對我最大的支持,若是有什麼意見或者不足指出歡迎指出

相關文章
相關標籤/搜索