目录
文本处理
io/ioutil、os、bufio
是Go中最常用的文本处理的库io/ioutil、os
库都支持打开文件、读取文件、创建文件、写入文件、关闭文件bufio
库只支持读取文件和写入文件、创建文件,不支持打开文件和关闭文件
一、文件打开与关闭
- Go语言没有python中的
with 这种上下文管理的关键字
,所以我们要注意打开文件之后要记得用defer
手动关闭文件 - 共3中打开文件的方式
1. os.open
打开
- 使用
os.Open()
或os.OpenFile()
打开文件(Open
本质还是调用了OpenFile
) - 需要用
defer
手动关闭文件
函数定义:
func Open(name string) (*File, error) {
return OpenFile(name, O_RDONLY, 0)
}
- 可以看出,Open()函数默认为只读,只接收了一个文件路径参数(支持相对路径)
- 示例:
func main() {
file,err:=os.Open("./xx.txt") // 打开文件,本质是调用了 OpenFile(name, O_RDONLY, 0)
if err != nil {
fmt.Println("打开文件出错:",err)
}
defer file.Close() // 关闭文件
fmt.Println(file) // 文件句柄
}
2. os.OpenFile
打开(推荐)
- 需要用
defer
手动关闭文件
函数定义:
func OpenFile(name string, flag int, perm FileMode) (*File, error)
- flag:打开模式
- perm:打开权限
示例:
func main() {
file,err:=os.OpenFile("./xx.txt",os.O_RDONLY,0) // 打开文件
if err != nil {
fmt.Println("打开文件出错:",err)
}
defer file.Close() // 关闭文件
fmt.Println(file) // 文件句柄
}
(1)flag参数
模式 | 说明 |
---|---|
os.O_WRONLY | 只写 |
os.O_CREATE | 创建文件 |
os.O_RDONLY | 只读 |
os.O_RDWR | 读写 |
os.O_TRUNC | 清空 |
os.O_APPEND | 追加 |
(2)prem参数(文件权限)
- 同Linux系统中文件的权限功能相同,点击查看详情:十七、权限相关
- 下面简要说下
# 关于- rwx rwx rwx:
第一个 - :代表这是一个普通文件(regular), 其中其他文件类型还包括了
d--------->目录文件(directory)
l --------->链接文件(link)
b--------->块设备文件(block)
c--------->字符设备文件(character)
s--------->套接字文件(socket)
p--------->管道文件(pipe)
# 一共有三组rwx(下图中的每一个框起来的rwx对应一组用户权限),分别是以下几种用户的rwx权限:
第一组:该文件拥有者的权限
第二组:该文件拥有者所在组的其他成员对该文件的操作权限
第三组:其他用户组的成员对该文件的操作权限
# r表示读权限,w表示写权限,x表示执行权限
# 值是使用八进制进行计算的;例如:-rwxrwxrwx(前面的-代表这是一个普通文件)文件权限,计算过程如下:
rwx rwx rwx
111 111 111
7 7 7
此时该文件的权限值为777
# 其他
777 代表
该文件拥有者对该文件拥有读写操作的权限
该文件拥有者所在组的其他成员对该文件拥有读写操作的权限
其他用户组的成员对该文件也拥有读写操作权限
666代表
该文件拥有者对该文件拥有读写的权限但是没有操作的权限
该文件拥有者所在组的其他成员对该文件拥有读写的权限但是没有操作的权限
其他用户组的成员对该文件也拥有读写权限但是没有操作的权限
# 所以
r(读)04,w(写)02,x(执行)01
3. ioutil.ReadFile
打开
- 其实调用的是
os.ReadFile()
方法,os.ReadFile()
是对os.open()
的封装 ioutil.ReadFile() 和 os.ReadFile()
都会打开文件并将文件内容一次性全读到内存中,在处理大文件时不友好- 无需手动关闭文件,在其函数内部已经封装了关闭文件的方法
函数定义:
func ReadFile(filename string) ([]byte, error) { // 本质是调用了 OpenFile(name, O_RDONLY, 0)
return os.ReadFile(filename)
}
- 其实是对 os.ReadFile()函数的封装,os.ReadFile()内部做了对文件的关闭,所以ioutil.ReadFile()不需要手动defer去关闭文件
二、文件读取
-
Go中文件的读取共有如下几种方式
-
使用
File类型
自带的Read()
方法,前提是要先打开文件。可以使用os.Open()
或os.OpenFile()
打开文件(os.Open()
本质还是调用了os.OpenFile()
) -
使用
bufio
库的Read
方法,使用bufio.NewReader(文件对象)
,但需要先打开文件 -
使用
io/ioutil
库的ReadAll()
,使用ioutil.ReadFile(文件路径)
,ioutil.ReadFile(文件路径)
本质还是调用了os.OpenFile()
-
1. File.Read
普通读取文件
- 类似python中的
文件对象.read(n)
方法,n为读取的字节数 - 可以和for循环组合使用,读取出文件的全部内容
- 属于分块读取
函数的定义:
func (f *File) Read(b []byte) (n int, err error)
- 它接收一个字节切片,返回读取的字节数和可能的具体错误,读到文件末尾时会返回0和io.EOF
(1)简单读文件
读取文件的简单实例:
func main() {
file,err:=os.OpenFile("./xx.txt",os.O_RDONLY,0) // 打开文件
if err != nil {
fmt.Println("打开文件出错:",err)
}
defer file.Close() // 关闭文件
fmt.Println(file) // 文件句柄
var buf []byte=make([]byte,1024)
//var buf []byte=make([]byte,8) // 假设文件就9个字节,使用8字节的切片读是读不全的
n,err:=file.Read(buf)
if err == io.EOF {
fmt.Println("文件读取完毕")
return
}
if err != nil {
fmt.Println("读取文件出错",err)
return
}
fmt.Printf("读了%d字节个数据,数据内容为:%s",n,string(buf[:n])) // string() 函数可以将字节切片直接转换成字符串
}
(2)循环读取文件
循环读取出文件的全部内容(上面如果buf大小太小,文件可能读不完,所以我们要循环读出所有内容)
func main() {
file,err:=os.OpenFile("./xx.txt",os.O_RDONLY,0) // 打开文件
if err != nil {
fmt.Println("打开文件出错:",err)
}
defer file.Close() // 关闭文件
fmt.Println(file) // 文件句柄
var buf []byte=make([]byte,5) // 存放每次读出的字节
var content []byte // 存放所有读出来的字节
// for{} 无限循环,类似python的while True
for {
n,err:=file.Read(buf)
if err == io.EOF {
fmt.Println("文件读取完毕")
break
}
if err != nil {
fmt.Println("读取文件出错",err)
break
}
content=append(content,buf[:n]...) // 把每次读出的数据,追加到content中
}
fmt.Printf("数据内容为:%s",string(content))
}
2. bufio
读取文件
bufio
是在上面的普通读文件中的File 类型
的基础上封装了一层API,支持更多的功能- 要先手动打开文件,再进行文件的读取,最后再手动关闭文件
- 共有两种读取方式
- 分块读取
bufio.NewReader()
- 按行读取,有3个实现方法:
reader.ReadLine()、reader.ReadString('\n')、bufio.NewScanner(file)
- 分块读取
(1)分块读取
func main() {
file,err:=os.Open("./xx.txt") // 打开文件
if err != nil {
fmt.Println("打开文件出错:",err)
}
defer file.Close() // 关闭文件
var buf =make([]byte,5) // 定义好每次读取的块大小,其实就是一个字节切片
reader:=bufio.NewReader(file)
for {
n,err:=reader.Read(buf)
if err == io.EOF {
fmt.Println("读完毕")
break
}
if err!=nil {
fmt.Println("读取出错:",err)
return
}
fmt.Println(string(buf[:n]))
}
}
(2)按行读取
i. reader.ReadLine()
func main() {
file,err:=os.Open("./xx.txt")
if err != nil {
fmt.Println("打开文件出错:",err)
}
defer file.Close() // 关闭文件
reader:=bufio.NewReader(file)
for {
line,_,err:=reader.ReadLine()
if err == io.EOF {
break
}
fmt.Println(string(line))
}
}
ii. reader.ReadString('\n')
func main() {
file,err:=os.Open("./xx.txt")
if err != nil {
fmt.Println("打开文件出错:",err)
}
defer file.Close() // 关闭文件
reader:=bufio.NewReader(file)
for {
line,err:=reader.ReadString('\n') // 按 \n 这个字符分隔读
if err == io.EOF {
if len(line)>0{
fmt.Println(line)
}
break
}
fmt.Println(string(line))
}
}
iii. bufio.NewScanner()
func main() {
file,err:=os.Open("./xx.txt")
if err != nil {
fmt.Println("打开文件出错:",err)
}
defer file.Close() // 关闭文件
s := bufio.NewScanner(file) // 创建一个NewScanner
for s.Scan() { //返回true表示没读完
fmt.Println(s.Text()) // 把读出的内容,转成字符串
}
}
3. 一次性全部读取
-
共两种方式
-
ioutil.ReadFile() 和 os.ReadFile()
可以打开并读取文件全部内容到内存,然后自动关闭文件 -
先手动打开文件,然后使用
io.Reader
读取文件类型的全部内容,再手动关闭文件
-
1. ioutil.ReadFile() 和 os.ReadFile()
func main() {
content,err:=ioutil.ReadFile("./xx.txt")
// content,err:=os.ReadFile("./xx.txt")
if err!=nil {
fmt.Println("读取文件出错:",err)
}
fmt.Println("文件内容为:",string(content))
}
2. io.Reader
io/ioutil库的 ReadAll函数定义:
func ReadAll(r io.Reader) ([]byte, error) {
return io.ReadAll(r)
}
- 其实是调用了io.ReadAll()
func main() {
file,err:=os.Open("./xx.txt") // 1.手动打开文件
if err != nil {
fmt.Println("打开文件出错:",err)
}
defer file.Close() // 2.手动关闭文件
content,err:=ioutil.ReadAll(file) // 3.传入io.Reader
if err!=nil {
fmt.Println("读取文件出错:",err)
}
fmt.Println("文件内容为:",string(content))
}
二、文件写入
- 想要写入文件,分为两种情况
- 文件已存在,打开文件后向文件中进行覆盖写、追加写
- 文件不存在,创建文件再进行写入
- 使用
os.OpenFile()
和上面我们提到的打开文件时的flag 参数
,指定模式来打开文件,之后再结合文件写入相关的函数进行文件的写入
1. 文件写入时的打开文件
使用os库中的两个函数
func OpenFile(name string, flag int, perm FileMode) (*File, error) {} // 根据后面的flag参数
func Create(name string) (*File, error) {}
// Create函数:文件不存在,创建并打开文件,文件存在则打开文件进行读取和清空写,本质还是OpenFile--》return OpenFile(name, O_RDWR|O_CREATE|O_TRUNC, 0666)
2. File.Write
(将字节写入文件)
func main() {
file,err:=os.Create("xxx.txt") // 创建并打开文件
if err != nil {
fmt.Println("打开文件错误:",err)
return
}
defer file.Close()
//d2 := []byte{104, 101, 108, 108, 111, 32, 119, 111, 114, 108, 100}
//d2 := []byte("hello world")
d2 := []byte{'h','e','l','l','o'}
n,err:=file.Write(d2)
if err != nil {
fmt.Println("写入文件出错:",err)
}
fmt.Printf("%d字节写入文件成功",n)
}
func main() {
file, err := os.OpenFile("xx.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()
str := "hello 沙河"
file.Write([]byte(str)) // 写入字节切片数据
file.WriteString("hello 小王子") //直接写入字符串数据
}
// xxx.txt 内容:hello 沙河hello 沙河hello 小王子
3. File.WriteString
(将字符串写入文件)
- 返回写入的字节数和错误
func main() {
file,err:=os.Create("xxx.txt") // 创建并打开文件
if err != nil {
fmt.Println("打开文件错误:",err)
return
}
defer file.Close()
s:="hello world"
n,err:=file.WriteString(s)
if err != nil {
fmt.Println("写入文件出错:",err)
}
fmt.Printf("%d字节写入文件成功",n)
}
4. bufio.NewWriter
(推荐)
func main() {
file, err := os.OpenFile(".\\hsw_test\\xx.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("hello沙河\n") // 将数据先写入缓存
}
writer.Flush() // 将缓存中的内容写入文件
}
5. ioutil.WriteFile
- ioutil.WriteFile 函数为清空写
- ioutil.WriteFile调用的是 os.WriteFile ,而 os.WriteFile 调用的是 OpenFile(name, O_WRONLY|O_CREATE|O_TRUNC, perm) 和 File.Write
- 写完自动关闭文件
- 示例
func main() {
err:=ioutil.WriteFile("xxx.txt",[]byte("xxx is nb"),0666)
if err != nil {
fmt.Println("写入文件出错",err)
return
}
}
三、其他扩展
1. 按行写
func main() {
file, err := os.OpenFile(".\\hsw_test\\xx1.txt", os.O_CREATE|os.O_WRONLY, 0666)
if err != nil {
fmt.Println("打开文件出错:", err)
return
}
defer file.Close()
d := []string{"Welcome to the world of Go", "Go is a compiled language.", "It is easy to learn Go."}
for _, v := range d {
fmt.Fprintln(file, v)
if err != nil {
fmt.Println(err)
return
}
}
}
/*
xx1.txt:
Welcome to the world of Go
Go is a compiled language.
It is easy to learn Go.
*/
2. 追加写
// 只要打开文件:file,err:=os.OpenFile(".\\hsw_test\\xx1.txt",os.O_APPEND|os.O_WRONLY,0666)
func main() {
file,err:=os.OpenFile(".\\hsw_test\\xx1.txt",os.O_APPEND|os.O_WRONLY,0666)
if err != nil {
fmt.Println("打开文件出错:",err)
return
}
defer file.Close()
d := []string{"Welcome to the world of Go", "Go is a compiled language.", "It is easy to learn Go."}
for _, v := range d {
fmt.Fprintln(file, v)
if err != nil {
fmt.Println(err)
return
}
}
}
3. 并发写
- 当多个 goroutines 同时(并发)写文件时,我们会遇到竞争条件(race condition)。因此,当发生同步写的时候需要一个 channel 作为一致写入的条件
- 原理类似python多进程中的数据交互时使用的队列
queue
,将各个生产者 goroutines 产生的数据放入 channel 中,再定义n个消费者 goroutines 从channel 中取数据,从而避免竞争条件
实现过程:
1. 创建一个 channel 用来读和写这个随机数。
2. 创建 100 个生产者 goroutine。每个 goroutine 将产生随机数并将随机数写入到 channel 里。
3. 创建一个消费者 goroutine 用来从 channel 读取随机数并将它写入文件。这样的话我们就只有一个 goroutinue 向文件中写数据,从而避免竞争条件。
4. 一旦完成则关闭文件
***************************分隔线***************************
1. 我们开始写产生随机数的 produce 函数
func produce(data chan int, wg *sync.WaitGroup) {
n := rand.Intn(999)
data <- n
wg.Done()
}
// 分析:上面的方法产生随机数并且将 data 写入到 channel 中,之后通过调用 waitGroup 的 Done 方法来通知任务已经完成。
2. 再让我们看看将数据写到文件的函数
func consume(data chan int, done chan bool) {
f, err := os.Create("concurrent")
if err != nil {
fmt.Println(err)
return
}
for d := range data {
_, err = fmt.Fprintln(f, d)
if err != nil {
fmt.Println(err)
f.Close()
done <- false
return
}
}
err = f.Close()
if err != nil {
fmt.Println(err)
done <- false
return
}
done <- true
}
/*
分析: 这个 consume 的函数创建了一个名为 concurrent 的文件。然后从 channel 中读取随机数并且写到文件中。一旦读取完成并且将随机数写入文件后,通过往 done 这个 cahnnel 中写入 true 来通知任务已完成
*/
3. 下面我们写 main 函数,并完成这个程序。下面是我提供的完整程序
package main
import (
"fmt"
"math/rand"
"os"
"sync"
)
// 生产者,每个生产者随机产生一个999内的数字
func producer(data chan int, wg *sync.WaitGroup) {
r := rand.Intn(999)
data <- r
wg.Done()
}
func consumer(data chan int, done chan bool) {
file, err := os.Create("concurrent") // 打开一个文件
if err != nil {
fmt.Println(err)
return
}
for d := range data {
_, err := fmt.Fprintln(file, d)
if err != nil {
fmt.Println("写入出错")
file.Close() // 关闭文件
done <- false // 成功标志中写入false
return
}
}
file.Close() // 在for循环外面关闭文件
done <- true
}
func main() {
var (
data = make(chan int)
done = make(chan bool)
wg sync.WaitGroup
)
// 启动1000个协程生产随机数
for i := 0; i < 1000; i++ {
wg.Add(1)
go producer(data,&wg)
}
// 开启消费者,往文件中写随机数
go consumer(data,done)
go func() { //在另一个协程中等待所有生产者完成,然后关闭数据信道,不能写在主协程中
wg.Wait()
close(data)
}()
//从done中取出数据
res:=<-done
if res{ // true表示顺利写完
fmt.Println("顺利写完")
}else {// false 表示写出问题
fmt.Println("写出问题")
}
}
/*
分析:
main 函数创建写入和读取数据的 channel,创建 done 这个 channel,此 channel 用于消费者 goroutinue 完成任务之后通知 main 函数。创建 Waitgroup 的实例 wg,用于等待所有生产随机数的 goroutine 完成任务。
使用 for 循环创建 100 个 goroutines。调用 waitgroup 的 wait() 方法等待所有的 goroutines 完成随机数的生成。然后关闭 channel。当 channel 关闭时,消费者 consume goroutine 已经将所有的随机数写入文件, 将 true 写入 done 这个 channel 中,这个时候 main 函数解除阻塞并且打印 File written successfully
*/
标签:文件,err,fmt,文本处理,file,Println,os
From: https://www.cnblogs.com/Mcoming/p/18055996