Go36-44,45-文件操作(os.File)

栏目: Go · 发布时间: 7年前

内容简介:通过os包,可以拥有操控计算机操作系统的能力。这个代码包提供的都是平台不相关的API。无论是Linux、macOS、Windows、FreeBSD、OpenBSD、Plan9,os包都可以提供统一的使用接口。这样就可以用同样的方法来操纵不同的操作系统,并得到相似的结果。os包中的API主要可以帮助我们使用操作系统中的文件系统、权限系统、环境变量、系统进程以及系统信号。其中,文件系统的API最丰富。不但可以用来创建和删除文件以及目录,还可以获取到各种信息、修改内容、修改访问权限。等等。这里,最常用的数据类型就

os包

通过os包,可以拥有操控计算机操作系统的能力。这个代码包提供的都是平台不相关的API。无论是 Linux 、macOS、Windows、FreeBSD、OpenBSD、Plan9,os包都可以提供统一的使用接口。这样就可以用同样的方法来操纵不同的操作系统,并得到相似的结果。

os包中的API主要可以帮助我们使用操作系统中的文件系统、权限系统、环境变量、系统进程以及系统信号。其中,文件系统的API最丰富。不但可以用来创建和删除文件以及目录,还可以获取到各种信息、修改内容、修改访问权限。等等。这里,最常用的数据类型就是:os.File。

os.File类型介绍

从字面上看,os.File类型代表了操作系统中的文件,但是实际上,它代表的远不止于此。比如对于类Unix的操作系统,包括Linux、macOS、FreeBSD等,其中的一切都可以被看作是文件。

除了文本文件、二进制文件、压缩文件、目录这些常见的形式之外,还有符号链接、各种物理设备(包括内置或外接的面向块或者字符的设备)、命名管道,以及套接字(也就是socket),等等。所以能够利用os.File类型操纵的东西有很多。不过接下来主要介绍os.File类型应用于常规文件。

实现的io接口

os.File类型拥有的都是指针方法,它的指针实现了很多io包中的接口。

对于io包中最核心的3个简单接口io.Reader、io.Writer和io.Closer,*os.File类型都实现了它们。另外还顺便实现了io包中的9个扩展接口中的7个。没有实现简单接口io.ByteReader和io.RuneReader,所以也没有实现上面这两个的扩展接口io.ByteScannser和io.RuneScanner。

总之,os.File类型及其指针类型的值,不但可以通过各种方式读取和写入某个文件中的内容,还可以寻找并设定下一次读取或写入时的起始索引位置,另外还可以随时对文件进行关闭。但是,它并不能专门的读取文件中的下一个字节或Unicode字符,也不能进行任何的读回退操作。不过,单读读取下一个字节或字符的功能也可以通过其他方式来实现。比如用Read方法传入适当的参数。

使用反射检查接口的实现下面的示例枚举了io包中的所有接口,检查*os.File是否实现了该接口:

package main

import (
    "bytes"
    "fmt"
    "io"
    "os"
    "reflect"
)

// ioTypes 代表了io代码包中的所有接口的反射类型。
var ioTypes = []reflect.Type{
    reflect.TypeOf((*io.Reader)(nil)).Elem(),
    reflect.TypeOf((*io.Writer)(nil)).Elem(),
    reflect.TypeOf((*io.Closer)(nil)).Elem(),

    reflect.TypeOf((*io.ByteReader)(nil)).Elem(),
    reflect.TypeOf((*io.RuneReader)(nil)).Elem(),
    reflect.TypeOf((*io.ReaderAt)(nil)).Elem(),
    reflect.TypeOf((*io.Seeker)(nil)).Elem(),
    reflect.TypeOf((*io.WriterTo)(nil)).Elem(),
    reflect.TypeOf((*io.ByteWriter)(nil)).Elem(),
    reflect.TypeOf((*io.WriterAt)(nil)).Elem(),
    reflect.TypeOf((*io.ReaderFrom)(nil)).Elem(),

    reflect.TypeOf((*io.ByteScanner)(nil)).Elem(),
    reflect.TypeOf((*io.RuneScanner)(nil)).Elem(),
    reflect.TypeOf((*io.ReadSeeker)(nil)).Elem(),
    reflect.TypeOf((*io.ReadCloser)(nil)).Elem(),
    reflect.TypeOf((*io.WriteCloser)(nil)).Elem(),
    reflect.TypeOf((*io.WriteSeeker)(nil)).Elem(),
    reflect.TypeOf((*io.ReadWriter)(nil)).Elem(),
    reflect.TypeOf((*io.ReadWriteSeeker)(nil)).Elem(),
    reflect.TypeOf((*io.ReadWriteCloser)(nil)).Elem(),
}

