go 文件与目录操作

 

文件打开与关闭

文件打开

原始的文件打开函数:

func OpenFile(name string, flag int, perm FileMode) (*File, error)

name:绝对路径或相对路径(相对于进程当前工作目录)
flag:指定文件的访问模式,在os中这些参数被定义为常量

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

其中,O_RDONLY、O_WRONLY、O_RDWR 应该只指定一个,剩下的通过 | 操作符来指定。该函数内部会给 flags 加上 syscall.O_CLOEXEC,在 fork 子进程时会关闭通过 OpenFile 打开的文件,即子进程不会重用该文件描述符。

perm:指定了文件的模式和权限位,类型是 os.FileMode,文件模式位常量定义在 os

const (
// 单字符是被 String 方法用于格式化的属性缩写。
ModeDir FileMode = 1 << (32 - 1 - iota) // d: 目录
ModeAppend // a: 只能写入,且只能写入到末尾
ModeExclusive // l: 用于执行
ModeTemporary // T: 临时文件(非备份文件)
ModeSymlink // L: 符号链接(不是快捷方式文件)
ModeDevice // D: 设备
ModeNamedPipe // p: 命名管道(FIFO)
ModeSocket // S: Unix 域 socket
ModeSetuid // u: 表示文件具有其创建者用户 id 权限
ModeSetgid // g: 表示文件具有其创建者组 id 的权限
ModeCharDevice // c: 字符设备,需已设置 ModeDevice
ModeSticky // t: 只有 root/ 创建者能删除 / 移动文件

// 覆盖所有类型位(用于通过 & 获取类型位),对普通文件,所有这些位都不应被设置
ModeType = ModeDir | ModeSymlink | ModeNamedPipe | ModeSocket | ModeDevice
ModePerm FileMode = 0777 // 覆盖所有 Unix 权限位(用于通过 & 获取类型位)
)
perm参数

以上常量在所有操作系统都有相同的含义(可用时),因此文件的信息可以在不同的操作系统之间安全的移植。不是所有的位都能用于所有的系统,唯一共有的是用于表示目录的 ModeDir 位。

以上这些被定义的位是 FileMode 最重要的位。另外 9 个位(权限位)为标准 Unix rwxrwxrwx 权限(所有人都可读、写、运行)。

FileMode 还定义了几个方法,用于判断文件类型的 IsDir() 和 IsRegular(),用于获取权限的 Perm()。

返回的 error,具体实现是 *os.PathError,它会记录具体操作、文件路径和错误原因。

一般打开文件使用下面这两个函数:

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

文件关闭

close() 系统调用关闭一个打开的文件描述符,并将其释放回调用进程,供该进程继续使用。当进程终止时,将自动关闭其已打开的所有文件描述符。

func (f *File) Close() error

os.File.Close() 是对 close() 的封装。我们应该养成关闭不需要的文件的良好编程习惯。文件描述符是资源,Go 的 gc 是针对内存的,并不会自动回收资源,如果不关闭文件描述符,长期运行的服务可能会把文件描述符耗尽。

所以,通常的写法如下:

file, err := os.Open("/tmp/studygolang.txt")
if err != nil {
    // 错误处理,一般会阻止程序往下执行
    return
}
defer file.Close()

关于返回值 error

以下两种情况会导致 Close 返回错误:

1. 关闭一个未打开的文件;
2. 两次关闭同一个文件;

通常,我们不会去检查 Close 的错误。

文件打开与关闭示例

package main

 
import (
    "bufio"
    "fmt"
    "io"
    "io/ioutil"
    "os"
)
 
func main00(){
    //打开文件与1关闭文件
    file,err :=os.Open("C:/Users/Administrator/go/src/awesomeProject/demo/test/test.txt")
    if err == nil {
        fmt.Printf("文件打开成功")
        fmt.Println(file)
    } else{
        fmt.Printf("文件打开失败,err:",err)
        return
    }
    defer func(){
        file.Close()
        fmt.Printf("关闭文件")
    }()
    //
}

