Press "Enter" to skip to content

38、Go文件操作

Last updated on 2022年2月16日

一、文件基本介绍

1、文件就是数据源,就是保存数据的地方;

2、文件流,数据在数据源(文件)和程序(内存)之间经历的路径;

3、输入流,数据从数据源到程序的路径;

4、输出流,数据从程序到数据源的路径;

5、Golang中os.FIle这个结构体封装了所有对文件的操作;

二、文件的基本操作

1、读取文件

带缓冲区的读取文件,适合大文件读取,每次默认只读取4096个字节

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

func main() {
// 打开一个文件
file, err := os.Open("./assets/a.txt")
if err != nil {
fmt.Println("文件打开搓出错:", err)
return
}
// 延迟关闭打开的文件
defer file.Close()

//带缓冲的读取文件,bufio 包
reader := bufio.NewReader(file)
for {
string, e := reader.ReadString('\n') // 遇到\n也就是换行结束一次
// 输出语句要放在判断是否读取完毕的上方,不然最后一行无法输出展示
fmt.Print(string) // 不需要Println,因为本身要把换行符读出来
// io.EOF表示读取到了文件的末尾
if e == io.EOF {
// 直接跳出循环即可
fmt.Println(e)      // EOF
fmt.Printf("%T", e) // *errors.errorString
break
}
}
}

一次性读取整个文件到内存,适用于文件不太大的情况

import (
"fmt"
"io/ioutil"
)

func main() {
// 一次性读取整个文件,返回byte切片和错误err
// 这个函数整合了文件的打开和关闭相关操作
content, err := ioutil.ReadFile("./assets/a.txt")
if err != nil {
fmt.Println("读取文件出错:", err)
}
fmt.Println(string(content))
 // 转朱阁,
 // 低绮户,
 // 照无眠,不应有恨。
}

2、写文件

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

func main() {
//使用os包下的方法: func OpenFile(name string, flag int, perm FileMode) (file *File, err error)
//第一个参数为文件路径,第二个为打开模式,第三个参数为打开的权限
//模式常量,可以组合:
// const (
// O_RDONLY int = syscall.O_RDONLY // 只读模式打开文件
// O_WRONLY int = syscall.O_WRONLY // 只写模式打开文件
// O_RDWR   int = syscall.O_RDWR   // 读写模式打开文件
// O_APPEND int = syscall.O_APPEND // 写操作时将数据附加到文件尾部
// O_CREATE int = syscall.O_CREAT // 如果不存在将创建一个新文件
// O_EXCL   int = syscall.O_EXCL   // 和O_CREATE配合使用,文件必须不存在
// O_SYNC   int = syscall.O_SYNC   // 打开文件用于同步I/O
// O_TRUNC int = syscall.O_TRUNC // 如果可能,打开时清空文件
// )

// 创建一个新的文件,写入5句任意字符串
file, err := os.OpenFile("./assets/abc.txt", os.O_CREATE|os.O_WRONLY, 0666)
if err != nil {
fmt.Println("打开/创建文件失败")
return
}
defer file.Close() // 关闭文件
// 带缓冲的写入数据
writer := bufio.NewWriter(file)
for i := 0; i < 5; i++ {
writer.WriteString(
fmt.Sprintf("你好,我是第%v句话\n", i+1))
}
//因为是带缓存的写入,其实内容现在还在缓存中,需要flush一下才能实际写到磁盘
writer.Flush()

fmt.Println("创建文件并写入数据完成")
}


./assets/abc.txt

你好,我是第1句话
你好,我是第2句话
你好,我是第3句话
你好,我是第4句话
你好,我是第5句话

3、写入新的内容并覆盖原内容

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

