命令记不住

登陆

文件操作与 I/O

文件操作与I/O

1.文件和目录操作

对于文件和目录的操作,Go语言主要在标准库的os包中提供了相应的函数

func Mkdir(name string,perm FileMode)error
func Chdir(dir string)error
func TempDir()string
func Rename(oldpath,newpath string)error
func Chmod(name string,mode FileMode)error
func OPen(name string)(*File,error){
return OPenFile(name,O_RDONLY,0)
}
func Create(name string)(*File , error){
return OPenFile(name,O_RDWR|O_CREATE|O_TRUNC,0666)
}
func OpenFile(name string.flag int,perm FileMode)(*File,error){
testlog.OPen(name)
return openFileNolog(name.flag,perm)
}

os包中各个函数打开(创建)文件的操作,最终还是通过OpenFile()函数来实现,而OpenFile()由编译器根据操作系统的情况选择不同的底层功能来实现

os.Open(name string)使用只读模式打开文件

os.Create(name string)创建新文件,如文件存在则原文件内容会丢失

os.OpenFile(name string,flag int,perm FileMode)这个函数可以指定flag和FileMode。这三个函数都会返回一个文件对象

OPenFile()函数的flag标志参数含义如下:

O_RDONLY int = syscall.O_RDONLY     //只读打开文件和os.Open()同义
O_WRONLY int = syscall.O_WRONLY     //只写打开文件
O_RDWR  int = syscall.O_RDWR        //读写方式打开文件
O_APPEND int = syscall.O_APPEND     //当写的时候使用追加模式到文件末尾
O_CREATE int = syscall.O_CREAT      //如果文件不存在,则创建
O_EXCL  int = syscall.O_EXCL        //和O_CREATE一起使用,只有当文件不存在时才创建
O_SYNC  int = syscall.O_SYNC        //以同步I/O方式打开文件,直接写入硬盘
O_TRUNC  int = syscall.O_TRUNC      //如果可以的话,当打开文件时先清空文件

在标准库的ioutil包中,也可以对文件进行操作,主要有下面三个函数:

func ReadFile(filename string)([]byte,error)// f,err := os.Open(filename)
func WriteFile(filename styring,data []byte,perm os.FileMode)error //os.OpenFile
func ReadDir(dirname string)([]os.FileInfo,error)//f,err := os.Open(dirname)

这三个函数涉及文件I/O操作,而对文件的操作除了打开(创建)、关闭外,更主要的是对内容的读写操作,既文件I/O的处理。在Go语言中,I/O操作在Go语言标准库的很多包中存在。

2.I/O读写

Go语言中,为了方便开发者使用,将I/O操作封装在了大概如下几个包中。

(1)io包为I/O原语(I/O primitives)提供基本的接口

(2)io/ioutil包封装一些实用的I/O处理函数

(3)fmt包实现格式化I/O,类似C语言中的printf和scanf

(4)bufio包实现了带缓冲I/O

在io包中最重要的是Reader和Writer接口,这两个接口是了解整个个I/O的关键。实现了这两个接口,就有了I/O功能

type Reader interface{
 Read(p []byte)(n int,err error)
}
type Writer interface{
 Write(p []byte)(n int,err error)
}

有关缓冲,需要了解:

内核中的缓冲:无论进程是否提供缓冲,内核都是提供缓冲的,系统对磁盘的读写都会提供一个缓冲(内核高速缓冲),将数据写入到块缓冲进行排队,当块缓冲达到一定量时,才把数据写入磁盘

进程中的缓冲:是指对输入输出流进行改进,提供一个流缓冲,当调用一个函数向磁盘写数据时,先把数据写入缓冲区,当达到某个条件,如流缓冲已满或刷新流缓冲,这时候才会把数据一次送往内核提供的块缓冲,再经块缓冲写入磁盘

Go语言提供了很多读写文件的方式,一般来说常用的有三种

(1)os.File实现了Reader和Writer接口,所以在文件对象上,可以直接读写文件

func (f *file)Read(b []byte)(n int,err error)
func (f *file)Write(b []byte)(n int,err error)

使用File.Read读文件时,可以考虑使用buffer.例如

package main

import (
    "fmt"
    "os"
)

func main() {
    b := make([]byte, 1024)
    f, err := os.Open("./tt.txt") // 只读模式打开文件
    _, err = f.Read(b)
    f.Close()

    if err != nil {
        fmt.Println(err)
    }
    fmt.Println(string(b))

}

(2)ioutil包,没有直接实现Reader和Writer接口,但是通过内部调用,也可读写文件内容

func ReadAll(r io.Reader)([]byte,error)
func ReadFile(filename string)([]byte,error)  //os.Open
func WriteFile(filename string,data []byte,perm os.FileMode)error //os.OpenFile
func ReadDir(dirname string)([]os.FileInfo,error) //os.Open

(3)使用bufio包,这个包实现了I/O的缓冲操作,通过内嵌io.Reader和io.Writer接口,新建了Reader和Writer结构体,同时也实现了Reader和Writer接口

