一、API
参考Go语言学习笔记(五)文件操作
1.os.File
-
type File
File代表一个打开的文件对象。 -
func Create(name string) (file *File, err error)
Create采用模式0666(任何人都可读写,不可执行)创建一个名为name的文件,如果文件已存在会截断它(为空文件)。如果成功,返回的文件对象可用于I/O;对应的文件描述符具有O_RDWR模式。如果出错,错误底层类型是*PathError。 -
func Open(name string) (file *File, err error)
Open打开一个文件用于读取。如果操作成功,返回的文件对象的方法可用于读取数据;对应的文件描述符具有O_RDONLY模式。如果出错,错误底层类型是*PathError。 -
func OpenFile(name string, flag int, perm FileMode) (file *File, err error)
OpenFile是一个更一般性的文件打开函数,大多数调用者都应用Open或Create代替本函数。它会使用指定的选项(如O_RDONLY等)、指定的模式(如0666等)打开指定名称的文件。如果操作成功,返回的文件对象可用于I/O。如果出错,错误底层类型是*PathError。 -
func NewFile(fd uintptr, name string) *File
NewFile使用给出的Unix文件描述符和名称创建一个文件。 -
func Pipe() (r *File, w *File, err error)
Pipe返回一对关联的文件对象。从r的读取将返回写入w的数据。本函数会返回两个文件对象和可能的错误。 -
func (f *File) Name() string
Name方法返回(提供给Open/Create等方法的)文件名称。 -
func (f *File) Stat() (fi FileInfo, err error)
Stat返回描述文件f的FileInfo类型值。如果出错,错误底层类型是*PathError。 -
func (f *File) Fd() uintptr
Fd返回与文件f对应的整数类型的Unix文件描述符。 -
func (f *File) Chdir() error
Chdir将当前工作目录修改为f,f必须是一个目录。如果出错,错误底层类型是*PathError。 -
func (f *File) Chmod(mode FileMode) error
Chmod修改文件的模式。如果出错,错误底层类型是*PathError。 -
func (f *File) Chown(uid, gid int) error
Chown修改文件的用户ID和组ID。如果出错,错误底层类型是*PathError。 -
func (f *File) Readdir(n int) (fi []FileInfo, err error)
Readdir读取目录f的内容,返回一个有n个成员的[]FileInfo,这些FileInfo是被Lstat返回的,采用目录顺序。对本函数的下一次调用会返回上一次调用剩余未读取的内容的信息。
如果n>0,Readdir函数会返回一个最多n个成员的切片。这时,如果Readdir返回一个空切片,它会返回一个非nil的错误说明原因。如果到达了目录f的结尾,返回值err会是io.EOF。
如果n<=0,Readdir函数返回目录中剩余所有文件对象的FileInfo构成的切片。此时,如果Readdir调用成功(读取所有内容直到结尾),它会返回该切片和nil的错误值。如果在到达结尾前遇到错误,会返回之前成功读取的FileInfo构成的切片和该错误。 -
func (f *File) Readdirnames(n int) (names []string, err error)
Readdir读取目录f的内容,返回一个有n个成员的[]string,切片成员为目录中文件对象的名字,采用目录顺序。对本函数的下一次调用会返回上一次调用剩余未读取的内容的信息。
如果n>0,Readdir函数会返回一个最多n个成员的切片。这时,如果Readdir返回一个空切片,它会返回一个非nil的错误说明原因。如果到达了目录f的结尾,返回值err会是io.EOF。
如果n<=0,Readdir函数返回目录中剩余所有文件对象的名字构成的切片。此时,如果Readdir调用成功(读取所有内容直到结尾),它会返回该切片和nil的错误值。如果在到达结尾前遇到错误,会返回之前成功读取的名字构成的切片和该错误。 -
func (f *File) Truncate(size int64) error
Truncate改变文件的大小,它不会改变I/O的当前位置。 如果截断文件,多出的部分就会被丢弃。如果出错,错误底层类型是*PathError。 -
func (f *File) Read(b []byte) (n int, err error)
Read方法从f中读取最多len(b)字节数据并写入b。它返回读取的字节数和可能遇到的任何错误。文件终止标志是读取0个字节且返回值err为io.EOF。 -
func (f *File) ReadAt(b []byte, off int64) (n int, err error)
ReadAt从指定的位置(相对于文件开始位置)读取len(b)字节数据并写入b。它返回读取的字节数和可能遇到的任何错误。当n<len(b)时,本方法总是会返回错误;如果是因为到达文件结尾,返回值err会是io.EOF。 -
func (f *File) Write(b []byte) (n int, err error)
Write向文件中写入len(b)字节数据。它返回写入的字节数和可能遇到的任何错误。如果返回值n!=len(b),本方法会返回一个非nil的错误。 -
func (f *File) WriteString(s string) (ret int, err error)
WriteString类似Write,但接受一个字符串参数。 -
func (f *File) WriteAt(b []byte, off int64) (n int, err error)
WriteAt在指定的位置(相对于文件开始位置)写入len(b)字节数据。它返回写入的字节数和可能遇到的任何错误。如果返回值n!=len(b),本方法会返回一个非nil的错误。 -
func (f *File) Seek(offset int64, whence int) (ret int64, err error)
Seek设置下一次读/写的位置。offset为相对偏移量,而whence决定相对位置:0为相对文件开头,1为相对当前位置,2为相对文件结尾。它返回新的偏移量(相对开头)和可能的错误。 -
func (f *File) Sync() (err error)
Sync递交文件的当前内容进行稳定的存储。一般来说,这表示将文件系统的最近写入的数据在内存中的拷贝刷新到硬盘中稳定保存。 -
func (f *File) Close() error
Close关闭文件f,使文件不能用于读写。它返回可能出现的错误。
2.文件打开模式:
const (
O_RDONLY int = syscall.O_RDONLY // 只读模式打开文件
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 // 如果可能,打开时清空文件
)
二、读,创建
logFile,err:=os.Open("log/system.txt")
if err!=nil{
log.Fatalln("读取日志文件失败",err)
}
defer logFile.Close()
logger:=log.New(logFile,"\r\n",log.Ldate|log.Ltime)
logger.Print("hello")
发现怎么都不能往system.txt文件中写入hello字符串,改了一下:
logFile,err:=os.OpenFile("log/system.txt",os.O_RDWR|os.O_CREATE,0)
...
结论就是Open方法只能读
2.创建
f,err := os.Create(fileName)
defer f.Close()
if err !=nil {
fmt.Println(err.Error())
} else {
_,err=f.Write([]byte("要写入的文本内容"))
checkErr(err)
}
三、创建文件夹Mkdir
// 判断文件夹是否存在
func PathExists(path string) (bool, error) {
_, err := os.Stat(path)
if err == nil {
return true, nil
}
if os.IsNotExist(err) {
return false, nil
}
return false, err
}
func main() {
_dir := "./gzFiles2"
exist, err := PathExists(_dir)
if err != nil {
fmt.Printf("get dir error![%v]\n", err)
return
}
if exist {
fmt.Printf("has dir![%v]\n", _dir)
} else {
fmt.Printf("no dir![%v]\n", _dir)
// 创建文件夹
err := os.Mkdir(_dir, os.ModePerm)
if err != nil {
fmt.Printf("mkdir failed![%v]\n", err)
} else {
fmt.Printf("mkdir success!\n")
}
}
}
四、删除
文件删除的时候,不管是普通文件还是目录文件,都可以用err:=os.Remove(filename)这样的操作来执行。当然要是想移除整个文件夹,直接使用RemoveAll(path string)操作即可。可以看一下RemoveAll函数的内部实现,整体上就是遍历,递归的操作过程,其他的类似的文件操作都可以用类似的模板来实现
os.RemoveAll("./gzFiles2")
五、读文件
这一部分较多的涉及I/O的相关操作,系统的介绍放在I/O那部分来整理,大体上向文件中读写内容的时候有三种方式:
1、在使用f, err := os.Open(file_path)
打开文件之后直接使用 f.read() f.write()
结合自定义的buffer每次从文件中读入/读出固定的内容
2、使用ioutl的readFile和writeFile方法
3、使用bufio采用带有缓存的方式进行读写,比如通过info:=bufio.NewReader(f)
将实现了io.Reader的接口的实例加载上来之后,就可以使用info.ReadLine()来每次实现一整行的读取,直到err信息为io.EOF时,读取结束
Golang几种读文件方式的比较对三种文件操作的读入速度进行了比较
package main
import(
"fmt"
"os"
"flag"
"io"
"io/ioutil"
"bufio"
"time"
)
func read1(path string)string{
fi,err := os.Open(path)
if err != nil{
panic(err)
}
defer fi.Close()
chunks := make([]byte,1024,1024)
buf := make([]byte,1024)
for{
n,err := fi.Read(buf)
if err != nil && err != io.EOF{panic(err)}
if 0 ==n {break}
chunks=append(chunks,buf[:n]...)
// fmt.Println(string(buf[:n]))
}
return string(chunks)
}
func read2(path string)string{
fi,err := os.Open(path)
if err != nil{panic(err)}
defer fi.Close()
r := bufio.NewReader(fi)
chunks := make([]byte,1024,1024)
buf := make([]byte,1024)
for{
n,err := r.Read(buf)
if err != nil && err != io.EOF{panic(err)}
if 0 ==n {break}
chunks=append(chunks,buf[:n]...)
// fmt.Println(string(buf[:n]))
}
return string(chunks)
}
func read3(path string)string{
fi,err := os.Open(path)
if err != nil{panic(err)}
defer fi.Close()
fd,err := ioutil.ReadAll(fi)
// fmt.Println(string(fd))
return string(fd)
}
func main(){
flag.Parse()
file := flag.Arg(0)
f,err := ioutil.ReadFile(file)
if err != nil{
fmt.Printf("%s\n",err)
panic(err)
}
fmt.Println(string(f))
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))
}
运行命令go run read.go filename, 指定需要读取的文件就可以测试了。
在golang bufio、ioutil读文件的速度比较(性能测试)和影响因素分析 中,给出了结论:
在查阅golang标准库的源代码后,之所以有不同的结果是与每个方法的实现相关的,最大的因素就是内部buffer的大小,这个直接决定了读取的快慢:
- 1.f.Read()底层实现是系统调用syscall.Read(),没有深究
- 2.bufio.NewReader(f)实际调用NewReaderSize(f, defaultBufSize),而defaultBufSize=4096,可以直接用bufio.NewReaderSize(f,32768)来预分配更大的缓存,缓存的实质是make([]byte, size)
- 3.ioutil.ReadAll(f)实际调用readAll(r, bytes.MinRead),而bytes.MinRead=512,缓存的实质是bytes.NewBuffer(make([]byte, 0, 512),虽然bytes.Buffer会根据情况自动增大,但每次重新分配都会影响性能
- 4.ioutil.ReadFile(path)是调用readAll(f, n+bytes.MinRead),这个n取决于文件大小,文件小于10^9字节(0.93GB),n=文件大小,就是NewBuffer一个略大于文件大小的缓存,非常慷慨;大于则n=0,好惨,也就是说大于1G的文件就跟ioutil.ReadAll(f)一个样子了。
- 5.但全量缓存的ReadFile为什么不如大块读取的前两者呢?我猜测是NewBuffer包装的字节数组性能当然不如裸奔的字符数组。。
结论
- •当每次读取块的大小小于4KB,建议使用bufio.NewReader(f), 大于4KB用bufio.NewReaderSize(f,缓存大小)
- •要读Reader, 图方便用ioutil.ReadAll()
- •一次性读取文件,使用ioutil.ReadFile()
- •不同业务场景,选用不同的读取方式
在Golang 超大文件读取的两个方案中提到几个G的日志文件读取
比如我们有一个 log 文件,运行了几年,有 100G 之大。按照我们之前的操作可能代码会这样写:
func ReadFile(filePath string) []byte{
content, err := ioutil.ReadFile(filePath)
if err != nil {
log.Println("Read error")
}
return content
}
上面的代码读取几兆的文件可以,但是如果大于你本身及其内存,那就直接翻车了。因为上面的代码,是把文件所有的内容全部都读取到内存之后返回,几兆的文件,你内存够大可以处理,但是一旦上几百兆的文件,就没那么好处理了。那么,正确的方法有两种,第一个是使用流处理方式代码如下:
func ReadFile(filePath string, handle func(string)) error {
f, err := os.Open(filePath)
defer f.Close()
if err != nil {
return err
}
buf := bufio.NewReader(f)
for {
line, err := buf.ReadLine("\n")
line = strings.TrimSpace(line)
handle(line)
if err != nil {
if err == io.EOF{
return nil
}
return err
}
return nil
}
}
第二个方案就是分片处理,当读取的是二进制文件,没有换行符的时候,使用下面的方案一样处理大文件
func ReadBigFile(fileName string, handle func([]byte)) error {
f, err := os.Open(fileName)
if err != nil {
fmt.Println("can't opened this file")
return err
}
defer f.Close()
s := make([]byte, 4096)
for {
switch nr, err := f.Read(s[:]); true {
case nr < 0:
fmt.Fprintf(os.Stderr, "cat: error reading: %s\n", err.Error())
os.Exit(1)
case nr == 0: // EOF
return nil
case nr > 0:
handle(s[0:nr])
}
}
return nil
}
bufio更多参考golang中bufio包
package test
important (
"bufio"
"io"
"os"
"regexp"
"strconv"
"strings"
"path/filepath"
"fmt"
"runtime"
"testing"
)
func TestLog(t *Testing.T) { //测试函数,参数必须是t *Testing.T
f,_:= os.Open("D:/file.log") //日志文件路径
defer f.Close()
//存储最后的结果切片,因为是要找出每一个时间值,所以用切片来存储
var resultSlice []int
//正则表达式,是为了从日志文件里获23_3executeTime:1234这种数据结果。
var exp = regexp.MustCompile(`[\d]+_[\d]+(executeTime:)[\d]+`)
buf :=bufio.NewReader(f) //读取日志文件里的字符流
for { //逐行读取日志文件
line,err :=buf.ReadString('\n')
expArr := exp.FindAllString(line,-1)
if len(expArr) > 0 {
arr := strings.Split(expArr[0],":")
value,_ := strconv.Atoi(arr[1])
resultSlice = append(resultSlice,value)
}
if err != nil {
if err = io.EOF {
break //表示文件读取完了
}
}
}
fmt.Println(len(resultSlice)) //打印出结果的总条数
bubbleSort(result) //对结果排序
amount :=0
for i :=0;i< len(resultSlice); i++ {
amount +=resultSlice[i]
}
average := amount/len(resultSlice)
fmt.Println("average",average,"amount",amount)
for k,v :=range resultSlice {
fmt.Println(k,v) //逐条逐条打印出时间。
}
}
//注:在这里省略了冒泡排序函数。
六、写文件
更多参考Golang读写文件的几种方式
WriteFile将data写入到filename指定的文件中。如果文件不存在,WriteFile将会创建该文件,且文件的权限是perm;如果文件存在,先清空文件内容再写入。
content := []byte("hello golang")
//将指定内容写入到文件中
err := ioutil.WriteFile("output.txt", content, 0666)
if err != nil {
fmt.Println("ioutil WriteFile error: ", err)
}
追加文件内容
func main() {
f, err := os.OpenFile("output.txt", os.O_RDWR|os.O_CREATE|os.O_APPEND, 0777)
if err != nil {
fmt.Println("os OpenFile error: ", err)
return
}
defer f.Close()
f.WriteString("another content")
}
网友评论