func main() {
    var file os.File
    fileType := reflect.TypeOf(&file)
    var buf bytes.Buffer  // 存放没有实现的那些接口信息,最后统一打印出来
    fmt.Fprintf(&buf, "Type %T not implements:\n", &file)
    fmt.Printf("Type %T implements:\n", &file)
    for _, t := range ioTypes {
        if fileType.Implements(t) {
            fmt.Println(t.String())
        } else {
            fmt.Fprintln(&buf, t.String())
        }
    }
    fmt.Println()
    fmt.Println(buf.String())
}

一般要检查接口是否实现了,不需要用到反射这么高级的用法。

操作文件

要操作文件,首先要获取一个os.File类型的指针值,简称File值。在os包中,有如下几个函数:

  • Create
  • NewFile
  • Open
  • OpenFile

os.Create函数

用于根据给定的路径创建一个新的文件。它会返回一个File值和一个错误值。可以在该函数返回的File值之上,对相应的文件进行读操作和写操作。使用这个函数创建的文件,对操作系统中的所有用户来说都是可以读和写的。

注意,如果给定的路径已经存在一个文件了,那么该函数会先清空现有文件中的内容,然后再把该文件的File值返回。就是覆盖原有文件创建一个新的空文件。另外,如果有错误,会通过第二个参数返回错误值。比如,如果路径不存在,那么会返回一个*os.PathErro类型的错误值。

下面的示例,尝试当在前目录下创建一个文件。还会把当前目录的名称截掉最后一个字符,这应该会是一个不存在的目录,同样尝试创建一个文件,然后会返回一个预期的错误:

package main

import (
    "fmt"
    "os"
    "path/filepath"
)

func main() {
    tempPath := os.TempDir()
    fmt.Println("系统的临时文件夹:", tempPath)

    fileName := "test.txt"
    var paths []string
    dir, _ := os.Getwd()
    dirPath := filepath.Join(dir, fileName)  // 在当前文件夹下创建一个文件
    paths = append(paths, dirPath)
    notExistsPath := filepath.Join(dir[:len(dir)-1], fileName)  // 这个文件夹路径应该不存在
    paths = append(paths, notExistsPath)

    for _, path := range paths {
        fmt.Println("创建文件:", path)
        _, err := os.Create(path)  // 返回的第一个参数是*File,就不要的
        if err != nil {
            var underlyingErr string
            if _, ok := err.(*os.PathError); ok {
                underlyingErr = "(path error)"
            }
            fmt.Fprintf(os.Stderr, "ERROR: %v %s\n", err, underlyingErr)
            continue
        }
        fmt.Println("创建文件成功.")
    }
}

os.NewFile函数

该函数在被调用的时候需要接受一个代表文件描述符的uintptr类型的值,以及一个用于表示文件名的字符串。如果给定的不是有效的文件描述符,那么会返回nil。否则,返回相应文件的File值。这里不要被函数名称误导,它的功能不是创建一个新的文件,而是依据一个已经存在的文件的描述符,来新建一个包装了该文件的File值。比如,可以像这样拿到一个包装了标准错误输出的File值:

file := os.NewFile(uintptr(syscall.Stderr), "/dev/stderr")

然后,通过这个File值向标准错误输出写入一些内容,一般的效果就是打印出错误信息:

if file != nil {
    file.WriteString("Test Stderr.\n")
}

如果是一个已存在的文件,可以使用该文件的文件描述符作为函数的第一个参数返回File值。下面文件描述符的内容里还有一个示例。

os.Open函数

打开一个文件并返回包装了该文件的File值。该函数只能以只读模式打开文件。如果调用了File值的任何写入方法,都会返回错误:

package main

import (
    "fmt"
    "os"
    "path/filepath"
)

