Go 在 os 中提供了文件的基本操作,包括打开、创建、读写等操作,除此以外为了追求便捷以及性能上,Go 还在 io/ioutil 以及 bufio 提供一些其他函数供开发者使用,今天在这篇文章中,我们介绍一些常用文件操作在 Go 中是如何使用的。
读取文件
打开文件:os.Open() / os.OpenFile() 方法
Open(name string) (*File, error)
Open 方法是用来打开已经存在的文件读取文件内容,它打开的文件是只读的不能写。
OpenFile(name string, flag int, perm FileMode) (*File, error)
OpenFile 方法就不一样了,从参数上看它多了一个 FileMode 参数,可以指定打开文件的读写方式。
读取文件:os.Read() 方法
func (f *File) Read(b []byte) (n int, err error)
它接收一个字节切片,返回读取的字节数和可能的具体错误,读到文件末尾时会返回 0
和 io.EOF
。
/*打开文件 ,如果文件不存在将会新建,如果已存在,新写入的内容将追加到文件尾
os.O_RDONLY : 如果设置为只读,那么将写入不了数据
os.O_RDWR : 设置为读写
*/
f, err := os.OpenFile("/Users/yangyue/Desktop/1.txt", os.O_RDWR | os.O_APPEND | os.O_CREATE, 0666)
if err != nil {
fmt.Println(err)
}
defer f.Close()
f.WriteString("test a string!")
buf := make([]byte, 1024)
var str string
f.Seek(0, io.SeekStart)
for {
n, ferr := f.Read(buf)
if ferr != nil && ferr != io.EOF {
fmt.Println(ferr.Error())
break
}
if n == 0 {
break
}
fmt.Println(n)
str += string(buf[0:n])
}
fmt.Println(str)
}
(f *File) Seek(offset int64, whence int) (ret int64, err error)
方法用来设置文件指针的位置,offet
是偏移量,whence
的取值可以是下面的三个:
const (
SeekStart = 0 // 相对文件开始偏移
SeekCurrent = 1 // 相对文件当前位置偏移
SeekEnd = 2 // 相对文件末尾偏移
)
io/ioutil
包
io/ioutil
包的 ReadFile 方法能够读取完整的文件,只需要将文件名作为参数传入。
将文件整个读入内存:
func main() {
file, err := os.Open("/Users/yangyue/Desktop/1.txt")
if err != nil {
panic(err)
}
defer file.Close()
content, err := ioutil.ReadAll(file)
fmt.Println(string(content))
}
或者:
func main() {
content ,err :=ioutil.ReadFile("/Users/yangyue/Desktop/1.txt")
if err !=nil {
panic(err)
}
fmt.Println(string(content))
}
将文件整个读入内存,效率比较高,占用内存也最高。
bufio
包
在大多数文件操作中,我们可能只需要一行行读取文件就可以满足需要,在 Go 中如何读取行呢?至少在 os 这个 package 中好像没有找到相关操作,其实 Go 已经在其他包中提供了这个操作 bufio。
bufio 顾名思义就是带 buffer 的 IO,由于频繁读写磁盘会有相当的性能开销,因为一次磁盘的读写就是一次系统的调用,所以 Go 提供了一个 buffer 来缓冲读写的数据,比如多次写磁盘 bufio 就会把数据先缓冲起来,待 buffer 装满之后一次性写入,又比如多次读数据,bufio 会预先按照 buffer 的大小(一般是磁盘 block size 的整数倍)尽量多的读取数据,也就是采用预读的技术以提高读的性能。
bufio 提供了 Reader 、Writer、Scanner 来进行文件的读写,其中 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 提供的默认的 SplitFunc 是 ScanLines,也就是 scanner.Text() 方法使用就是这个 splitfunc。
预定义的SplitFunc 除了 ScanLines 还有:
- ScanLines(默认)
- ScanWords 逐个单词扫描
- ScanRunes(对于遍历 UTF-8 码点非常有用,而不是字节)
- ScanBytes 遍历字节
func main() {
filepath := "/Users/yangyue/Desktop/1.txt"
file, err := os.Open(filepath)
if err != nil {
fmt.Println(err)
return
}
defer file.Close()
scanner := bufio.NewScanner(file)
scanner.Split(bufio.ScanWords)
for scanner.Scan() {
fmt.Println(scanner.Text())
}
}
写入文件
写入文件必须以先打开文件为前提上面我们说到 os.OpenFile()
方法提供了文件打开之后的操作模式指令:
func OpenFile(name string, flag int, perm FileMode) (*File, error) {
...
}
其中:name 即要打开的文件名 ,flag 参数表示打开文件的模式,有以下几种:
模式 | 含义 |
---|---|
os.O_WRONLY | 只写 |
os.O_CREATE | 创建文件 |
os.O_RDONLY | 只读 |
os.O_RDWR | 读写 |
os.O_TRUNC | 清空 |
os.O_APPEND | 追加 |
perm 参数表示文件权限,就是 Linux 文件系统对应的文件权限,一个八进制数。这个参数只对 Linux 系统有效,在 Windows 下无效。
r(读)04,w(写)02,x(执行)01
Linux 下权限的粒度有 拥有者 、群组 、其它组 三种,一般来说普通文件授予读写的权限即可,所以大部分的场景:666 就够用。
原生 os 包写入文件
func main() {
file, err := os.OpenFile("/Users/yangyue/Desktop/1.txt", os.O_CREATE|os.O_RDWR|os.O_APPEND, 666)
if err != nil {
fmt.Println("open file failed, err:", err)
return
}
defer file.Close()
str := "hello world"
file.Write([]byte(str))
file.WriteString("\nhello string")
}
上面的示例在 os.OpenFile
中设置文件的操作权限为:创建,读写,追加,所以你多次执行这段代码每次都会追加,如果你不想追加就把 os.O_APPEND 去掉即可。
使用 io 包写入文件
func main() {
var writeString = "测试文件写入啦啦啦\n"
var filename = "/Users/yangyue/Desktop/1.txt"
var f *os.File
var err1 error
f, err1 = os.OpenFile(filename, os.O_CREATE|os.O_RDWR|os.O_APPEND, 0666)
if err1 != nil {
fmt.Println(err1)
}
defer f.Close()
if _, err := io.WriteString(f, writeString); err != nil {
fmt.Println(err)
os.Exit(1)
}
}
使用 ioutil.WriteFile
func main() {
filepath:= "/Users/yangyue/Desktop/1.txt"
content := []byte("测试文字\n 测试字母hahahah\n")
err := ioutil.WriteFile(filepath, content, 0644)
if err != nil {
panic(err)
}
}
这种方式每次都会覆盖 1.txt 内容,如果 1.txt 文件不存在会创建。
使用 bufio.NewWriter 写入文件
func main() {
file, err := os.OpenFile("/Users/yangyue/Desktop/1.txt", os.O_CREATE|os.O_TRUNC|os.O_WRONLY, 0666)
if err != nil {
fmt.Println("open file failed, err:", err)
return
}
defer file.Close()
writer := bufio.NewWriter(file)
for i := 0; i 10; i++ {
writer.WriteString("测试文件写入操作:bufio.NewWriter\n")
}
writer.Flush() //将缓存中的内容写入文件
}
bufio.NewWriter
的写入是先将数据写入缓存,等最后执行 Flush 的时候才写入磁盘。
gob 序列化
序列化就是将对象的状态信息转换为可以存储或传输的形式的过程。
Gob 是 Go 中所特有的序列化技术,它支持除 interface,function,channel 外的所有 Go 数据类型。序列化使用 Encode(s),反序列化使用 decoder.Decode(s1)。
import (
"encoding/gob"
"fmt"
"os"
)
type User struct {
Name string
Age int
}
func main() {
s := &User{"xiaoming", 20}
f, err := os.Create("/Users/yangyue/gob.td")
if err != nil {
fmt.Println(err)
return
}
defer f.Close()
//创建 Encoder 对像
encode := gob.NewEncoder(f)
//将s序列化到文件中
encode.Encode(s)
//重置文件指针到开始位置
f.Seek(0, os.SEEK_SET)
decoder := gob.NewDecoder(f)
var u1 User
//反序列化对像
decoder.Decode(&u1)
fmt.Println(u1)
}
一些重要 API 操作使用
遍历目录下的文件:os.Readdir(n int)
n > 0 表示最多返回 n 个文件。如果小于等于 0 则返回所有。
func main() {
f, err := os.OpenFile("/Users/yangyue/Downloads/4425", os.O_RDONLY, 0666)
if err != nil {
fmt.Println(err)
return
}
arrFile, err1 := f.Readdir(0)
if err1 != nil {
fmt.Println(err1)
return
}
for k, v := range arrFile {
fmt.Println(k, "\t", v.Name(), "\t", v.IsDir())
}
}
当然你可以借助 IsDir()
方法实现递归遍历。
ioutil.ReadDir(dirname string) 也可以实现遍历功能
func main() {
arrFile, err := ioutil.ReadDir("/Users/yangyue/Downloads/4425")
if err != nil {
fmt.Println(err.Error())
return
}
for k, v := range arrFile {
fmt.Println(k, "\t", v.Name(), "\t", v.IsDir())
}
}
标准输入、输出和错误
os
包有三个可用变量 os.Stdout ,os.Stdin 和 os.Stderr ,它们的类型为 *os.File
,分别代表 系统标准输入,系统标准输出 和 系统标准错误 的文件句柄。
os.Stdin 可以用来监听控制台输入:
func main() {
reader := bufio.NewReader(os.Stdin)
fmt.Println("Simple Shell")
fmt.Println("---------------------")
for {
fmt.Print("-> ")
text, _ := reader.ReadString('\n')
// convert CRLF to LF
text = strings.Replace(text, "\n", "", -1)
if strings.Compare("hi", text) == 0 {
fmt.Println("hello, hahaha")
}
}
}
文件复制
借助io.Copy()
实现一个拷贝文件函数。
func CopyFile(dstName, srcName string) (written int64, err error) {
src, err := os.Open(srcName)
if err != nil {
fmt.Printf("open %s failed, err:%v.\n", srcName, err)
return
}
defer src.Close()
dst, err := os.OpenFile(dstName, os.O_WRONLY|os.O_CREATE, 0644)
if err != nil {
fmt.Printf("open %s failed, err:%v.\n", dstName, err)
return
}
defer dst.Close()
//调用io.Copy()
return io.Copy(dst, src)
}
func main() {
_, err := CopyFile("dst.txt", "src.txt")
if err != nil {
fmt.Println("copy file failed, err:", err)
return
}
fmt.Println("copy done!")
}