Golang 文件操作

参考
Golang文件操作整理
golang中的文件读写

一、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 // 如果可能,打开时清空文件
)

二、读,创建

1.参考os.Open()和os.OpenFile()

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

参考go判断文件夹是否存在,并创建

// 判断文件夹是否存在
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包

参考用GO语言提取日志文件里的信息

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

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 194,491评论 5 459
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 81,856评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 141,745评论 0 319
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,196评论 1 263
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,073评论 4 355
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,112评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,531评论 3 381
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,215评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,485评论 1 290
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,578评论 2 309
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,356评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,215评论 3 312
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,583评论 3 299
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,898评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,174评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,497评论 2 341
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,697评论 2 335

推荐阅读更多精彩内容

  • Lua 5.1 参考手册 by Roberto Ierusalimschy, Luiz Henrique de F...
    苏黎九歌阅读 13,674评论 0 38
  • 本文为转载,原文:Golang 学习笔记(08)—— 文件操作 path 在path包中封装了一些路径相关的操作,...
    ChainZhang阅读 3,093评论 0 4
  • Truncate文件 得到文件信息 重命名和移动 译者按: rename 和 move 原理一样 删除文件 打开和...
    黑手党老k阅读 2,274评论 0 2
  • 滑雪后的某一天、冬阳约我看电影、电影票是朋友给的、我说下班比较晚、冬阳说那就定晚点时间看、他在家等我。 想想要和冬...
    泰梦鱼阅读 165评论 0 2
  • 又是全新的一天,早起无事,听着别人的故事,梳理着自己的情绪,不知不觉就走完了全程。不着急不功利,任凭车走,任凭景流...
    一星若月阅读 198评论 0 0