Go 文件操作详解

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

内容简介:Go 在Go 在 os 中定义了打开一个文件进行读直接使用

Go 在 os 中提供了文件的基本操作,包括通常意义的打开、创建、读写等操作,除此以外为了追求便捷以及性能上,Go 还在 io/ioutil 以及 bufio 提供一些其他函数供开发者使用,今天在这篇文章中,我们介绍一些常用文件操作在 Go 中是如何使用的。

File 文件类型

Go 在 os 中定义了 File 类型:

type File struct {
        // contains filtered or unexported fields
}

打开一个文件进行读直接使用 os.Open :

file, err := os.Open("msg.txt")

os.Open 只接受一个文件名参数,默认打开的文件只支持读操作,文件的读写 flag 是以常量的形式定义的 Constants 分别是:

const (
        // Exactly one of O_RDONLY, O_WRONLY, or O_RDWR must be specified.
        O_RDONLY int = syscall.O_RDONLY // open the file read-only.
        O_WRONLY int = syscall.O_WRONLY // open the file write-only.
        O_RDWR   int = syscall.O_RDWR   // open the file read-write.
        // The remaining values may be or'ed in to control behavior.
        O_APPEND int = syscall.O_APPEND // append data to the file when writing.
        O_CREATE int = syscall.O_CREAT  // create a new file if none exists.
        O_EXCL   int = syscall.O_EXCL   // used with O_CREATE, file must not exist.
        O_SYNC   int = syscall.O_SYNC   // open for synchronous I/O.
        O_TRUNC  int = syscall.O_TRUNC  // truncate regular writable file when opened.
)

os.Open 打开的文件其实就只有 O_RDONLY flag。

文件读取

读取文件操作时通过 File 的方法 Read 进行的,这个方法接受一个参数 buf []byte ,默认读取的内容大小是 len(buf) ,并且返回读取的字节 size 和错误(如果有的话),如果读取到了文件末尾,则返回 0以及 io.EOF

if err != nil {
    fmt.Println(err)
}
buf := make([]byte, 126)
n, err := file.Read(buf)
if err != nil {
    fmt.Println(err)
}

fmt.Printf("%d = %q", n, buf)

按行读取

在大多数文件操作中,我们可能只需要的一行行读取文件就可以满足需要,在 Go 中如何读取行呢?至少在 os 这个 package 中好像没有找到相关操作,其实 Go 已经在其他包中提供了这个操作 bufio

bufio 顾名思义就是带 buffer 的 IO,由于频繁读写磁盘会有相当的性能开销,因为一次磁盘的读写就是一次系统的调用,所以 Go 提供了一个 buffer 来缓冲读写的数据,比如多次写磁盘 bufio 就会把数据先缓冲起来,待 buffer 装满之后一次性写入,又比如多次读数据,bufio 会预先按照 buffer 的大小(一般是磁盘 block size 的整数倍)尽量多的读取数据,也就是采用预读的技术以提高读的性能。

bufio 提供了 ReaderWriterScanner 来进行文件的读写,其中 Reader 和 Scanner 都支持按行读取文件。

Reader 读取行

使用 Reader 的 ReadLine 按行读,其中 file 表示我们刚才打开的文件:

reader := bufio.NewReader(file)
buf, _, err = reader.ReadLine()

ReadLine 读取文件的一行,默认是以 \r\n 或者 \n 分割,并且不包括分割符,如果行太长超过了内部 buffer 的大小,第二个返回值 isPrefix 就会被设置,直到 isPrefix 为 false 为止,表示一行读取完成。

除了 ReadLine 之外, ReadBytes 也支持按行读取,区别是 ReadBytes 需要显示的指定分隔符,而且其返回的数据中包括分割符:

buf, err = reader.ReadBytes('\n')
fmt.Printf("%d = %q", len(buf), buf) //输出包含 \n

除了对行的读取,bufio.Reader 还包含 ReadRune 、ReadSlice、ReadString 等读取内容的函数。

Scanner 读取行

Scanner 其实类似于 Reader,但是 scanner 有更强的便捷性,scanner 的主要目的就是利用各种分隔符来读取行,他提供了 SplitFunc 来自定义对文件内容的分割:

scanner := bufio.NewScanner(file)
for scanner.Scan() {
    fmt.Println(scanner.Text())
}

上面的代码会把文件 file 的内容按行输出,为什么恰好会按行输出?主要原因是 scanner 提供的默认的 SplitFuncScanLines ,也就是 scanner.Text() 方法使用就是这个 splitfunc。

接下类我们使用一个自定义的 SplitFunc 来实现从文本中找到可以转换成数字的字符。

r := strings.NewReader("123 456 k789 123")
split := func(data []byte, atEOF bool) (advance int, token []byte, err error) {
    // scanWords 按照 space 进行分割
    advance, token, err = bufio.ScanWords(data, atEOF)
    fmt.Printf("data=%q\n", data)
    fmt.Printf("advance=%d\n", advance)
    fmt.Printf("token=%q\n", token)
    fmt.Printf("atEOF=%t\n", atEOF)
    if strings.Trim(string(token), " ") != "" {
        _, err = strconv.ParseInt(string(token), 10, 32)
    }

    return
}

scanner := bufio.NewScanner(r)
scanner.Split(split)

for scanner.Scan() {
    fmt.Println("scan text=", scanner.Text())
    fmt.Println("=======")
}

if err := scanner.Err(); err != nil {
    fmt.Printf("%s", err)
}