判断文件是否存在

 //使用os包状态检测结合os.IsNotExist(err)判断文件是否存在
    //获取指定文件的信息
    fileInfo,err := os.Stat("C:/Users/Administrator/go/src/awesomeProject/demo/test/test.txt")
    if err != nil {
        fmt.Println("err=",err)
        //校验错误是否为【文件不存在错误】
        if os.IsNotExist(err){
            fmt.Println("文件不存在!")
        }
        return
    }else{
        fmt.Println("文件存在!")
        fmt.Println(fileInfo)
    }

文件读取

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

Read 方法从 f 中读取最多 len(b) 字节数据并写入 b。它返回读取的字节数和可能遇到的任何错误。文件终止标志是读取 0 个字节且返回值 err 为 io.EOF。

从方法声明可以知道,File 实现了 io.Reader 接口。

Read 对应的系统调用是 read。

对比下 ReadAt 方法:

func (f *File) ReadAt(b []byte, off int64) (n int, err error)

ReadAt 从指定的位置(相对于文件开始位置)读取长度为 len(b) 个字节数据并写入 b。它返回读取的字节数和可能遇到的任何错误。当 n<len(b) 时,本方法总是会返回错误;如果是因为到达文件结尾,返回值 err 会是 io.EOF。它对应的系统调用是 pread。

Read 和 ReadAt 的区别:前者从文件当前偏移量处读,且会改变文件当前的偏移量;而后者从 off 指定的位置开始读,且不会改变文件当前偏移量。

func main01(){
    //以只读方式打开一个文件,创建其带缓冲的读取器,逐行读取到末尾
    //4=readable,2=writeable,1=executeable,6=4+2
    file,err :=os.OpenFile("C:/Users/Administrator/go/src/awesomeProject/demo/test/test.txt",os.O_RDONLY,0666)
    //判断读入是否成功
    if err == nil {
        fmt.Printf("文件打开成功")
        //打印内容
        fmt.Println(file)
    } else{
        fmt.Printf("文件打开失败,err:",err)
        return
    }
    //延迟关闭文件:在函数return前执行的程序
    defer func(){
        file.Close()
        fmt.Printf("关闭文件")
    }()
    //创建文件的读取器
    Reader := bufio.NewReader(file)
    for{
        //一次读取一行
        data,err := Reader.ReadString('\n')
        if err == nil{
            fmt.Println(data)
        }else{
            //到达文件结尾,跳出循环
            if err == io.EOF{
                fmt.Println("已经文件结尾")
                break
            }else{
                //读取异常,打印异常并结束
                fmt.Println("读取失败,err:",err)
                return
            }
        }
    }
}  

文件写入

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

Write 向文件中写入 len(b) 字节数据。它返回写入的字节数和可能遇到的任何错误。如果返回值 n!=len(b),本方法会返回一个非 nil 的错误。

从方法声明可以知道,File 实现了 io.Writer 接口。

Write 对应的系统调用是 write。

Write 与 WriteAt 的区别同 Read 与 ReadAt 的区别一样。为了方便,还提供了 WriteString 方法,它实际是对 Write 的封装。

注意:Write 调用成功并不能保证数据已经写入磁盘,因为内核会缓存磁盘的 I/O 操作。如果希望立刻将数据写入磁盘(一般场景不建议这么做,因为会影响性能),有两种办法:

1. 打开文件时指定 `os.O_SYNC`;
2. 调用 `File.Sync()` 方法。
说明:File.Sync() 底层调用的是 fsync 系统调用,这会将数据和元数据都刷到磁盘;如果只想刷数据到磁盘(比如,文件大小没变,只是变了文件数据),需要自己封装,调用 fdatasync 系统调用。(syscall.Fdatasync)

