# Go语言文件操作

# 1 文件的概念

文件是数据源(保存数据的地方)的一种,比如大家经常使用的word文档,txt文档,excel文件…都是文件。文件最主要的作用就是保存数据,它既可以保存一张图片,也可以保存视频,声音………

# 2 输入流和输出流

文件在程序是以流的形式操作的, 在Python,JAVA,Go ... 都一样;

输入流和输出流

# 3 打开和关闭文件

要想操作文件,有两个关键的概念,就是打开文件和关闭文件。 os包下有一个Open函数,如下:

//Open打开指定的文件进行读取。如果成功,可以使用返回File对象的方法进行读取;
//关联的文件描述符只有O_RDONLY模式。如果出现错误,则类型为*PathError。
//Open()函数返回的是一个 *File 文件类型的指针,和一个error。
func Open(name string) (*File, error)
1
2
3
4
//Close关闭文件,使其不能用于I/O。如果有错误,则返回一个。
func (f *File) Close() error
1
2

# 3.1 简单举例

package main

import (
	"fmt"
	"os"
)
func main(){
	//这里的file 有人叫对象,有人叫指针,有人叫句柄,无所谓--心态,哈哈
	//打开文件
	file, err := os.Open("D:/test.txt")
	if err!=nil{
		fmt.Println(err)
	}
	//这里简单打印下这个file,没有任何操作,现在是个指针而已
	fmt.Printf("file=%v",file)
	e:= file.Close()//关闭文件
	if e!=nil {
		fmt.Println(e)
	}
}
//输出结果
file=&{0xc000090780}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22

# 4 读取文件

go语言读取文件有很多种方式,基本上在标准库的 os包,io/ioutil包以及bufio包,比如下面几种

  • 1、os包下的Open()和Read()
  • 2、bufio包下的带缓冲的读取
  • 3、ioutil包下的读取

# 4.1 OS包下的Open()和Read()

os包提供了平台无关的操作系统功能接口。 里面的File 的各种方法也是可以获取文件相关信息,具体的可以看api。 比如读取文件,我们就可以用 Open() 和Read()方法 D:/test.txt内容如下图

test.txt内容

//从文件中读取到len(b)字节。它返回读取的字节数和遇到的任何错误。
//读取到文件末尾,Read返回0 io.EOF。
func (f *File) Read(b []byte) (n int, err error)
1
2
3

# os包下的Read()举例

package main
import (
	"fmt"
	"io"
	"os"
)