func main() {
    fileName := "test.txt"
    dir, _ := os.Getwd()
    dirPath := filepath.Join(dir, fileName)
    file, err := os.Open(dirPath)
    if err != nil {
        // 文件可能不存在,先创建一个文件
        fmt.Fprintf(os.Stderr, "ERROR: %v\n", err)
        return
    }
    _, err = file.WriteString(" ")  // 文件是只读的,尝试写入会返回错误
    var underlyingErr string
    if _, ok := err.(*os.PathError); ok {
        underlyingErr = "(path error)"
    }
    fmt.Fprintf(os.Stderr, "ERROR: %v %s\n", err, underlyingErr)
}

文件描述符

实际上,上面说的只读模式,正是应用在File值所持有的文件描述符之上的。

文件描述符,是由通常很小的非负整数代表的。它一般会由I/O相关的系统调用返回,并作为某个文件的一个表示存在。

从操作系统的层面看,针对任何文件的I/O操作都需要用到这个文件描述符。只不过,Go语言中的一些数据类型,为我们隐匿掉了这个描述符。实际上,在调用os.Create函数、os.Open函数以及之后会提到的os.OpenFile函数时,都会执行同一个系统调用,并且在成功之后会得到这样一个文件描述符。这个文件描述符将会被存储在返回的File值中。os.File类型有一个Fd的指针方法,返回一个uintptr类型的值。这个值就代表了当前的File值所持有的那个文件描述符。

不过在os包中,只有NewFile函数需要用到它。所以,如果操作的只是常规的文件或目录,也无需特别在意。

文件描述符相关的示例:

package main

import (
    "fmt"
    "os"
    "path/filepath"
)

func main() {
    fileName := "test.txt"
    dir, _ := os.Getwd()
    dirPath := filepath.Join(dir, fileName)
    file1, err := os.Open(dirPath)
    if err != nil {
        // 文件可能不存在,先创建一个文件
        fmt.Fprintf(os.Stderr, "ERROR: %v\n", err)
        return
    }
    file2, _ := os.Open(dirPath)  // 虽然打开的是同一个文件,但却是不同的文件描述符
    file3 := os.NewFile(file1.Fd(), dirPath)  // 可以通过文件描述符,获取File值
    fmt.Println(file1.Fd(), file2.Fd(), file3.Fd())
}

通过Fd方法获取到的文件描述符可以通过os.NewFile函数返回File值。

os.OpenFile函数

这个函数其实是os.Create函数和os.Open函数的底层支持,它最灵活。这个函数有3个参数:

func OpenFile(name string, flag int, perm FileMode) (*File, error) {
    testlog.Open(name)
    return openFileNolog(name, flag, perm)
}

name参数,是文件的路径。

flag参数,是需要施加在文件描述符上的模式,叫操作模式,Open函数是只读的就是因为在Open函数里调用OpenFile的时候指定了该参数:

func Open(name string) (*File, error) {
    return OpenFile(name, O_RDONLY, 0)
}

perm参数,也是模式,叫权限模式。类型是os.FileMode,此类型是一个基于uint32类型的再定义类型:

type FileMode uint32

这里的两个模式:

  • flag : 操作模式,限定了操作文件方式
  • perm : 权限模式,控制文件的访问权限

关于操作模式和访问权限的更多细节,在后面继续讲。

打开文件并写入内容的操作示例:

package main

import (
    "fmt"
    "os"
    "path/filepath"
)

func main() {
    fileName := "test.txt"
    dir, _ := os.Getwd()
    dirPath := filepath.Join(dir, fileName)
    // O_WRONLY:只写模式。O_CREATE:文件不存在就创建。O_TRUNC:打开并清空文件
    file, err := os.OpenFile(dirPath, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0666)
    if err != nil {
        fmt.Fprintf(os.Stderr, "ERROR: %v\n", err)
        return
    }
    n, err := file.WriteString("写入操作")
    if err != nil {
        fmt.Fprintf(os.Stderr, "ERROR: %v\n", err)
    } else {
        fmt.Println("写入字节数(bytes):", n)
    }
}

操作模式

针对File值的操作模式主要有:

  • 只读模式:os.O_RNONLY
  • 只写模式:os.O_WRONLY
  • 读写模式:os.O_RDWR