func main() {
// 打开一个存在的文件,打开模式为清空+写
file, err := os.OpenFile("./assets/abc.txt", os.O_TRUNC|os.O_WRONLY, 0666)
if err != nil {
fmt.Println("文件打开失败:", err)
return
}
defer file.Close()

writer := bufio.NewWriter(file)
// 写文件数据
for i := 0; i < 3; i++ {
writer.WriteString(
fmt.Sprintf("%v 今天天气晴朗\n", i))
}
writer.Flush() // 此处不flush的话文件中没有数据的,因为都在缓存中
fmt.Println("文件写入成功")
}

./assets/abc.txt

0 今天天气晴朗
1 今天天气晴朗
2 今天天气晴朗

4、追加内容到原文件

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

func main() {
// 打开一个存在的文件,并追加内容,打开模式为 追加+写
file, err := os.OpenFile("./assets/abc.txt", os.O_APPEND|os.O_WRONLY, 0666)
if err != nil {
fmt.Println("文件打开错误:", err)
return
}
defer file.Close()

writer := bufio.NewWriter(file)
for i := 0; i < 3; i++ {
writer.WriteString(
fmt.Sprintf("我是第%v个新增的数据\n", i+1))
}
writer.Flush()

fmt.Println("文件操作完毕")
}


./assets/abc.txt

0 今天天气晴朗
1 今天天气晴朗
2 今天天气晴朗
我是第1个新增的数据
我是第2个新增的数据
我是第3个新增的数据

5、读取一个存在的文件内容并追加新的内容

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

func main() {
// 打开一个存在的文件,读取展示到终端并追加新的你内容,打开模式为 读写+追加,
 // 注意此处不是读(O_RDONLY)+写(O_WRONLY)+追加(O_APPEND),读写是一起的(O_RDWR)
file, err := os.OpenFile("./assets/abc.txt", os.O_RDWR|os.O_APPEND, 0666)
if err != nil {
fmt.Println("文件打开错误:", err)
return
}
defer file.Close()

//读取文件内容到终端
reader := bufio.NewReader(file)
for {
str, e := reader.ReadString('\n')
fmt.Print(str) // 逐行打印内容
if e == io.EOF {
break
}
}

// 追加内容到文件
writer := bufio.NewWriter(file)
for i := 0; i < 3; i++ {
writer.WriteString(
fmt.Sprintf("我是第%v个读取后追加的数据\n", i+1))
}
writer.Flush()

fmt.Println("文件操作完毕")
}

./assets/abc.txt

0 今天天气晴朗
1 今天天气晴朗
2 今天天气晴朗
我是第1个新增的数据
我是第2个新增的数据
我是第3个新增的数据
我是第1个读取后追加的数据
我是第2个读取后追加的数据
我是第3个读取后追加的数据
三、练习题

编写一个程序,将一个文件的内容写入到另外一个文件,两个文件之前已经存在了。

1、使用缓冲方式写

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

func main() {
//读取一个文件
file, err := os.OpenFile("./assets/a.txt", os.O_RDONLY, 0666)
if err != nil {
fmt.Println("读取文件错误:", err)
}
defer file.Close()
reader := bufio.NewReader(file)

// 读取第二个文件
file2, err2 := os.OpenFile("./assets/abc.txt", os.O_WRONLY, 0666)
if err2 != nil {
fmt.Println("读取文件错误:", err)
}
defer file2.Close()
file2.Truncate(0) // 清空第二个文件原本内容
file2.Seek(0, 0)  // 清空后重置一下当前位置,不然原来的地方是空白
writer := bufio.NewWriter(file2)

// 把第一个文件的内容写入第二个文件
for {
str, e := reader.ReadString('\n')
writer.WriteString(str)
if e == io.EOF {
break
}
}
writer.Flush()

fmt.Println("文件操作完成")
}

2、直接读取文件并写入,这种还是要求小文件读写

import (
"fmt"
"io/ioutil"
)

func main() {
// 读取第一个文件的内容
content, err := ioutil.ReadFile("./assets/a.txt")
if err != nil {
fmt.Println("读取文件出错:", err)
return
}
// 直接写内容到第二个文件
// 此时写入第二个文件的效果是清空了再写新的
e := ioutil.WriteFile("./assets/abc.txt", content, 0666)
if e != nil {
fmt.Println("写入文件错误:", e)
return
}

fmt.Println("操作文件成功")
}
四、判断文件或文件夹是否存在
func main() {
path := "./assets/aa.txt"
hasFileExists(path)
}

