Golang: 常用的文件读写操作

Go 语言提供了很多文件操作的支持,在不同场景下,有对应的处理方式,今天就来系统地梳理一下,几种常用的文件读写的形式。

一、读取文件内容

1、按字节读取文件

这种方式是以字节为单位来读取,相对底层一些,代码量也较大,我们看下面代码:

// read-bytes.go

package main

import (
    "fmt"
    "io"
    "os"
)

func main()  {
    file, _ := os.Open("test.txt")

    defer file.Close()

    // 字节切片缓存 存放每次读取的字节
    buf := make([]byte, 1024)

    // 该字节切片用于存放文件所有字节
    var bytes []byte

    for {
        // 返回本次读取的字节数
        count, err := file.Read(buf)

        // 检测是否到了文件末尾
        if err == io.EOF {
            break;
        }

        // 取出本次读取的数据
        currBytes := buf[:count]

        // 将读取到的数据 追加到字节切片中
        bytes = append(bytes, currBytes...)
    }

    // 将字节切片转为字符串 最后打印出来文件内容
    fmt.Println(string(bytes))
}

2、结合 ioutil 来读取

如果我们不想那么麻烦,可以结合 ioutil 来精简上面的代码:

// read-with-util.go

package main

import (
    "fmt"
    "io/ioutil"
    "os"
)

func main()  {
    file, _ := os.Open("test.txt")

    defer file.Close()

    // ReadAll接收一个io.Reader的参数 返回字节切片
    bytes, _ := ioutil.ReadAll(file)

    fmt.Println(string(bytes))
}

由于 os.File 也是 io.Reader 的实现,我们可以调用 ioutil.ReadAll(io.Reader) 方法,将文件所有字节读取出来,省去了使用字节缓存循环读取的过程。

3、仅使用 ioutil 包来完成读取操作:

为了进一步简化文件读取操作,ioutil 还提供了 ioutil.ReadFile(filename string) 方法,一行代码搞定读取任务:

// read-by-util.go

package main

import (
    "fmt"
    "io/ioutil"
)

func main()  {
    bytes, _ := ioutil.ReadFile("test.txt")

    fmt.Println(string(bytes))
}

4、逐行读取:

有时候为了便于分析处理,我们希望能够逐行读取文件内容,这个时候可以 Scanner 来完成:

// read-line-by-line.go

package main

import (
    "bufio"
    "fmt"
    "os"
)

func main() {
    file, _ := os.Open("test.txt")

    defer file.Close()

    // 接受io.Reader类型参数 返回一个bufio.Scanner实例
    scanner := bufio.NewScanner(file)

    var count int

    for scanner.Scan() {
        count++
        
        // 读取当前行内容
        line := scanner.Text()

        fmt.Printf("%d %s\n", count, line)
    }
}

这简直就是 java.util.Scanner 翻版嘛,并且 Go 语言中的 Scanner 也可以接收不同的输入源,比如 os.Stdin 等。

上面代码直接打印出了每一行的数据,如果大家想得到最终文件的内容,可以创建一个字符串切片,每次逐行扫描时,将当前行内容追加到切片中即可。

以上就是几种常用的文件读取方式,当然还有其他更高级的方式,有机会再做总结。

二、写入文件操作

1、使用 ioutil 完成写入操作

上面我们介绍了 ioutil.ReadFile(filename string),与之对应地有 ioutil.WriteFile(filename string, ...) 方法,可以轻松完成写入操作:

// write-by-util.go

package main

import (
    "io/ioutil"
)

func main() {
    data := []byte("hello goo\n")

    // 覆盖式写入
    ioutil.WriteFile("test.txt", data, 0664)
}

我们看到,WriteFile() 方法需要传入三个参数,它的完整签名是:ioutil.WriteFile(filename string, data []byte, perm os.FileMode)。如果文件不存在,则会根据指定的权限创建文件,如果存在,则会先清空文件原有内容,然后再写入新数据。

需要注意最后一个参数,它是一个无符号 32 位整数,表示当前文件的权限,也是标准的 Unix 文件权限格式。

Unix 使用 -rwxrwxrwx 这样的形式来表示文件权限,其中:

  • 第1位:文件属性,- 表示是普通文件,d 表示是一个目录
  • 第2-4位:文件所有者的权限
  • 第5-7位:文件所属用户组的权限
  • 第8-10位:其他人的权限

在权限设置中:

  • r 表示 read,值为 4
  • w 表示 write,值为 2
  • x 表示 exec,值为 1

下面我们通过 os.FileMode 测试一下:

package main

import (
    "fmt"
    "os"
)

func showMode(code int) {
    fmt.Println(os.FileMode(code).String())
}

func main() {
    showMode(0777)
    showMode(0766)
    showMode(0764)
}

运行程序,控制台打印如下:

-rwxrwxrwx
-rwxrw-rw-
-rwxrw-r--

2、通过File句柄完成写入操作

上面我们曾使用过 os.Open(name string) 方法,这个方法是以只读方式打开文件的,os 包还提供了 os.OpenFile(name string, flag int, perm FileMode) 方法,通过指定额外的 读写方式文件权限 参数,使文件操作变得更为灵活。

其中,flag 有以下几种常用的值:

  • os.O_CREATE: create if none exists 不存在则创建
  • os.O_RDONLY: read-only 只读
  • os.O_WRONLY: write-only 只写
  • os.O_RDWR: read-write 可读可写
  • os.O_TRUNC: truncate when opened 文件长度截为0:即清空文件
  • os.O_APPEND: append 追加新数据到文件

在打开文件之后,我们可以通过 Write() 和 WriteString() 方法写入数据,最后通过 Sync() 方法将数据持久化到磁盘:

// write-by-file-descriptor.go

package main

import (
    "fmt"
    "os"
)

// 打印写入的字节数
func printWroteBytes(count int) {
    fmt.Printf("wrote %d bytes\n", count)
}

func main() {
    // 以指定的权限打开文件
    file, _ := os.OpenFile("test2.txt", os.O_RDWR | os.O_APPEND | os.O_CREATE, 0664)

    defer file.Close()

    data := []byte("hello go\n")

    // 写入字节
    count, _ := file.Write(data)

    printWroteBytes(count)

    // 写入字符串
    count, _ = file.WriteString("hello world\n")

    printWroteBytes(count)

    // 确保写入到磁盘
    file.Sync()
}

3、通过bufio包完成写入操作

这种方式其实是在 File 句柄上做了一层封装,调用方式和上面直接写入非常相似,大家仅做个参考:

// write-with-bufio.go

package main

import (
    "bufio"
    "fmt"
    "os"
)

func printWroteBytes(count int) {
    fmt.Printf("wrote %d bytes\n", count)
}

func main() {
    file, _ := os.OpenFile("test.txt", os.O_RDWR | os.O_APPEND | os.O_CREATE, 0664)

    defer file.Close()

    // 获取bufio.Writer实例
    writer := bufio.NewWriter(file)

    // 写入字符串
    count, _ := writer.Write([]byte("hello go\n"))

    fmt.Printf("wrote %d bytes\n", count)

    // 写入字符串
    count, _ = writer.WriteString("hello world\n")

    fmt.Printf("wrote %d bytes\n", count)

    // 清空缓存 确保写入磁盘
    writer.Flush()
}

以上就是常用的文件读写方式,今天就总结到这里吧,后续有机会再探讨更多内容。

posted @ 2019-08-29 07:21  liuhe688  阅读(12366)  评论(0编辑  收藏  举报