func main() {
	// 只读方式打开当前目录下的main.go文件
	file, err := os.Open("D:/test.txt")
	if err != nil {
		fmt.Println("打开文件异常, err:", err)
		return
	}
	defer file.Close() //退出时关闭
	// 使用Read方法读取数据
	var tmp = make([]byte, 128)
	n, err := file.Read(tmp)
	if err == io.EOF {
		fmt.Println("文件读完了")
		return
	}
	if err != nil {
		fmt.Println("读取文件错误, err:", err)
		return
	}
	fmt.Printf("读取了%d字节数据\n", n)
	fmt.Println(string(tmp[:n]))
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29

# 输出结果如下

读取了55字节数据
the first data
two Hello 中国
three world 成都
1
2
3
4

# 4.2 bufio-带缓冲区的方式读取

bufio是在file的基础上封装了一层API,支持更多的功能。

package main
import (
	"bufio"
	"fmt"
	"io"
	"os"
)
func main(){
	//打开文件
	file, err := os.Open("D:/test.txt")
	if err!=nil{
		fmt.Println(err)
	}
	defer file.Close()  //函数退出时关闭file对象
	//创建一个*Reader ,带缓冲区,注意默认是4096
	reader:=bufio.NewReader(file)
	//循环读取
	for{
		line,err:=reader.ReadString('\n')//读到换行就结束
		fmt.Print(line)
		if err==io.EOF{
			if len(line) != 0 {
				fmt.Println(line)
			}
			fmt.Println("文件读完了")
			break
		}
		if err != nil {
			fmt.Println("读取文件错误, err:", err)
			return
		}

	}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34

# 4.3 ioutil 包下的读取

更多ioutil说明请看ioutil-API

ioutil包下的函数

- func NopCloser(r io.Reader) io.ReadCloser   //其实就是个转换工具
- func ReadAll(r io.Reader) ([]byte, error) 	//从io.Reader中一次读取所有数据
- func ReadDir(dirname string) ([]os.FileInfo, error)//读取目录并返回排好序的文件和子目录名
- func ReadFile(filename string) ([]byte, error) //ReadFile 读取整个文件的内容
- func TempDir(dir, prefix string) (name string, err error)  //临时目录
- func TempFile(dir, pattern string) (f *os.File, err error)  //临时文件
- func WriteFile(filename string, data []byte, perm os.FileMode) error
1
2
3
4
5
6
7

# 注意点

ReadFile不需要写Close()内部已经封装了。而ReadAll()好像内部没写,我反正没找到。所以,这个不同一定要注意。

# ioutil读取文件举例

package main

import (
	"fmt"
	"io/ioutil"
)

// ioutil.ReadFile读取整个文件
func main() {
	content, err := ioutil.ReadFile("D:/test.txt")
	if err != nil {
		fmt.Println("读取文件错误, err:", err)
		return
	}
	fmt.Println(string(content))
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16

# 5 写文件操作应用实例

  • 1、os 包下的Read 和Write
  • 2、bufio包下的writer,bufio.NewWriter
  • 3、ioutil 下的WriteFile

# 5.1 os包下的写文件

os.OpenFile()函数能够以指定模式打开文件,从而实现文件写入相关功能。

func OpenFile(name string, flag int, perm FileMode) (*File, error) {
	...
}
1
2
3

name:要打开的文件名 flag:打开文件的模式。perm:文件权限,一个八进制数。r(读)04,w(写)02,x(执行)01。 打开文件的模式有以下几种:

模式 含义
os.O_WRONLY 只写
os.O_CREATE 创建文件
os.O_RDONLY 只读
os.O_RDWR 读写
os.O_TRUNC 清空
os.O_APPEND 追加

# 举例

package main

import (
	"fmt"
	"os"
)

func main() {
	filePath := "D:/new.txt"
	//打开文件,没有就创建一个,在linux 下rw可读可写
	file, err := os.OpenFile(filePath, os.O_RDWR|os.O_CREATE, 0666)
	if err!=nil{
		fmt.Println("打开文件失败")
	}
	defer  file.Close()
	var  str="我是写入的内容"
	n, e := file.WriteString(str)
	if e != nil {
		fmt.Println("写入文件错误", e)
		return
	}
	fmt.Println("写入的字节数是:", n)
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23

# 5.2 bufio包下的写文件

package main

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

func main() {
	filePath := "D:/new.txt"
	//打开文件,没有就创建一个,在linux 下rw可读可写
	file, err := os.OpenFile(filePath, os.O_RDWR|os.O_CREATE, 0666)
	if err != nil {
		fmt.Println("打开失败")
	}
	defer file.Close() //退出时关闭
	str := "我是bufio写入内容"
	writer := bufio.NewWriter(file)//创建一个Writer
	writer.WriteString(str) //将数据写入缓冲区
	writer.Flush() //将缓存中的内容写入文件
	//因为带缓冲区的writer ,是先将内容写入缓存,然后调用flush方法写入文件中
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22

# 5.3 ioutil下的写文件

//WriteFile 将data写入filename文件中,当文件不存在时会根据perm指定的权限进行创建一个,
//文件存在时会先清空文件内容。对于perm参数,我们一般可以指定为:0666,具体含义os包中讲解。
func WriteFile(filename string, data []byte, perm os.FileMode) error
1
2
3

# 举例

package main

import (
	"fmt"
	"io/ioutil"
)

func main() {
	filePath1 := "d:/new.txt"
	filePath2:="d:/xxx.txt"
	//读取文件new.txt的内容准备写入xxx.txt
	bytes, e := ioutil.ReadFile(filePath1)
	if e!=nil{
		fmt.Println("读取文件错误",e)
	}
	// 写入文件内容
	err := ioutil.WriteFile(filePath2, bytes, 0666)
	if err != nil {
		fmt.Println("写入文件错误, err:", err)
		return
	}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22

# 注意点

ioutil读和写文件就是省事,工具。 但是写文件要注意的是,它没有回自动创建,并且有会清空文件, 所以如果你要往文件中追加内容就不能用writeFile了,所以,如果要追加还是清空,根据需求来

# 6文件编程应用实例

# 6.1 拷贝文件

借助io.Copy()实现一个拷贝文件函数。io.Copy()简要说明如下

//从src复制到dst,直到在src上到达EOF或发生错误。它返回复制的字节数和复制时遇到的第一个错误(如果有的话)。
//成功复制将返回err==nil,而不是err==EOF。因为Copy被定义为从src读取到EOF,所以它不将从read读取的EOF视为要报告的错误。
//如果src实现WriterTo接口,则通过调用src.writeto(dst)实现copy。否则,如果dst实现了ReaderFrom接口,则通过调用dst.readfrom(src)来实现copy。
func Copy(dst Writer, src Reader) (written int64, err error)
1
2
3
4
package main

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

//CopyFile文件函数封装
//参数说明 dstFileName--目标文件路径 srcFileName--源文件路径
// 返回参数说明written--复制成功的字节数,err--复制失败的错误信息
func CopyFile(dstFileName, srcFileName string) (written int64, err error) {
	// 以读方式打开源文件
	reader, readError := os.Open(srcFileName)
	if readError != nil {
		fmt.Printf("从 %s 打开文件失败, err:%v.\n", srcFileName, err)
		return 0, readError
	}
	defer reader.Close() //退出时关闭

	// 以读写|创建的方式打开目标文件
	writer, writerError := os.OpenFile(dstFileName, os.O_RDWR|os.O_CREATE, 0666)
	if writerError != nil {
		fmt.Printf("从 %s 打开文件失败, err:%v.\n", dstFileName, err)
		return 0, writerError
	}
	defer writer.Close() //退出时关闭

	return io.Copy(writer, reader)
}
func main() {
	//把srcFile.txt这个文件复制给d:/dstFile.txt
	written, err := CopyFile("d:/dstFile.txt", "d:/srcFile.txt")
	if err != nil {
		fmt.Println("复制文件失败, err:", err)
		return
	}
	fmt.Println("复制成功!")
	fmt.Println("复制的字节数", written)

}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41

# 6.2 统计字符数量

看一个例子:统计一个文件中含有的英文、数字、空格以及其他字符数量

  • 实现思路: 打开文件——>逐行读——>记录每一行的统计数量——>得到总的统计
package main

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

// CharCount 统计个数的结构体
type  CharCount struct {
	EnCount int //英文个数
	NumCount int //数字个数
	SpaceCount int //空格个数
	OtherCount int //其他字符
}
func main()  {

	file, err := os.Open("d:/test.txt")
	if err!=nil{
		fmt.Println(err)
		return
	}
	defer  file.Close()
	count:=CharCount{}
	reader := bufio.NewReader(file)
	for{
		s, err := reader.ReadString('\n')
		if err==io.EOF{
			break
		}
		str:=[]rune(s)
		for _,v:=range str{
			switch{
			case v>'a'&&v<'z':
				fallthrough
			case v>'A'&&v<'Z':
				count.EnCount++
			case v==' '||v=='\t':
				count.SpaceCount++
			case v>0&&v<9:
				count.NumCount++
			default:
				count.OtherCount++
			}
		}
	}

	fmt.Printf(" 英文个数:%d\n 数字个数:%d\n 空格个数:%d\n 其他字符个数:%d\n",count.EnCount,count.NumCount,count.SpaceCount,count.OtherCount)
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50