在新建一个文件的时候,必须把这三个模式中的一个设定为此文件的操作模式。

另外,还可以再设置额外的操作模式,选项如下:

  • os.O_APPEND : 写入时追加到现有内容的后边
  • os.O_CREARE : 当文件不存在是,创建一个新文件
  • os.O_EXCL : 需要与os.O_CREATE一同使用,表示给定的路径不能是一个已存在的文件。(就是指定的文件必须不存在,然后创建文件)
  • os.O_SYNC : 在打开的文件之上实施同步I/O。它会保证读写的内容总会与硬盘上的数据保持同步
  • os.O_TRUNC : 如果文件已存在,并且是常规文件,就先清空其中的内容。(就是创建文件,如果文件存在则新建并覆盖)

操作模式的使用

对于以上操作模式的使用,os.Open函数和os.Create函数都是现成的例子:

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

这里顺便也是下面访问权限的例子了。

这里可以看到,多个操作符是通过按位或操作符(|)组合起来的,常用的写模式的组合还有:

os.O_WRONLY|os.O_CREATE|os.O_EXCL
os.O_WRONLY|os.O_CREATE|os.O_TRUNC
os.O_WRONLY|os.O_APPEND

访问权限

os.OpenFile函数的第三个参数perm代表的是权限模式,类型是os.FileMode。实际上,os.FIleMode类型能够代表的,不只权限模式,还可以代表文件模式,也可以称之为文件种类。

os.FileMode是基于uint32类型的再定义类型,它包含了32个比特位。在这32个比特位中,每个比特位都有其特定的意义:

  • 如果最高位上的二进制数是1,那么该值的文件模式等同于os.ModeDir,这代表是一个目录。
  • 如果第26个比特位是1,那么该值的文件模式等同于os.ModeNamedPipe,这代表是一个命名管道。
  • 最低的9个比特位,这几位才用于表示文件的权限。这个权限参考Linux的ugo权限。

所有的常量都在源码里有说明:

const (
    // The single letters are the abbreviations
    // used by the String method's formatting.
    ModeDir        FileMode = 1 << (32 - 1 - iota) // d: is a directory
    ModeAppend                                     // a: append-only
    ModeExclusive                                  // l: exclusive use
    ModeTemporary                                  // T: temporary file; Plan 9 only
    ModeSymlink                                    // L: symbolic link
    ModeDevice                                     // D: device file
    ModeNamedPipe                                  // p: named pipe (FIFO)
    ModeSocket                                     // S: Unix domain socket
    ModeSetuid                                     // u: setuid
    ModeSetgid                                     // g: setgid
    ModeCharDevice                                 // c: Unix character device, when ModeDevice is set
    ModeSticky                                     // t: sticky
    ModeIrregular                                  // ?: non-regular file; nothing else is known about this file

    // Mask for the type bits. For regular files, none will be set.
    ModeType = ModeDir | ModeSymlink | ModeNamedPipe | ModeSocket | ModeDevice | ModeIrregular

    ModePerm FileMode = 0777 // Unix permission bits
)

可以像操作模式那样用按位或操作符(|)组合起来。一般也就直接0666或0777就好了。

补充-文件读写

这篇还是偏重理论,主要讲的打开文件的操作。在打开文件获取到os.File类型后,由于它的指针已经实现了各种io接口,之后的读写操作,就是通过调用*os.File实现的io接口来实现了。另外,io包还有一个ioutil子包,可以读取整个文件。而逐行读取文件的内容,也需要一些具体的实现。

下面这篇有一些文件读写的示例:

http://blog.51cto.com/steed/2315597

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持 码农网

查看所有标签

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

The Filter Bubble

The Filter Bubble

Eli Pariser / Penguin Press / 2011-5-12 / GBP 16.45

In December 2009, Google began customizing its search results for each user. Instead of giving you the most broadly popular result, Google now tries to predict what you are most likely to click on. Ac......一起来看看 《The Filter Bubble》 这本书的介绍吧!

CSS 压缩/解压工具
CSS 压缩/解压工具

在线压缩/解压 CSS 代码

html转js在线工具
html转js在线工具

html转js在线工具

HEX CMYK 转换工具
HEX CMYK 转换工具

HEX CMYK 互转工具