type Reader struct {
    buf         []byte
    rd          io.Reader  //客户端提供的Reader
    r,w         int
    err         error
    lastByte    int
    lastRunSize int
}

type Writer struct {
    err     error
    buf     []byte
    n       int 
    wr      io.Writer                                                        
}
func (b *Reader)Read(p []byte)(n int,err error)
func (b *Writer)Write(p []byte)(nn int,err error)
示例三种读写方式的效率:
package main

import (
    "bufio"
    "fmt"
    "io"
    "io/ioutil"
    "os"
    "time"
)

func Read1(path string) {
    fi, err := os.Open(path)
    if err != nil {
        panic(err)
    }
    defer fi.Close()
    buf := make([]byte, 1024)
    for {
        n, err := fi.Read(buf)
        if err != nil && err != io.EOF {
            panic(err)
        }
        if 0 == n {
            break
        }
    }
}

func Read2(path string) {
    fi, err := os.Open(path)
    if err != nil {
        panic(err)
    }
    defer fi.Close()
    r := bufio.NewReader(fi)
    buf := make([]byte, 1024)
    for {
        n, err := r.Read(buf)
        if err != nil && err != io.EOF {
            panic(err)
        }
        if 0 == n {
            break
        }
    }
}

func Read3(path string) {
    fi, err := os.Open(path)
    if err != nil {
        panic(err)
    }
    defer fi.Close()
    _, err = ioutil.ReadAll(fi)
}

func main() {

    file := "./all.txt" //找一个大的文件,如日志文件
    start := time.Now()
    Read1(file)
    t1 := time.Now()
    fmt.Printf("Cost time %v\n", t1.Sub(start))
    Read2(file)
    t2 := time.Now()
    fmt.Printf("Cost time %v\n", t2.Sub(t1))
    Read3(file)
    t3 := time.Now()
    fmt.Printf("Cost time %v\n", t3.Sub(t2))
}

测试用例:

package main

import (
    "testing"
)

func TestRead1(t *testing.T) {
    file := "./all.txt"
    Read1(file)
}

func TestRead2(t *testing.T) {
    file := "./all.txt"
    Read2(file)
}

func TestRead3(t *testing.T) {
    file := "./all.txt"
    Read3(file)
}

func BenchmarkRead1(b *testing.B) {

    for i := 0; i < b.N; i++ {
        file := "./all.txt"
        Read1(file)
    }
}

func BenchmarkRead2(b *testing.B) {

    for i := 0; i < b.N; i++ {
        file := "./all.txt"
        Read2(file)
    }
}

func BenchmarkRead3(b *testing.B) {

    for i := 0; i < b.N; i++ {
        file := "./all.txt"
        Read3(file)
    }
}

文件的读取时间基本保持file.Read>ioutil>bufio这样的成绩,bufio读同一文件耗费时间最少,效果最佳。

3.ioutil包读写

下面的代码使用ioutil包实现两种读文件、一种写文件的方法,其中iouitl.ReadAll可以读取所有io.Reader流。所以在网络连接中,也经常使用ioutil.ReadAll来读取流

package main

import (
    "fmt"
    "io/ioutil"
    "os"
)

func main() {
    fileObj, err := os.Open("./all.txt")
    defer fileObj.Close()

    contents, _ := ioutil.ReadAll(fileObj)
    fmt.Println(string(contents))

    if contents, _ := ioutil.ReadFile("./all.txt"); err == nil {
        fmt.Println(string(contents))
    }

    ioutil.WriteFile("./t3.txt", contents, 0666)

}

4.bufio包读写

bufio包通过bufio.NewReader和bufio.NewWriter来创建I/O方法集,利用缓冲来处理流,例如:

package main

import (
    "bufio"
    "fmt"
    "os"
)

func main() {
    fileObj, _ := os.OpenFile("./all.txt", os.O_RDWR|os.O_CREATE, 0666)
    defer fileObj.Close()

    Rd := bufio.NewReader(fileObj)
    cont, _ := Rd.ReadSlice('#')
    fmt.Println(string(cont))

    Wr := bufio.NewWriter(fileObj)
    Wr.WriteString("WriteString writes a ## string.")
    Wr.Flush()
}

程序输出:

WriteString  writes a #

上面代码的打开方式为os.O_RDWR|os.O_CREATE,所以会在文件尾追加内容,不会每次运行程序都清空内容。

bufio包中,主要方法如下:

package main

import "io"

//NewReaderSize将rd封装成一个带缓冲bufio.Reader对象
//带缓存大小由size指定(如果小于16则会被设置为16)
func NewReaderSize(rd io.Reader, size int) *Reader

//NewReader相当于NewReaderSize)rd,4096
func NewReader(rd io.Reader) *Reader

//Peek返回缓存的一个切片,该切片引用缓存中前n个字节的数据
//如果n大于缓存的总大小,则返回当前缓存中能读到的字节的数据
func (b *Reader) Peek(n int) ([]byte, error)