上面的例子中我们定义了一个 SplitFunc,正如 SplitFunc 签名一样,他接受三个参数,分别是待处理的数据 data,是否还有更多的数据要处理的标识 atEOF,然后返回的是当前已经处理的数据的字节长度 advance,已经处理的字节数组 token,以及一个可选的错误 err。

advance 的计算是从当前剩下要处理的数据首位 0 的位置开始一直到下一个分割符,并且包含分隔符占用的字节,可以对照看以下输出就能明白:

data="123 456 k789 123"
advance=4         //从 1 开始直到下一个空格
token="123"
atEOF=false
scan text= 123
=======
data="456 k789 123"
advance=4
token="456"
atEOF=false
scan text= 456
=======
data="k789 123"   
advance=5   //从 k 开始直到下一个空格
token="k789"
atEOF=false
strconv.ParseInt: parsing "k789": invalid syntax%

而且需要注意的是,scanner 在遇到一个错误之后就停止 Scan 了,上面的 ParseInt 发生错误之后之后的 Scan 也不会输出。

File 类型和 bufio

Go 文件操作详解

如图 File 是实现了 io.Readerio.Writer 两个 interface 的 type,而 bufio 提供的几种操作都以这两个 interface 为基础实现文件的读写,也就是说只要 type 实现了 io.Reader 就可以使用 bufio 读取,实现了 io.Writer 就可以使用 bufio 输出。

str := strings.NewReader(strings.Repeat("ab", 10))
buf := make([]byte, 2)
reader := bufio.NewReader(str)

如上代码 str 是一个 string 的 Reader,然后就可以使用 bufio进行高效读取。

文件的输出

文件的写入类似文件的读取,Go 提供了 CreateOpenFile 打开文件进行写入或追加。

Create 会打开一个文件,默认的模式是 O_RDWR 即读和写,如果原来的文件已经存在则清空,如果不存在则新创建一个。

file, err := os.Create("new.txt")
if err != nil {
    fmt.Println(err)
}

defer file.Close()

file.WriteString(time.Now().Local().String())

OpenFile 提供了更灵活的方式打开一个文件,他接受三个参数,依次是文件名,打开文件的 flag,以及文件权限。

file, err := os.OpenFile("new.txt", os.O_RDWR|os.O_CREATE, 0775)
if err != nil {
    fmt.Println(err)
}

defer file.Close()
file.WriteString(time.Now().Local().String())

除了 WriteString,file 类型还提供了 Write 方法,区别是 Write 接受的是 []byte 。

使用 bufio.Writer 进行文件输出

上面我们提到过 bufio 提供了 Writer 来进行高效的输出,如何使用呢?

Writer 实际上是一个内部包含 buffer 的特殊 struct,其结构大致如下:

type Writer struct {
    err error
    buf []byte
    n   int
    wr  io.Writer
}

buf 这个 field 就是缓冲输出内容的,当满足指定 size 之后,Writer 才会把 buf 中的内容通过 wr 写到输出对象。

wr := bufio.NewWriterSize(os.Stdout, 38)
	count := 0
	for {
		wr.WriteString(time.Now().Format("2006-01-02 15:04:05"))
		time.Sleep(time.Second * 1)
		fmt.Println("\ncount ", count)
		count++
		if count > 10 {
			break
		}

	}
wr.Flush()

上面的代码会在 buf 的 size 满足 38 之后输出到标准输出,可以运行代码查看输出时间隔 2 秒产生的:

count  0
count  1
2019-03-10 14:01:022019-03-10 14:01:03
count  2
count  3
2019-03-10 14:01:042019-03-10 14:01:05
count  4
count  5
2019-03-10 14:01:062019-03-10 14:01:07
count  6
count  7
2019-03-10 14:01:082019-03-10 14:01:09
count  8
count  9
2019-03-10 14:01:102019-03-10 14:01:11
count  10
2019-03-10 14:01:12

默认情况下 bufio.Writer 指定的 size 大小是 defaultBufSize = 4096,像上面的代码一样可以通过 NewWriterSize 来改变这个大小。

需要注意的是,Writer 在遇到错误之后不会接着执行后面的输出,看以下代码:

type Writer int

func (*Writer) Write(p []byte) (n int, err error) {
	fmt.Printf("Write: %q\n", p)
	return 0, errors.New("IO Error!")
}

func main() {
	wr := bufio.NewWriterSize(new(Writer), 3)
	wr.Write([]byte{'a'})
	wr.Write([]byte{'b'})
	wr.Write([]byte{'c'})
	wr.Write([]byte{'d'})
	err := wr.Flush()
	fmt.Println(err)
}

输出:

Write: "abc"
IO Error!

最后一个字符 d 没有输出

ioutil 包的文件读写

除了上面提到的对文件的读写操作, io/ioutil 中提供了几个便捷的函数来读写文件,分别是:

WriteFileReadFile ,他们可以直接对文件进行写入和读取,省去了一个打开的过程。


以上所述就是小编给大家介绍的《Go 文件操作详解》,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对 码农网 的支持!

查看所有标签

猜你喜欢:

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

Types and Programming Languages

Types and Programming Languages

Benjamin C. Pierce / The MIT Press / 2002-2-1 / USD 95.00

A type system is a syntactic method for automatically checking the absence of certain erroneous behaviors by classifying program phrases according to the kinds of values they compute. The study of typ......一起来看看 《Types and Programming Languages》 这本书的介绍吧!

HTML 压缩/解压工具
HTML 压缩/解压工具

在线压缩/解压 HTML 代码

RGB HSV 转换
RGB HSV 转换

RGB HSV 互转工具

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

HEX CMYK 互转工具