func mian04(){
    //以【创写追加】或【创写覆盖】方式打开一个文件,缓冲写入几行数据,倒干缓冲区
    //打开文件,模式:不存在就创建+只写+追加,生成的文件的权限666
    file,err :=os.OpenFile("C:/Users/Administrator/go/src/awesomeProject/demo/test/test.txt",os.O_CREATE|os.O_WRONLY|os.O_APPEND,0666)
    //判断读入是否成功
    if err != nil {
        fmt.Printf("文件打开失败,err:",err)
        return
    }
    //延迟关闭文件:在函数return前执行的程序
    defer func(){
        file.Close()
        fmt.Printf("关闭文件")
    }()
    //分批写入数据
    writer := bufio.NewWriter(file)
    writer.WriteString("hello")
    writer.WriteString("2019")
    writer.WriteString("。。。。")
    //写入一个字符
    writer.WriteRune('你')
    writer.WriteRune('好')
    //写一位数据
    writer.WriteByte(123)
    //写一个字节数据,范围:0-255
    writer.Write([]byte{123,222,21})
    //把缓冲区清空,立即将最后的数据写入文件
    writer.Flush()
}
func main05(){
    //反引号代表保留原始排版的字符串
    data := `测试文档、1、2、3、4、5。`
    fmt.Printf("data type=%T,value=%v\n",data,data)
    //将数据字符串转换为原始字节切片
    dataBytes := []byte(data)
    fmt.Printf("data type=%T,value=%v\n",dataBytes,dataBytes)
    //向指定文件写入上面的字节数据
    err := ioutil.WriteFile("C:/Users/Administrator/go/src/awesomeProject/demo/test/test.txt",dataBytes,0666)
    if err != nil {
        fmt.Println("写入错误")
    }else{
        fmt.Println("写入成功")
    }
}

文件拷贝

//使用ioutil包做一个傻瓜式拷贝
//使用io.Copy进行文件拷贝
//使用缓冲1K的缓冲区配合缓冲读写器进行图片拷贝
func main07(){
    //使用ioutil包做一个傻瓜式拷贝
    data,_:= ioutil.ReadFile("C:/Users/Administrator/go/src/awesomeProject/demo/test/test.txt")
    err := ioutil.WriteFile("C:/Users/Administrator/go/src/awesomeProject/demo/test/test1.txt",data,0666)
    if err != nil{
        fmt.Println("拷贝失败",err)
    }else{
        fmt.Println("拷贝成功")
    }
    fmt.Println("执行完成")
}
 
func main08(){
    //使用io.Copy进行文件拷贝
 
    //打开拷贝源文件,模式为只读模式
    srcFile,err := os.OpenFile("C:/Users/Administrator/go/src/awesomeProject/demo/test/test.txt",os.O_RDONLY,0666)
 
    //打开目标文件,模式为创建|写入
    dstFile,err := os.OpenFile("C:/Users/Administrator/go/src/awesomeProject/demo/test/test1.txt",os.O_WRONLY |os.O_CREATE,0666)
 
    //执行源文件到目标文件的拷贝,writen为拷贝字节数
    writen,err := io.Copy(dstFile,srcFile)
 
    if err == nil {
        fmt.Println("拷贝成功,字节数=",writen)
    }else{
        fmt.Println("拷贝失败,err=",err)
    }
}
 
