
文件操作与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") }