//Read 从b中读取数据到p中,返回读取的字节数和遇到的错误
//如果缓存不为空,则只能读取缓存中的数据,不会从底层io.Reader中提取数据,如果缓存为空,则:
//1.len(p)>=缓存大小,则跳过缓存,直接从底层io.Reader中读出到p中。
//2.len(p)<缓存大小,则先将数据从底层io.Reader读取到缓存中,再从缓存读取到p中。
func (b *Reader) Read(p []byte) (n int, err error)

//Buffered该方法返回当前缓存中能被读到的字节数
func (b *Reader) Buffered() int

//Discard方法跳过后续的n个字节的数据,返回跳过的字节数
func (b *Reader) Discard(n int) (discarded int, err error)

//ReadSile在b中查找delim并返回delim及其之前的所有数据
//该操作会读出数据,返回的切片是已读出数据的引用,切片中的数据在下一次读取操作之前是有效的
//如果找到delim,则返回查找结果,err返回nil
//如果未找到delim,则:
//1.缓存不满,则将缓存填满之后再次查找
//2.缓存是满的,则返回整个缓存,err返回ErrBufferFull
//如果未找到delim,且遇到错误(通常是io.EOF),则返回缓存中的所有数据和遇到的错误
//因为返回的数据有可能被下一次是读写操作修改,所以大多数操作应该使用
//ReadBytes或ReadString,它们返回的是数据的副本。
func (b *Reader) ReadSlice(delim byte) (line []byte, err error)

//ReadLine是一个低水平的行读取原语,大多数情况下,应该使用ReadBytes('\n')或ReadString('\n')或使用一个Scanner。
//ReadLine通过调用ReadSlice方法实现,返回的也是缓存切片。
//用于读取一行数据,不包括行尾标记(\n或\r\n)
//返回flase,err返回io.EOF
//如果找到行尾标记,则返回查找结果,isPrefix返回false
//如果未找到行未标记,则:
//1.缓存不满,则将缓存填满后再次查找
//2.缓存是满的,则返回整个缓存,isPrefix返回true
//整个数据尾部"有一个换行标记"和”没有换行标记“的读取结果是一样
//如果是ReadLine读取到换行标记,则调用UnreadByte撤销的是换行标记,而不是返回的数据
func (b *Reader) ReadLine() (line []byte, isPrefix bool, err error)

//ReadBytes功能同ReadSlice,只不过返回的是缓存的副本
func (b *Reader)ReadString(delim byte)(line string,err error)

//ReadString功能同ReadBytes,只不过返回的是字符串
func(b *Reader)ReadString(delim byte)(line string,err error)

//Reset将b的底层Reader重新指定为r,同时丢弃缓存中的所有数据,复位所有标记和错误信息,bufio.Reader
func (b *Reader)Reset(r io.Reader)

下面这段代码用到peek(),Discard()等方法,可以修改方法参数值

package main

import (
    "bufio"
    "fmt"
    "strings"
)

func main() {
    sr := strings.NewReader("ABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890")
    buf := bufio.NewReaderSize(sr, 0) //默认16
    b := make([]byte, 10)

    fmt.Println("==", buf.Buffered()) // 0
    s, _ := buf.Peek(5)
    fmt.Printf("%d ==  %q\n", buf.Buffered(), s) //ABCDE
    nn, er := buf.Discard(3)
    fmt.Println(nn, er)

    for n, err := 0, error(nil); err == nil; {
        fmt.Printf("Buffered:%d ==Size:%d== n:%d==  b[:n] %q ==  err:%v\n", buf.Buffered(), buf.Size(), n, b[:n], err)
        n, err = buf.Read(b)
        fmt.Printf("Buffered:%d ==Size:%d== n:%d==  b[:n] %q ==  err: %v == s: %s\n", buf.Buffered(), buf.Size(), n, b[:n], err, s)
    }

    fmt.Printf("%d ==  %q\n", buf.Buffered(), s)
}

log包日志操作

在Go语言标准库中提供了log包作为日志处理专用包。在一般的开发应用中,经常会使用这个包,这个包会默认把日志输出到标准设备上

log包最主要的结构体是Logger:

type Logger struct {
    mu      sync.Mutex
    prefix  string
    flag    int
    out     io.Writer
    buf     []byte
}
func New(out io,Writer,prefix string,flag int)*Logger

日志写入时存在缓冲区,而且有互斥锁来保证操作的原子性,这两点对日志处理的安全性和写性能都有一定的保证。输出默认是os.Stderr,可以设置日志前缀信息prefix,可以指定日志flag如时间格式,记录代码文件名和行数。下面考虑写入到日志文件My.Log,并通过SetPrefix()设置日志的前缀

package main

import (
    "log"
    "os"
)

func main() {
    LogFile, err := os.OpenFile("./My.log", os.O_CREATE|os.O_RDWR|os.O_APPEND, 0644)
    //若果文件不存在就创建文件并打开
    defer LogFile.Close()

    if err != nil {
        log.Fatalln("fail to create My.log file!")
    }

    logger := log.New(LogFile, "[info]", log.Ldate|log.Ltime|log.Llongfile)
    logger.Println("Log info")

    logger.SetPrefix("[debug]")
    logger.Println("Log Debug")
}