func main09(){
    //使用缓冲1K的缓冲区配合缓冲读写器进行图片拷贝
 
    //打开源文件
    srcFile,_ := os.OpenFile("C:/Users/Administrator/go/src/awesomeProject/demo/test/test.txt",os.O_RDONLY,0666)
 
    //打开目标文件
    dstFile,_ := os.OpenFile("C:/Users/Administrator/go/src/awesomeProject/demo/test/test1.txt",os.O_WRONLY |os.O_CREATE,0666)
 
    defer func(){
        srcFile.Close()
        dstFile.Close()
        fmt.Println("文件全部关闭!")
    }()
    //创建源文件的缓冲读取器
    reder := bufio.NewReader(srcFile)
    //创建目标文件的缓冲写出器
    writer := bufio.NewWriter(dstFile)
 
    //创建缓冲区
    buffer := make([]byte,1024)
 
    //将数据存入,直到io.EOF
    for {
        //循环的读取数据到缓冲区,然后把缓冲区的数据写入到目标文件
        _,err :=reder.Read(buffer)
        if err !=nil{
            if err == io.EOF{
                fmt.Println("源文件读取完毕!")
                break
            }else{
                fmt.Println("读取文件发生错误,err=",err)
            }
        }else{
            _,err := writer.Write(buffer)
            if err !=nil {
                fmt.Println("写出错误,err=",err)
                return
            }
        }
        }
}

改变文件偏移量:Seek

对于每个打开的文件,系统内核会记录其文件偏移量,有时也将文件偏移量称为读写偏移量或指针。文件偏移量是指执行下一个 Read 或 Write 操作的文件其实位置,会以相对于文件头部起始点的文件当前位置来表示。文件第一个字节的偏移量为 0。

文件打开时,会将文件偏移量设置为指向文件开始,以后每次 Read 或 Write 调用将自动对其进行调整,以指向已读或已写数据后的下一个字节。因此,连续的 Read 和 Write 调用将按顺序递进,对文件进行操作。

而 Seek 可以调整文件偏移量。方法定义如下:

func (f *File) Seek(offset int64, whence int) (ret int64, err error)

Seek 设置下一次读 / 写的位置。offset 为相对偏移量,而 whence 决定相对位置:0 为相对文件开头,1 为相对当前位置,2 为相对文件结尾。它返回新的偏移量(相对开头)和可能的错误。使用中,whence 应该使用 os 包中的常量:SEEK_SETSEEK_CUR 和 SEEK_END

注意:Seek 只是调整内核中与文件描述符相关的文件偏移量记录,并没有引起对任何物理设备的访问。

一些 Seek 的使用例子(file 为打开的文件对象),注释说明了将文件偏移量移动到的具体位置:

file.Seek(0, os.SEEK_SET)    // 文件开始处
file.Seek(0, SEEK_END)        // 文件结尾处的下一个字节
file.Seek(-1, SEEK_END)        // 文件最后一个字节
file.Seek(-10, SEEK_CUR)     // 当前位置前 10 个字节
file.Seek(1000, SEEK_END)    // 文件结尾处的下 1001 个字节

最后一个例子在文件中会产生“空洞”。

Seek 对应系统调用 lseek。该系统调用并不适用于所有类型,不允许将 lseek 应用于管道、FIFO、socket 或 终端。

 

文件目录操作

创建名称为name的目录,权限设置是perm,例如0777

func Mkdir(name string, perm FileMode) error

根据path创建多级子目录,例如astaxie/test1/test2。

 func MkdirAll(path string, perm FileMode) error

删除名称为name的目录,当目录下有文件或者其他目录时会出错

func Remove(name string) error

根据path删除多级子目录,如果path是单个名称,那么该目录下的子目录全部删除。

func RemoveAll(path string) error 

更改文件名,Rename 修改一个文件的名字或移动一个文件。如果 newpath 已经存在,则替换它。

func Rename(oldpath, newpath string) error

  

例子:

package main

import (
    "fmt"
    "os"
)

func main() {
    os.Mkdir("test", 0777)
    os.MkdirAll("test/test1/test2", 0777)
    err := os.Remove("test")
    if err != nil {
        fmt.Println(err)
    }
    os.RemoveAll("test")
}

  

 

笔记来源:

https://books.studygolang.com/The-Golang-Standard-Library-by-Example/chapter06/06.1.html

posted @ 2020-02-20 11:12  -零  阅读(3108)  评论(0编辑  收藏  举报