// 判断文件或文件夹是否存在
func hasFileExists(path string) bool {
// 如果返回的错误为nil,说明文件或文件夹存在
fileinfo, err := os.Stat(path)
if err == nil {
fmt.Println("文件存在")
fmt.Println(fileinfo)
return true
}
// 如果返回的错误类型使用os.IsNotExist()判断为true,则文件或文件夹不存在
if os.IsNotExist(err) {
fmt.Println("文件或文件夹不存在")
return false
}
// 如果错误类型为其他,则不确定是否存在
fmt.Println("其他错误")
return false
}
五、拷贝文件
import (
"bufio"
"fmt"
"io"
"os"
)

// 自定义函数完成文件的拷贝
func CopyFile(dstFilePath, srcFilePath string) (written int64, err error) {
// 打开需要拷贝的源文件
dstFile, err := os.Open(dstFilePath)
if err != nil {
fmt.Println("打开源文件错误:", err)
return
}
defer dstFile.Close()
// 获取源文件的Reader对象
reader := bufio.NewReader(dstFile)

// 打开需要拷贝至的文件
srcFile, err := os.OpenFile(srcFilePath, os.O_CREATE|os.O_WRONLY, 0666)
if err != nil {
fmt.Println("打开目标文件错误:", err)
return
}
// 获取目标文件的Writer对象
writer := bufio.NewWriter(srcFile)

// 进行拷贝操作
return io.Copy(writer, reader)
}

func main() {
// 文件拷贝
// 将 ./image.jpeg 文件拷贝到 ./asets/images/image.jpeg
// 关键函数 io 包下的 Copy()
// func Copy(dst Writer, src Reader) (written int64, err error)

written, err := CopyFile("./image.jpeg", "./assets/images/image.jpeg")
if err != nil {
fmt.Println("拷贝文件出错:", err)
return
}
fmt.Println(written) // 65622, 返回成功拷贝的字节数,1kb = 1024字节
}
六、统计不同类型的文件字符的个数
import (
"bufio"
"fmt"
"io"
"os"
)

// 定义一个统计结构体
type charCount struct {
chCount    int // 字符个数
numCount   int // 数字个数
spaceCount int // 空格个数
otherCount int // 其他个数
}

func main() {
// 思路:打开一个文件,创建一个Reader对象
// 每读取一行,就统计该行有多少个英文、数字、空格和其他字符
// 然后将结果保存到一个结构体中

file, err := os.Open("./assets/abc.txt")
if err != nil {
fmt.Println("打开文件错误:", err)
return
}
defer file.Close()
// 创建reader对象
reader := bufio.NewReader(file)

var counter charCount
// 进行读取
for {
// 按行读取内容
str, err := reader.ReadString('\n') // string直接可以遍历,因为底层是byte切片
// 如果想统计中文,可以直接把str转为[]rune(str)
for _, v := range str {
// v为byte类型
switch {
// 像这种 a A 1-9之类的会在比较时直接转为ASCII对应的数字
case v >= 'a' && v <= 'z', v >= 'A' && v <= 'Z':
counter.chCount++
case v == ' ':
counter.spaceCount++
case v >= '0' && v <= '9':
counter.numCount++
default:
counter.otherCount++
}
}

// 读取完毕后就跳出
if err == io.EOF {
break
}
}

// 打印个数
fmt.Printf("字符的个数为%v,数字的个数为%v,空格的个数为%v,其他的个数为%v",
counter.chCount, counter.numCount, counter.spaceCount, counter.otherCount)

// 字符的个数为261,数字的个数为0,空格的个数为49,其他的个数为106
}
发表回复

您的电子邮箱地址不会被公开。 必填项已用 * 标注